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.
2
* - Version 4: condition variable allows counter
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.
2
* - Version 4: condition variable allows counter
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
35
if
(
sem_init(
&
sem_write,
0
,
0
)
==
-
1
||
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);
sem_destroy(&sem_read);
sem_destory(&sem_write);
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