用户注册



邮箱:

密码:

用户登录


邮箱:

密码:
记住登录一个月忘记密码?

发表随想


还能输入:200字

洋葱码农    -  云代码空间

—— 一直在改,从未有变

Linux中多线程编程

2014-06-03|1635阅||

摘要:    如我们所知,进程间可以通过管道,【socket】,信号,退出/等待以及运行环境来进行回话。线程间的通信也很容易。多个线程在一个单独的进程中运行,共享全局变量,因此线程间可以设置和读取这些全局变量来进行通信。不过得知道,对共享内存的访问可是线程的一个既有用又极其危险的特性。

    如我们所知,进程间可以通过管道,【socket】,信号,退出/等待以及运行环境来进行回话。线程间的通信也很容易。多个线程在一个单独的进程中运行,共享全局变量,因此线程间可以设置和读取这些全局变量来进行通信。不过得知道,对共享内存的访问可是线程的一个既有用又极其危险的特性。
    如果我们想写一个类似Unix平台上【wc】工具的程序来统计某两篇文件的单词个数(注意,【wc】是典型的单线程),怎么来设计一个多线程程序来计数并打印出两个文件的所有字数呢。以下我列出了可能实现的三种版本。

【版本一】:两个线程,一个计数器
    第一个版本程序创建分开的线程开对每个文件进行计算。所有的线程在检查到单词是对同一个计数器增值。图1体现了该思路:
—图1

    此版本的代码如下:
/* twordcount1.c - threaded word counter for two files. Version 1 */

#include  <stdio.h>
#include  <pthread.h>
#include  <ctype.h>

int	  total_words ;

main(int ac, char *av[])
{
	pthread_t t1, t2;		/* two threads */
	void	  *count_words(void *);

	if ( ac != 3 ){
		printf("usage: %s file1 file2\n", av[0]);
		exit(1);
	}
	total_words = 0;
	pthread_create(&t1, NULL, count_words, (void *) av[1]);
	pthread_create(&t2, NULL, count_words, (void *) av[2]);
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	printf("%5d: total words\n", total_words);
}
void *count_words(void *f)
{
	char *filename = (char *) f;
	FILE *fp;
	int  c, prevc = '\0';
	
	if ( (fp = fopen(filename, "r")) != NULL ){
		while( ( c = getc(fp)) != EOF ){
			if ( !isalnum(c) && isalnum(prevc) )
				total_words++;
			prevc = c;
		}
		fclose(fp);
	} else 
		perror(filename);
	return NULL;
}



    函数count_words是这样区分单词的:凡是一个非字母的或数字的字符跟在字母或数字的后面,那么这个字母或数字就是单词的结尾。当然这种思路忽略了最后一个单词,并且还吧类似‘U.S.A’这样的单词分成了三个。编译程序按照如下方式测试:



    twocount1.c产生的结果与wc并不相同,因为这两个程序对单词结尾的定义不同。但是尽管如此,细心的你难道就没发现问题?代码中两个线程同时对一个共享内存变量进行读写,这不会发生问题吗?正如你所想,这种方案是行不通的。如若不信,你可以多次测试,看看结果相同否。

    看到问题并分析了问题,接下来就是解决问题。

【版本二】:两个线程,一个计数器,一个互斥量
    两个线程如果要安全共享一个全局变量,它们需要对变量进行加锁。线程系统包涵了称为互斥锁的变量,他可以使线程间很好的合作,避免了对变量,函数以及资源的访问冲突。下面的twocount2.c将告诉大家如何创建和使用互斥变量。

    版本二代码如下:
/* twordcount2.c - threaded word counter for two files.	   */
/*                 version 2: uses mutex to lock counter   */

#include  <stdio.h>
#include  <pthread.h>
#include  <ctype.h>

int	        total_words ;    /* the counter and its lock */
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;

