引言:题目具体描述记不大清了,大概是:Linux平台,利用线程调度的随机性和sleep的不准确性,生成一个各位均不相同的字符数组的伪随机序列。不得使用任何库函数。(这句记得清楚,当时在想线程库算不算,题目的意思应该是:不得使用库提供的随机函数)
1.算法
当时读完题很开心,这题可以用与“《编程珠玑》取样问题(ch12, p119)”类似的算法解决。算法如下——除了第一字符(下标0)以外,为其余N-1个字符各创建一个线程,每个线程先sleep一秒(也可以更长),再将对应位置的字符和第一个字符交换;N-1个线程完成后,主线程结束。原理暗藏在题目中,sleep一秒后,因为sleep的不准确性,这N-1个线程几乎同时醒来(就绪)(试想如若sleep非常精确,各个线程醒来的顺序就会和创建顺序相同);又由于线程调度的随机性,这时会被执行的线程是随机的,(不知先后顺序地)执行N-1次之前所述的交换所得的便是一个伪随机序列。不过当时想不起来pthread_create几个参数的顺序了(前面题的计算量不小,头都搞晕了),就随便按个顺序写了。回来后,按照当时的思路很快在电脑上写了出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#ifdef WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif
char
str
[]
=
"ABCDEFGH"
;
#define LEN sizeof(str) / sizeof(str[0]) - 1
pthread_t
tids
[
LEN
];
void
*
thread_func
(
void
*
idx
)
{
char
tmp
;
sleep
(
1
);
tmp
=
str
[(
int
)
idx
];
str
[(
int
)
idx
]
=
str
[
0
];
// str[0] is the critical resource !!
str
[
0
]
=
tmp
;
return
(
void
*
)
0
;
}
int
main
(
void
)
{
int
i
;
puts
(
str
);
for
(
i
=
1
;
i
<
LEN
;
i
++
)
{
pthread_create
(
&
tids
[
i
],
NULL
,
thread_func
,
(
void
*
)
i
);
}
sleep
(
2
);
puts
(
str
);
return
0
;
}
|
注意:main里用sleep(2)(比N-1个线程多sleep一秒)等待其他线程只是为了写起来简单,并不严谨!
2.互斥
不过一在电脑上写出来立即意识到一个问题——第一个元素是临界资源(所有“其他线程”都想抢着用这块地);如果不做互斥访问可能会出现——有的字符出现两次(获更多)有的字符没了,这种错误不是每次都会出现:
意识到这个错误之后,比较容易修改,只需将线程函数内对第一个元素的操作放入临界区中即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#ifdef WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif
char
str
[]
=
"ABCDEFGH"
;
#define LEN sizeof(str) / sizeof(str[0]) - 1
pthread_t
tids
[
LEN
];
pthread_mutex_t
mutex
;
void
*
thread_func
(
void
*
idx
)
{
char
tmp
;
sleep
(
1
);
tmp
=
str
[(
int
)
idx
];
pthread_mutex_lock
(
&
mutex
);
str
[(
int
)
idx
]
=
str
[
0
];
// str[0] is the critical resource !!
str
[
0
]
=
tmp
;
pthread_mutex_unlock
(
&
mutex
);
return
(
void
*
)
0
;
}
int
main
(
void
)
{
int
i
;
puts
(
str
);
pthread_mutex_init
(
&
mutex
,
NULL
);
for
(
i
=
1
;
i
<
LEN
;
i
++
)
{
pthread_create
(
&
tids
[
i
],
NULL
,
thread_func
,
(
void
*
)
i
);
}
sleep
(
2
);
puts
(
str
);
pthread_mutex_destroy
(
&
mutex
);
return
EXIT_SUCCESS
;
}
|
这次不会再有错误。for命令连续测试10次:
到此,说明这个算法没有问题。
3.同步
还应该main里的sleeep(2)改掉。main创建好N-1个线程后就应该被挂起,直到其他所有线程都“完工”后才应该被唤醒;肯定要用条件变量,main创建好其他线程后wait阻塞,其他所有线程都“完工”再被signal唤醒。为了保证这样的顺序,必须要让最后一个完工的线程知道自己是最后一个,也就是在最后一个其他线程“收工”的时候signal。只需要加个计数变量count用来标记还有多少线程没有完工,将其初始化为要创建的线程数N-1,每有一个线程“完工”就count--,判断count的值即可知道当前线程是倒数第几个完工的。(啰嗦一堆,还是上代码直观):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#ifdef WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif
char
str
[]
=
"ABCDEFGH"
;
#define LEN sizeof(str) / sizeof(str[0]) - 1
int
count
;
pthread_t
tids
[
LEN
];
pthread_mutex_t
mutex
;
pthread_mutex_t
mc
;
pthread_cond_t
cond
;
void
*
thread_func
(
void
*
idx
)
{
char
tmp
;
// in TLS(Thread local space.)
sleep
(
1
);
tmp
=
str
[(
int
)
idx
];
pthread_mutex_lock
(
&
mutex
);
str
[(
int
)
idx
]
=
str
[
0
];
// str[0] is the critical resource.
str
[
0
]
=
tmp
;
--
count
;
printf
(
"%d %d
\n
"
,
idx
,
count
);
if
(
count
==
0
)
{
puts
(
"SIGNAL"
);
pthread_cond_signal
(
&
cond
);
}
pthread_mutex_unlock
(
&
mutex
);
return
(
void
*
)
0
;
}
int
main
(
void
)
{
int
i
;
puts
(
str
);
count
=
LEN
-
1
;
pthread_mutex_init
(
&
mutex
,
0
);
pthread_mutex_init
(
&
mc
,
0
);
pthread_cond_init
(
&
cond
,
0
);
for
(
i
=
1
;
i
<
LEN
;
i
++
)
{
pthread_create
(
&
tids
[
i
],
0
,
thread_func
,
(
void
*
)
i
);
}
pthread_mutex_lock
(
&
mc
);
puts
(
"WAIT"
);
pthread_cond_wait
(
&
cond
,
&
mc
);
pthread_mutex_unlock
(
&
mc
);
puts
(
str
);
pthread_cond_destroy
(
&
cond
);
pthread_mutex_destroy
(
&
mc
);
pthread_mutex_destroy
(
&
mutex
);
return
EXIT_SUCCESS
;
}
|
需要稍加留意的是:count也是临界资源,需要放到临界区里(和str[0]一样);另外pthread_cond_wait接口所需的pthread_mutex_t * 不能用维护“其他线程”临界的那个mutex,应该再定义一个mutex。因为pthread_cond_wait会原子性地阻塞当前线程同时unlock传入的mutex,pthread_cond_wait返回时,传入的mutex再次被锁。(APUEcn2e p309)
注:本机环境gcc 4.6.1(MinGW)