多线程同步问题 条件变量和信号量
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