main(int ac, char *av[])
{
	pthread_t t1, t2;		/* two threads */
	void	  *count_words(void *);

	if ( ac != 3 ){
		printf("usage: %s file1 file2\n", av[0]);
		exit(1);
	}
	total_words = 0;
	pthread_create(&t1, NULL, count_words, (void *) av[1]);
	pthread_create(&t2, NULL, count_words, (void *) av[2]);
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	printf("%5d: total words\n", total_words);
}
void *count_words(void *f)
{
	char *filename = (char *) f;
	FILE *fp;
	int  c, prevc = '\0';
	
	if ( (fp = fopen(filename, "r")) != NULL ){
		while( ( c = getc(fp)) != EOF ){
			if ( !isalnum(c) && isalnum(prevc) ){
				pthread_mutex_lock(&counter_lock);
				total_words++;
				pthread_mutex_unlock(&counter_lock);
			}
			prevc = c;
		}
		fclose(fp);
	} else 
		perror(filename);
	return NULL;
}


    此程序的逻辑类似与图3:

    _图6
    细心的读者可以发现,尽在版本一种添加了三行代码。首先定义一个pthread_mutex_t类型的全局变量counter_lock,然后将它赋一个初值。另外改动的地方是对count_words的操作夹在两个函数pthread_mutex_lock和pthread_mutex_unlock的调用之间。现在两个线程可以安全地共享计数器了。当一个线程调用了pthread_mutex_lock时候,如果另外一个线程将这个互斥量锁住了,那这个线程只能阻塞等待这个锁被后者释放,才能对计数器进行写操作。


    版本二已经解决了版本一所带来的问题,但是还请读者认真思考下,版本二中对所有文件中的每个单词都会执行检查,设置与及释放锁的操作。考虑如果你统计一个单词量非常大的文件,这样带来的效率是不是很低呢?是的,那我们怎么办?抛弃互斥锁?那岂不是会引发并发问题。对,版本三将抛弃互斥锁,怎么解决并发问题呢?

【版本三】:两个线程,两个计数器,向线程传递多个参数
    版本三代码如下:
/* twordcount3.c - threaded word counter for two files.	
 *		 - Version 3: one counter per file
 */

#include  <stdio.h>
#include  <pthread.h>
#include  <ctype.h>

struct arg_set {		/* two values in one arg */
		char *fname;	/* file to examine	 */
		int  count;	/* number of words	 */
};

main(int ac, char *av[])
{
	pthread_t      t1, t2;		/* two threads */
	struct arg_set args1, args2;	/* two argsets */
	void	       *count_words(void *);

	if ( ac != 3 ){
		printf("usage: %s file1 file2\n", av[0]);
		exit(1);
	}
	args1.fname = av[1];
	args1.count = 0;
	pthread_create(&t1, NULL, count_words, (void *) &args1);

	args2.fname = av[2];
	args2.count = 0;
	pthread_create(&t2, NULL, count_words, (void *) &args2);

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	printf("%5d: %s\n", args1.count, av[1]);
	printf("%5d: %s\n", args2.count, av[2]);
	printf("%5d: total words\n", args1.count+args2.count);
}
void *count_words(void *a)
{
	struct arg_set *args = a;	/* cast arg back to correct type */
	FILE *fp;
	int  c, prevc = '\0';
	
	if ( (fp = fopen(args->fname, "r")) != NULL ){
		while( ( c = getc(fp)) != EOF ){
			if ( !isalnum(c) && isalnum(prevc) )
				args->count++;
			prevc = c;
		}
		fclose(fp);
	} else 
		perror(args->fname);
	return NULL;
}

  
    版本三为每个线程设置自己的计数器,从而避免了对于互斥两的使用。当线程返回时,再将这两个计数器的值加起来得到最后的结果。通过定义一个以文件名和该文件名中字数为成员的结构体解决了同时传递两个参数的问题。main函数中定义了两个这种类型的局部变量,并且将他们的地址传递给线程。传递本地结构体的指针的方法即避免了对互斥量的依赖,又消除了全局变量。

    进程的数据空间包涵了所有属于它的变量。此进程中运行的所有线程都拥有对这些变量的访问权。如果这些变量值不变的话,线程可以无误地读写它们。不过如果进程中的任何线程修改了一个变量值,所有对此变量的线程必须采取某种策略避免访问的冲突。在某一时刻,只有唯一的线程可以对变量进行访问。

    忠告:能不用多线程,就不要用多线程。学过【操作系统-精髓与设计原理】的朋友都应该了解,多线程编程怎么可能像这边描述的简单...

  

    

顶 1踩 0收藏
文章评论
    发表评论

    个人资料

    • 昵称: 洋葱码农
    • 等级: 中级程序员
    • 积分: 192
    • 代码: 4 个
    • 文章: 2 篇
    • 随想: 1 条
    • 访问: 4 次
    • 关注

    站长推荐