Unix/Linux编程实践一书 p440 14.5.2,介绍了使用条件变量进行线程同步。
程序是开两个线程分别统计两个文件的字数,都统计完后,主线程得出总文字数。
现在想要一个线程统计完成之后立即能够通知主线程,从而主线程能够立即打印出已经
完成的文件信息。就像各州选举,可以及时通告已经结束的州的选情一个道理。
书中程序的思想是
由muterx保护一个mailbox,子线程获得mailbox写权力后将统计好的字数信息写mailbox后,通知(pthread_cond_signal)主线程。
主线程一直在等待子线程写好mailbox的信号(pthread_cond_wait),然后读。读完mailbox后给出已读完信号。
注意子线程在主线程读mailbox的时候不能写mailbox,它要等待主线程的已读完信号。
子线程写完它的信息后结束。
主线程读完它需要的所有信息后结束(例如两个线程统计,则需要读两个)。
pthread_cond_wait(&flag, &lock) //特别注意pthread_cond_wait 会解锁lock
pthread_cond_signal(&flag, &lock)
但是感觉条件变量并不是很好的方法,例如对于书中的程序两个线程统计两个文件没有问题,但是如果
3个线程统计3个文件,就可能发生死锁。文件名之类都没变,只是3个线程统计3个文件。
代码如下:
1 /* twordcount4.c - threaded word counter for two files.
3 * functions to report results early
4 */
5
6 #include < stdio.h >
7 #include < pthread.h >
8 #include < ctype.h >
9
10 struct arg_set { /* two values in one arg */
11 char * fname; /* file to examine */
12 int count; /* number of words */
13 int tid;
14 };
15
16 struct arg_set * mailbox = NULL;
17 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
18 pthread_cond_t flag = PTHREAD_COND_INITIALIZER;
19
20 main( int ac, char * av[])
21 {
22 pthread_t t1, t2, t3; /* two threads */
23 struct arg_set args1, args2, args3; /* two argsets */
24 void * count_words( void * );
25 int reports_in = 0 ;
26 int total_words = 0 ;
27
28 if ( ac != 4 ){
29 printf( " usage: %s file1 file2 file3\n " , av[ 0 ]);
30 exit( 1 );
31 }
32 pthread_mutex_lock( & lock ); /* lock the report box now */
33
34 args1.fname = av[ 1 ];
35 args1.count = 0 ;
36 args1.tid = 1 ;
37 pthread_create( & t1, NULL, count_words, ( void * ) & args1);
38
39 args2.fname = av[ 2 ];
40 args2.count = 0 ;
41 args2.tid = 2 ;
42 pthread_create( & t2, NULL, count_words, ( void * ) & args2);
43
44 args3.fname = av[ 2 ];
45 args3.count = 0 ;
46 args3.tid = 3 ;
47 pthread_create( & t3, NULL, count_words, ( void * ) & args3);
48
49
50 while ( reports_in < 3 ){
51 printf( " MAIN: waiting for flag to go up\n " );
52 pthread_cond_wait( & flag, & lock ); /* wait for notify */
53 printf( " MAIN: Wow! flag was raised, I have the lock\n " );
54 sleep( 10 );
55 printf( " %7d: %s\n " , mailbox -> count, mailbox -> fname);
56 total_words += mailbox -> count;
57 if ( mailbox == & args1)
58 pthread_join(t1,NULL);
59 if ( mailbox == & args2)
60 pthread_join(t2,NULL);
61 sleep( 10 );
62 mailbox = NULL;
63 printf( " Ok,I have read the mail\n " );
64 pthread_cond_signal( & flag); /* announce state change */
65 reports_in ++ ;
66 }
67 printf( " %7d: total words\n " , total_words);
68 }
69 void * count_words( void * a)
70 {
71 struct arg_set * args = a; /* cast arg back to correct type */
72 FILE * fp;
73 int c, prevc = ' \0 ' ;
74
75 if ( (fp = fopen(args -> fname, " r " )) != NULL ){
76 while ( ( c = getc(fp)) != EOF ){
77 if ( ! isalnum(c) && isalnum(prevc) )
78 args -> count ++ ;
79 prevc = c;
80 }
81 fclose(fp);
82 } else
83 perror(args -> fname);
84 printf( " COUNT %d: waiting to get lock\n " , args -> tid);
85 pthread_mutex_lock( & lock ); /* get the mailbox */
86 printf( " COUNT %d: have lock, storing data\n " , args -> tid);
87 if ( mailbox != NULL ){
88 printf( " COUNT %d: oops..mailbox not empty. wait for signal\n " , args -> tid);
89 pthread_cond_wait( & flag, & lock );
90 }
91 printf( " COUNT %d:OK,I can write mail\n " , args -> tid);
92 mailbox = args; /* put ptr to our args there */
93 printf( " COUNT %d: raising flag\n " , args -> tid);
94 pthread_cond_signal( & flag); /* raise the flag */
95 printf( " COUNT %d: unlocking box\n " , args -> tid);
96 pthread_mutex_unlock( & lock ); /* release the mailbox */
97 return NULL;
98 }
//运行结果
allen:~/study/unix_system/CH14$ ./twordcount4 inc.cc inc.cc inc.cc
MAIN: waiting for flag to go up
COUNT 1: waiting to get lock
COUNT 1: have lock, storing data
COUNT 1:OK,I can write mail
COUNT 1: raising flag
COUNT 1: unlocking box
COUNT 2: waiting to get lock
COUNT 2: have lock, storing data
COUNT 2: oops..mailbox not empty. wait for signal
COUNT 3: waiting to get lock
COUNT 3: have lock, storing data
COUNT 3: oops..mailbox not empty. wait for signal
MAIN: Wow! flag was raised, I have the lock
105: inc.cc
Ok,I have read the mail
MAIN: waiting for flag to go up
COUNT 2:OK,I can write mail
COUNT 2: raising flag
COUNT 2: unlocking box
COUNT 3:OK,I can write mail
COUNT 3: raising flag
COUNT 3: unlocking box
MAIN: Wow! flag was raised, I have the lock
105: inc.cc
Ok,I have read the mail
MAIN: waiting for flag to go up
出现死锁,按照上面的时序,出现count2 count3都在wait signal,子线程count2 wait
signal,pthread_cond_wait(&flag, &lock)使得lock解锁了,这时count3也就进入了互斥
区,不应该出现这种情况。
然后主线程读完mail,singal,count2接到signal继续写mail,然后主线程wait signal,
注意这个时候count3和主线程都在wait signal,count2写完,signal
这时候唤醒的是count3…!count2的mail被丢了,而主进程还等着要读第
3封mail.
所以问题是主线程wait的signal 和 子线程wait 的signal不应该用同样的signal.
,使用semaphore 比较自然
一个子线程写,子线程通知主线程已写完,主线程读mailbox,主线程通知子线程已读完,另一子线程写....
写, 读, 写,读....
write = 0;
read = 0;
//server main thread
v(read) //to let one client sub thread can write at first since mail == null
for (i =0 ; i < sub threads num; i++)
p(write)
read mail
mail = null
v(read)
//clinent sub threads
p(read)
write mail
v(write)
1 /* twordcount4.c - threaded word counter for two files.
3 * functions to report results early
4 */
5
6 #include < stdio.h >
7 #include < pthread.h >
8 #include < ctype.h >
9 #include < semaphore.h >
10
11 struct arg_set { /* two values in one arg */
12 char * fname; /* file to examine */
13 int count; /* number of words */
14 int tid;
15 };
16
17 struct arg_set * mailbox = NULL;
18 static sem_t sem_write;
19 static sem_t sem_read;
20
21 main( int ac, char * av[])
22 {
23 pthread_t t1, t2, t3; /* two threads */
24 struct arg_set args1, args2, args3; /* two argsets */
25 void * count_words( void * );
26 int reports_in = 0 ;
27 int total_words = 0 ;
28
29 if ( ac != 4 ){
30 printf( " usage: %s file1 file2 file3\n " , av[ 0 ]);
31 exit( 1 );
32 }
33
34 // init semaphore,first o means semaphore only available in this process,second mean init value 0
36 sem_init( & sem_read, 0 , 0 ) == - 1 ) {
37 printf( " Failed to init semaphore!\n " );
38 exit( 1 );
39 }
40
41
42
43 args1.fname = av[ 1 ];
44 args1.count = 0 ;
45 args1.tid = 1 ;
46 pthread_create( & t1, NULL, count_words, ( void * ) & args1);
47
48 args2.fname = av[ 2 ];
49 args2.count = 0 ;
50 args2.tid = 2 ;
51 pthread_create( & t2, NULL, count_words, ( void * ) & args2);
52
53 args3.fname = av[ 3 ];
54 args3.count = 0 ;
55 args3.tid = 3 ;
56 pthread_create( & t3, NULL, count_words, ( void * ) & args3);
57
58
59 sem_post( & sem_read); // allow the first write
60 while ( reports_in < 3 ){
61 printf( " MAIN: waiting for sub thread write\n " );
62 sem_wait( & sem_write);
63 // sleep(10);
64 printf( " %7d: %s\n " , mailbox -> count, mailbox -> fname);
65 total_words += mailbox -> count;
66 if ( mailbox == & args1)
67 pthread_join(t1,NULL);
68 if ( mailbox == & args2)
69 pthread_join(t2,NULL);
70 if ( mailbox == & args3)
71 pthread_join(t3,NULL);
72 // sleep(10);
73 mailbox = NULL;
74 printf( " Ok,I have read the mail\n " );
75 sem_post( & sem_read) ;
76 reports_in ++ ;
77 }
78 printf( " %7d: total words\n " , total_words);
79 }
80 void * count_words( void * a)
81 {
82 struct arg_set * args = a; /* cast arg back to correct type */
83 FILE * fp;
84 int c, prevc = ' \0 ' ;
85
86 if ( (fp = fopen(args -> fname, " r " )) != NULL ){
87 while ( ( c = getc(fp)) != EOF ){
88 if ( ! isalnum(c) && isalnum(prevc) )
89 args -> count ++ ;
90 prevc = c;
91 }
92 fclose(fp);
93 } else
94 perror(args -> fname);
95 printf( " COUNT %d: waiting for main thread read the mail\n " , args -> tid);
96 sem_wait( & sem_read);
97 printf( " COUNT %d:OK,I can write mail\n " , args -> tid);
98 mailbox = args; /* put ptr to our args there */
99 printf( " COUNT %d: Finished writting\n " , args -> tid);
100 sem_post( & sem_write);
101 return NULL;
102 }
也可以直接初始sem_read = 1从而不需要一开始sem_post(&sem_read)
//test
allen:~/study/unix_system/CH14$ ./twordcount4_semaphore inc.cc incprint.c empty.c
COUNT 1: waiting for main thread read the mail
COUNT 2: waiting for main thread read the mail
COUNT 3: waiting for main thread read the mail
COUNT 1:OK,I can write mail
COUNT 1: Finished writting
MAIN: waiting for sub thread write
105: inc.cc
Ok,I have read the mail
COUNT 2:OK,I can write mail
COUNT 2: Finished writting
MAIN: waiting for sub thread write
76: incprint.c
Ok,I have read the mail
COUNT 3:OK,I can write mail
COUNT 3: Finished writting
MAIN: waiting for sub thread write
0: empty.c
Ok,I have read the mail
181: total words
allen:~/study/unix_system/CH14$ ./twordcount4_semaphore inc.cc inc.cc inc.cc
COUNT 1: waiting for main thread read the mail
COUNT 2: waiting for main thread read the mail
COUNT 3: waiting for main thread read the mail
COUNT 1:OK,I can write mail
COUNT 1: Finished writting
MAIN: waiting for sub thread write
105: inc.cc
Ok,I have read the mail
COUNT 2:OK,I can write mail
COUNT 2: Finished writting
MAIN: waiting for sub thread write
105: inc.cc
Ok,I have read the mail
MAIN: waiting for sub thread write
COUNT 3:OK,I can write mail
COUNT 3: Finished writting
105: inc.cc
Ok,I have read the mail
315: total words