多线程
来源:互联网 发布:java ftp上传附件 编辑:程序博客网 时间:2024/06/01 21:14
1.创建缺省线程 2
2.终止线程 2
3. 等待线程终止 2
pthread_exit和pthread_join进一步说明: 3
4.分离线程 7
5.获取线程标识符 8
6.比较线程ID 8
7. 一次性初始化 8
8. 设置线程的调度策略和优先级 9
9. 获取线程的优先级 11
10.取消线程 12
取消线程,是否会释放线程的所有资源?例子: 14
设置取消类型 16
11.初始化属性 17
12.设置分离状态 18
13.设置范围 18
14. 设置继承的调度策略 18
16. 设置调度参数 19
17.初始化互斥锁 21
18.销毁互斥锁 21
19.锁定互斥锁 22
20.解除锁定互斥锁 23
21. 互斥锁的类型: 23
22. 初始化互斥锁属性对象 23
23. 销毁互斥锁属性对象 23
24.设置互斥锁类型的属性 24
互斥锁动态初始化和静态初始化区别: 26
销毁互斥锁:事实上没做任何销毁操作,如下: 27
非递归类型的互斥锁解锁和加锁操作: 27
29.初始化条件变量 27
30.基于条件变量阻塞 27
31.解除阻塞一个线程 28
31.解除阻塞所有线程 29
33. 在指定的时间之前阻塞 30
32.唤醒丢失问题 31
33. 计数信号量概述 31
34. 初始化信号 31
35. 增加信号 31
36. 基于信号计数进行阻塞 32
37.多线程链表添加删除例子(使用条件变量实现互斥): 32
38.为线程特定数据创建键 34
39. 删除线程特定数据键 35
40.设置线程特定数据 35
41. 获取线程特定数据 35
读写锁属性初始化: 36
销毁读写锁属性对象: 36
设置读写锁共享属性: 37
初始化读写锁: 37
销毁读写锁: 37
读写锁总结: 39
获得写锁: 39
获得读锁: 40
注:线程的使用需要包含: #include <pthread.h>
1.创建缺省线程
int pthread_create(pthread_t *tid, pthread_attr_t *tattr,
void*(*start_routine)(void *), void *arg);
参数:
当pthread_create() 成功时,所创建线程的ID 被存储在由tid 指向的位置中。第二个参数用于设置线程属性,如果不需要特殊的属性,可以简单的设置该参数为NULL,最后两个参数告诉线程将要启动执行的函数和传递给该函数的参数。
返回值:
调用成功完成后返回0,其他的值都表示出现错误。
如果检测到以下任一情况,pthread_create() 将失败并返回相应的值。
EAGAIN 超出了系统限制,如创建的线程太多。
EPERM 调用者没有适当的权限设置所需的参数或安排调度策略
EINVAL 描述: tattr 的值无效。(设置的属性有问题)
默认属性:绑定,非分离,继承创建者线程中定义的调度策略。
2.终止线程
void pthread_exit(void *status);
本函数可用来终止调用线程。将释放调用线程所有特定数据绑定。如果调用线程尚未分离,则线程ID 和status 指定的退出状态将保持不变,直到应用程序调用pthread_join() 以等待该线程。否则,将忽略status。线程ID 可以立即回收。
有一个重要的特殊情况,即当初始线程(即调用main() 的线程)从main() 调用返回时或调用exit() 时,整个进程及其所有的线程将终止。因此,一定要确保初始线程不会从main()过早地返回,在其它线程调用exit()也会终止整个进程。
请注意,如果主线程仅仅调用了pthread_exit,则仅主线程本身终止。进程及进程内的其他线程将继续存在。所有线程都已终止时,进程也将终止。
3. 等待线程终止
int pthread_join(thread_t tid, void **status);
参数:
参数tid指定要等待的线程ID,指定的线程必须位于当前的进程中,而且不得是分离线程。
当参数status 不是NULL 时,status 指向某个位置,在pthread_join() 成功返回时,将该位置设置为已终止线程的退出状态。
返回值:
调用成功完成后,pthread_join() 将返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_join() 将失败并返回相应的值。
ESRCH 描述: 没有找到与给定的线程ID 相对应的线程。
EDEADLK 描述: 将出现死锁,如一个线程等待其本身,或者线程A和线程B 互相等待。
EINVAL 描述: 与给定的线程ID 相对应的线程是分离线程。
说明:
如果多个线程等待同一个线程终止,则所有等待线程将一直等到目标线程终止。然后,一个等待线程成功返回。其余的等待线程将失败并返回ESRCH 错误。
pthread_join() 仅适用于非分离的目标线程。如果没有必要等待特定线程终止之后才进行其他处理,则应当将该线程分离。
pthread_exit和pthread_join进一步说明:(一下主线程是main线程)
1.线程自己运行结束,或者调用pthread_exit()结束,线程都会释放自己独有的空间资源。
2.如果线程是非分离的,线程会保留线程id号,直到其他线程通过"joining"这个线程确认其已死亡。join 的结果是joining 线程得到已终止线程的退出状态,已终止的线程将消失。
3.如果线程是分离的,不需要使用pthread_exit(),线程自己运行结束,就会释放所有资源(包括线程id号)。
4.子线程最终一定要使用pthread_join()或者设置为分离状态来结束线程,否则线程的资源不会被完全释放。(使用取消线程功能也不能完全释放),线程id依然存在。
5.主线程运行pthread_exit(),会结束主线程,但不会结束子线程。
6.主线程结束,则整个程序结束,所以主线程最好要使用join等待子线程运行结束。使用join一个线程可以等待多个线程结束。
7.使用join的线程将会阻塞,直到被join的线程结束,join函数返回,但是它对被join的线程运行没有影响。
8.如果子线程使用exit()则可以结束整个进程
9.由于pthread_create会立即出发子线程的执行,那么对于如下代码的解析来看:在main函数内:
pthread_create(&tid, NULL, start_routine, (void *)c);
printf("pthread id :%u\n", tid);
printf("pthread id :%u\n", tid);
printf("pthread id :%u\n", tid);
printf("pthread id :%u\n", tid);
sleep(10);
pthread_join(tid, (void **)&d);
printf("haha \n");
那么在执行pthread_join前,其实start_routine的从线程就已经结束了。Pthread_join只是标识,在pthread_join后的代码需要等到从线程tid执行完毕后才执行,如果tid未执行,则一直处于等待,如果在join前已经结束,则直接执行join后的代码,join本身只负责阻塞,不负责再次运行tid从线程。。这里特别注意,我原来以为从线程tid还重新执行一次呢!!!真是搞笑。。。哈哈
例子:
1 #include <stdlib.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <string.h>
5 void * start_routine(void *arg)
6 {
7 char * a;
8 printf("thread runing,%s\n", (char *)arg);
9 a = malloc(sizeof(char)*6);
10 memset(a, 'a', 5);
11 a[5] = '\0';
12 pthread_exit((void *)a);
13 }
14 int main(int argc, char *argv[])
15 {
16 pthread_t tid;
17 char c[4], *d;
18 memset(c, 'c', 3);
19 c[3] = '\0';
20 pthread_create(&tid, NULL, start_routine, (void *)c);
21 printf("pthread id :%u\n", tid);
22 pthread_join(tid, (void **)&d);
23 printf("main: %s\n", d);
24 free(d);
25 return 0;
26 }
运行结果:
pthread id :3077950352
thread runing,ccc
main: aaaaa
非分离线程未使用join函数例子:
1 #include <stdlib.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <sched.h>
5 #include <errno.h>
6 void *consumer(void *p)
7 {
8 static a = 0;
9 a++;
10 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), a);
11 pthread_exit(NULL);
12 }
13 int main(int argc, char *argv[])
14 {
15 pthread_t t1, t2, t3;
16 int ret;
17 do{
18 ret = pthread_create(&t1, NULL, consumer, NULL);
19 if(ret != 0)
20 {
21 printf("create failed,%d\n", ret);
22 exit(1);
23 }
24 }while(1);
25 sleep(1);
26 return 0;
27 }
运行结果:
创建350个左右的线程后,就不能再创建线程,已创建的线程线程号都不同。因为从线程只能通过主线程进行回收,在从线程中使用pthread_exit(NULL);并未完全回收从线程资源,线程编号依然存在。
<<<<<<<<<<<<<<<<<<<<<(33799056),362
<<<<<<<<<<<<<<<<<<<<<(25406352),363
<<<<<<<<<<<<<<<<<<<<<(17013648),364
<<<<<<<<<<<<<<<<<<<<<(8620944),365
create failed,12
如果主函数加上pthread_join(t1, NULL);才可以一直创建线程,如下,
13 int main(int argc, char *argv[])
14 {
15 pthread_t t1, t2, t3;
16 int ret;
17 do{
18 ret = pthread_create(&t1, NULL, consumer, NULL);
19 if(ret != 0)
20 {
21 printf("create failed,%d\n", ret);
22 exit(1);
23 }
24 pthread_join(t1, NULL);
25 }while(1);
26 sleep(1);
27 return 0;
28 }
部分结果如下:
<<<<<<<<<<<<<<<<<<<<<(3076656016),15002
<<<<<<<<<<<<<<<<<<<<<(3076656016),15003
<<<<<<<<<<<<<<<<<<<<<(3076656016),15004
<<<<<<<<<<<<<<<<<<<<<(3076656016),15005
<<<<<<<<<<<<<<<<<<<<<(3076656016),15006
<<<<<<<<<<<<<<<<<<<<<(3076656016),15007
可见线程id号被回收,并被用于创建新的线程。
没有pthread_exit(),只有pthread_join()线程资源也能释放:
6 void *consumer(void *p)
7 {
8 static a = 0;
9 a++;
10 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), a);
11 }
12 int main(int argc, char *argv[])
13 {
14 pthread_t t1, t2, t3;
15 int ret;
16 do{
17 ret = pthread_create(&t1, NULL, consumer, NULL);
18 if(ret != 0)
19 {
20 printf("create failed,%d\n", ret);
21 exit(1);
22 }
23 pthread_join(t1, NULL);
24 }while(1);
25 sleep(1);
26 return 0;
27 }
输出结果如下:
<<<<<<<<<<<<<<<<<<<<<(3076357008),19997
<<<<<<<<<<<<<<<<<<<<<(3076357008),19998
<<<<<<<<<<<<<<<<<<<<<(3076357008),19999
只使用pthread_detach()分离子线程例子:
6 void *consumer(void *p)
7 {
8 static a = 0;
9 a++;
10 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), a);
11 pthread_detach(pthread_self());
12 return NULL;
13 }
14 int main(int argc, char *argv[])
15 {
16 pthread_t t1, t2, t3;
17 int ret;
18 do{
19 ret = pthread_create(&t1, NULL, consumer, NULL);
20 if(ret != 0)
21 {
22 printf("create failed,%d\n", ret);
23 exit(1);
24 }
25 }while(1);
26
27 return 0;
28 }
运行结果:只能创建一千个左右的线程,如下
<<<<<<<<<<<<<<<<<<<<<(3128908688),1184
<<<<<<<<<<<<<<<<<<<<<(3137301392),1185
<<<<<<<<<<<<<<<<<<<<<(3145694096),1186
create failed,12
分析原因可能是主线程创建线程速度太快,子线程还来不及释放资源,最终导致资源不足,下面在主函数里面加一个printf(),就不会出问题了。
14 int main(int argc, char *argv[])
15 {
16 pthread_t t1, t2, t3;
17 int ret;
18 do{
19 ret = pthread_create(&t1, NULL, consumer, NULL);
20 if(ret != 0)
21 {
22 printf("create failed,%d\n", ret);
23 exit(1);
24 }
25 printf("<<<<<<<<<<<<<<<");
26 }while(1);
27
28 return 0;
29 } 部分结果如下:
<<<<<<<<<<<<<<<<<<<<<(2539965328),15210
<<<<<<<<<<<<<<<<<<<<<(3060312976),15211
<<<<<<<<<<<<<<<<<<<<<(3051920272),15212
<<<<<<<<<<<<<<<<<<<<<(3009956752),15213
总结如下:
1.线程自己运行结束,或者调用pthread_exit()结束,线程都会释放自己独立的空间资源。
2.如果线程是非分离的,线程会保留线程id号,直到其他线程通过"joining"这个线程确认其已死亡。join 的结果是joining 线程得到已终止线程的退出状态,已终止的线程将消失。
4.分离线程
int pthread_detach(thread_t tid);
函数用于指示应用程序在线程tid 终止时回收其存储空间。如果tid 尚未终
止,pthread_detach() 不会终止该线程。
返回值:
pthread_detach() 在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_detach() 将失败并返回相应的值。
EINVAL 描述: tid 是分离线程。
ESRCH 描述: tid 不是当前进程中有效的未分离的线程。
注意:为了避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于detached 状态,否则就需要调用 pthread_join() 函数来对其进行资源回收。(如果不进行以上操作,线程id号不会被回收)。但是如果主线程结束,整个程序就结束了,所以最好在主线程使用,join等待其它线程结束。
5.获取线程标识符
pthread_t pthread_self(void);
返回调用线程的标识符,(是一个无符号整形数)
6.比较线程ID
int pthread_equal(pthread_t tid1, pthread_t tid2);
如果tid1 和tid2 相等,pthread_equal() 将返回非零值,否则将返回零。如果tid1 或tid2 是无效的线程标识号,则结果无法预测。
7. 一次性初始化,这个函数粗暴的完成了线程的同步,哈哈
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))
本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
例子:
1 #include <stdlib.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <string.h>
5 pthread_once_t once = PTHREAD_ONCE_INIT;
6
7 void once_run(void)
8 {
9 printf("once_run in thread %u\n ",(unsigned int )pthread_self());
10 }
11
12 void * child1(void * arg)
13 {
14 pthread_t tid =pthread_self();
15 printf("thread: %u enter\n", tid);
16 pthread_once(&once,once_run);
17 printf("thread %u return\n", tid);
18 }
19
20
21 void * child2(void * arg)
22 {
23 pthread_t tid =pthread_self();
24 printf("thread: %u enter\n", tid);
25 pthread_once(&once,once_run);
26 printf("thread %u return\n", tid);
27 }
28
29 int main(void)
30 {
31 pthread_t tid1,tid2;
32 printf("hello\n");
33 pthread_create(&tid1,NULL,child1,NULL);
34 pthread_create(&tid2,NULL,child2,NULL);
35 sleep(5);
36 printf("main thread exit\n");
37 return 0;
38 }
运行结果:
hello
thread: 3067587472 enter
once_run in thread 3067587472 —这里可以看到,once_run(void)这个函数只运行了一次
thread 3067587472 return
thread: 3075980176 enter
thread 3075980176 return
main thread exit
8. 设置线程的调度策略和优先级——关于线程的优先级暂时就不用看了
int pthread_setschedparam(pthread_t tid, int policy,
const struct sched_param *param);
用于设置现有线程的调用策略和优先级。
参数:
tid需要设置的线程id号,
policy 调度策略,
param 优先级。
线程的调度有三种策略:SCHED_OTHER、SCHED_RR和SCHED_FIFO
SCHED_OTHER
它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。
SCHED_FIFO
它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部((POSIX 1003.1)。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。
SCHED_RR
鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。
例子:
1 #include <stdlib.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <sched.h>
5
6 void *consumer(void *p)
7 {
8 int i;
9 printf("start (%d)\n", (int)p);
10 for (i = 0; 1; i++)
11 {
12 if(i%200 == 10)
13 printf("<<<<<<<<<<<<<<<<<<<<<(%d)\n", (int)p);
14
15 }
16 }
17 int main(int argc, char *argv[])
18 {
19 pthread_t t1, t2, t3;
20 struct sched_param sched3;
21 sched3.__sched_priority = 70;
22
23 pthread_create(&t1, NULL, consumer, (void *)4);
24 pthread_create(&t2, NULL, consumer, (void *)5);
25 pthread_create(&t3, NULL, (consumer), (void *)6);
26
27 sleep(8);
28 pthread_setschedparam(t3, SCHED_FIFO, &sched3);
29
30 pthread_join(t1, NULL);
31 pthread_join(t2, NULL);
32 pthread_join(t3, NULL);
33 return 0;
34 }
35
运行结果:
前8秒交替打印 <<<<<<<<<<<<<<<<<<<<<(4) 和<<<<<<<<<<<<<<<<<<<<<(5)
和 <<<<<<<<<<<<<<<<<<<<<(6)
8秒以后只打印 <<<<<<<<<<<<<<<<<<<<<(6)
注意:如果t3线程用sleep()阻塞自己,其它线程将会被调度执行。
上面的程序在8秒后主线程(执行main)也不会执行,比如在29行增加一个exit(1),如果主线程会执行到29行,整个程序将结束,但实际上还会一直执行t3线程.主线程不会被执行。
9. 获取线程的优先级
int pthread_getschedparam(pthread_t thread, int *restrict policy,
struct sched_param *restrict param);
函数在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。
ESRCH 描述: tid 指定的值不引用现有的线程。
例子:
1 #include <stdlib.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <sched.h>
5
6 void *consumer(void *p)
7 {
8 int i;
9 printf("start (%d)\n", (int)p);
10 for (i = 0; 1; i++)
11 {
12 sleep(1);
13 printf("<<<<<<<<<<<<<<<<<<<<<wake(%d)\n", (int)p);
14
15 }
16 }
17 int main(int argc, char *argv[])
18 {
19 pthread_t t1, t2, t3;
20 struct sched_param sched3, sched4;
21 sched3.__sched_priority = 70;
22 int policy;
23
24 pthread_create(&t1, NULL, consumer, (void *)4);
25 pthread_create(&t2, NULL, consumer, (void *)5);
26 pthread_create(&t3, NULL, (consumer), (void *)6);
27
28 sleep(4);
29 pthread_setschedparam(t3, SCHED_FIFO, &sched3);
30 printf("main run\n");
31 pthread_getschedparam(t3, &policy, &sched4);
32 printf("policy: %d, priority: %d\n", policy, sched4.__sched_priority);
33 exit(1);
34
35 pthread_join(t1, NULL);
36 pthread_join(t2, NULL);
37 pthread_join(t3, NULL);
38 return 0;
39 }
运行结果:
start (6)
start (5)
start (4)
<<<<<<<<<<<<<<<<<<<<<wake(6)
<<<<<<<<<<<<<<<<<<<<<wake(5)
<<<<<<<<<<<<<<<<<<<<<wake(4)
<<<<<<<<<<<<<<<<<<<<<wake(6)
<<<<<<<<<<<<<<<<<<<<<wake(5)
<<<<<<<<<<<<<<<<<<<<<wake(4)
<<<<<<<<<<<<<<<<<<<<<wake(6)
<<<<<<<<<<<<<<<<<<<<<wake(5)
<<<<<<<<<<<<<<<<<<<<<wake(4)
main run
policy: 1, priority: 70
10.取消线程
int pthread_cancel(pthread_t thread);
设置取消点
void pthread_testcancel(void);
测试是否接收到取消请求,如果有,结束线程。
例子:
9 int a = 0;
10 void *thread1(void *arg)
11 {
12 pthread_testcancel();
13 a = 10;
14 }
15 int main(int argc, char *argv[])
16 {
17 pthread_t t1, t2, t3;
18 int ret, i;
19 printf("main start\n");
20 ret = pthread_create(&t1, NULL, thread1, NULL);
21 pthread_cancel(t1);
22 pthread_join(t1, NULL);
23 sleep(3);
24 printf("main end, a=%d\n", a);
25 return 0;
26 }
运行结果:在取消点处程序结束,a值未该。
main start
main end, a=0
如果改为:
9 int a = 0;
10 void *thread1(void *arg)
11 {
12 a = 10;
13 pthread_testcancel();
14 }
运行结果:a值被修改了
main start
main end, a=10
例子:
9 void *thread1(void *arg)
10 {
11 printf("start thread (%u)\n", (unsigned)pthread_self());
12 printf("thread (%u) end\n", (unsigned)pthread_self());
13 }
14 int main(int argc, char *argv[])
15 {
16 pthread_t t1, t2, t3;
17 int ret;
18 printf("main start\n");
19 ret = pthread_create(&t1, NULL, thread1, NULL);
20 if(ret != 0)
21 {
22 printf("create thread failed\n");
23 exit(1);
24 }
25 pthread_cancel(t1);
26 sleep(5);
27 printf("main end\n");
28 return 0;
29 }
运行结果:
main start
start thread (3076238224)
main end
注意:子线程并没设置取消点,但是却被取消了,原因是printf函数包含了一个个取消点(应该在函数结尾),在取消点检测到取消请求时结束线程,第二个printf不会运行,如果在printf前加一个取消点,线程在printf运行前被取消了,不会有结果输出。如下:
9 void *thread1(void *arg)
10 {
11 pthread_testcancel();
12 printf("start thread (%u)\n", (unsigned)pthread_self());
13 printf("thread (%u) end\n", (unsigned)pthread_self());
14 }
运行结果:
main start
main end
取消线程,是否会释放线程的所有资源?例子:
9 void *thread1(void *arg)
10 {
11 printf("start thread (%u)\n", (unsigned)pthread_self());
12 }
13 int main(int argc, char *argv[])
14 {
15 pthread_t t1, t2, t3;
16 int ret;
17 printf("main start\n");
18 do{
19 ret = pthread_create(&t1, NULL, thread1, NULL);
20 if(ret != 0)
21 {
22 printf("create thread failed\n");
23 exit(1);
24 }
25 pthread_cancel(t1);
26 if(ret != 0)
27 {
28 printf("join failed\n");
29 exit(1);
30 }
31 }while(1);
32 return 0;
33 }
运行结果:
start thread (349191056), 327
start th<<<<<<<<<<<<<<<<<<<<<<<create thread failed
注意:每次运行的结果都不一样,在主线程里面加了printf限制产生线程的速度,但是能生成的线程数都在350个左右,应该可以判断,取消并没完全释放资源。所以取消线程后,还应该用join来完全释放资源:如下:
14 int main(int argc, char *argv[])
15 {
16 pthread_t t1, t2, t3;
17 int ret, i;
18 printf("main start\n");
19 do{
20 ret = pthread_create(&t1, NULL, thread1, NULL);
21 if(ret != 0)
22 {
23 printf("create thread failed\n");
24 exit(1);
25 }
26 pthread_cancel(t1);
27 printf("<<<<<<<<<<<<<<<<<<<<<");
28 pthread_join(t1, NULL);
29 if(ret != 0)
30 {
31 printf("join failed\n");
32 exit(1);
33 }
34 }while(1);
35 return 0;
36 }
运行结果:将不断创建新线程.
注意:取消线程相当于使用pthread_exit终止线程。
启用或禁用取消功能:
int pthread_setcancelstate(int state, int *oldstate);
oldstate放置旧的取消状态
pthread_setcancelstate() 在成功完成之后返回零。
例子:
int oldstate;
int ret;
/* enabled */
ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
/* disabled */
ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
设置取消类型
int pthread_setcanceltype(int type, int *oldtype);
type可取的值为PTHREAD_CANCEL_DEFERRED 延迟(默认),就是在取消点结束。
PTHREAD_CANCEL_ASYNCHRONOUS ,可以在执行过程中的任意一点取消线程。
例子:
9 int a = 0;
10
11 void *thread1(void *arg)
12 {
13 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
14 a = 10;
15 printf("start thread (%u)\n", (unsigned)pthread_self());
16 }
17 int main(int argc, char *argv[])
18 {
19 pthread_t t1, t2, t3;
20 int ret, i;
21 printf("main start\n");
22 ret = pthread_create(&t1, NULL, thread1, NULL);
23 if(ret != 0)
24 {
25 printf("create thread failed\n");
26 exit(1);
27 }
28 pthread_cancel(t1);
29 pthread_join(t1, NULL);
30 sleep(3);
31 printf("main end, a=%d\n", a);
32 return 0;
33 }
运行结果: 设置为异步模式,在没到达取消点之前就结束了,a没被改动。如果把13行注释,a会被改动。
main start
main end, a=0
注意:使用取消功能要很注意,很多C库函数(有阻塞性质的)也隐含有取消点,比如sleep(),pthread_cond_wait(),而且,取消线程并不能完全释放线程资源,所以编程的时候尽量不要使用取消功能。
但是,下面这个情况用 异步取消+join 可以达到终止无限循环线程的目的:
9 int a = 0;
10 void *thread1(void *arg)
11 {
12 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
13 do{
14 a++;
15 }while(1);
16 }
17 int main(int argc, char *argv[])
18 {
19 pthread_t t1, t2, t3;
20 int ret, i;
21 printf("main start\n");
22 ret = pthread_create(&t1, NULL, thread1, NULL);
23
24 sleep(1);
25 pthread_cancel(t1);
26 pthread_join(t1, NULL);
27 printf("main end, a=%d\n", a);
28 return 0;
29 }
运行结果:
main start
main end, a=294314990
11.初始化属性
int pthread_attr_init(pthread_attr_t *tattr);
函数将对象属性初始化为其缺省值。可能会分配一些存储空间,所以需要下面的函数删除初始化期间分配的存储空间。
int pthread_attr_destroy(pthread_attr_t *tattr);
以上两个函数成功都返回 0.
6 void *consumer(void *p)
7 {
8 static int a = 0;
9 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), ++a);
10 return NULL;
11 }
12 int main(int argc, char *argv[])
13 {
14 pthread_t t1, t2, t3;
15 int ret;
16 pthread_attr_t attr;
17 pthread_attr_init(&attr);
18 ret = pthread_create(&t1, &attr, consumer, NULL);
19 if(ret != 0)
20 {
21 printf("create failed,%d\n", ret);
22 exit(1);
23 }
24 pthread_attr_destroy(&attr);
25 sleep(1);
26 return 0;
27 }
输出结果:<<<<<<<<<<<<<<<<<<<<<(3077286800),1
注意:属性对象必须初始化,否则属性不能生效,创建线程时将返回错误。
属性对象被销毁,并不影响线程的属性。
12.设置分离状态
int pthread_attr_setdetachstate(pthread_attr_t *tattr,int detachstate);
第二个参数有两种取值, PTHREAD_CREATE_DETACHED 分离
PTHREAD_CREATE_JOINABLE 非分离
区别参考上面的 《3. 等待线程终止》
设置的效果和使用函数pthread_detach()是一样的。
13.设置范围
有两种范围: PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS,但是没看出来设置后的运行效果有什么区别,还有就是linux里面也只有PTHREAD_SCOPE_SYSTEM范围的,不支持PTHREAD_SCOPE_PROCESS,如果设置范围PTHREAD_SCOPE_PROCESS将返回错误。所以编程的时候可以不关心这个设置。
14. 设置继承的调度策略
int pthread_attr_setinheritsched(pthread_attr_t *tattr, int inherit);
inherit 值PTHREAD_INHERIT_SCHED 表示新建的线程将继承创建者线程中定义的调度策略。将忽略在pthread_create() 调用中定义的所有调度属性。如果使用PTHREAD_EXPLICIT_SCHED,则将使用pthread_create() 调用中的属性。
返回值:
成功完成后将返回零。其他任何返回值都表示出现了错误。
如果出现以下任一情况,该函数将失败并返回对应的值。
EINVAL 描述: 尝试将tattr 设置为无效的值。
ENOTSUP 描述: 尝试将该属性设置为不受支持的值。
15.设置调度策略
int pthread_attr_setschedpolicy(pthread_attr_t *tattr, int policy);
policy可取SCHED_FIFO(实行先入先出)、SCHED_RR(实时循环)或SCHED_OTHER
它们的区别参看 《8. 设置线程的调度策略和优先级》
它们设置的效果是一样的,不同点在于:这里在线程创建前设置,前者在线程创建后设置。默认调度策略是SCHED_OTHER。
返回值:
成功完成后将返回零。其他任何返回值都表示出现了错误。
如果出现以下任一情况,该函数将失败并返回对应的值。
EINVAL 描述: 尝试将tattr 设置为无效的值。
ENOTSUP 描述: 尝试将该属性设置为不受支持的值。
注意:必须同时设置继承的调度策略为PTHREAD_EXPLICIT_SCHED,这里的设置才能生效。
16. 设置调度参数
int pthread_attr_setschedparam(pthread_attr_t *tattr,
const struct sched_param *param);
调度参数是在param 结构中定义的。仅支持优先级参数。新创建的线程使用此优先级运行。
注意:SCHED_FIFO和SCHED_RR,对应的优先级范围是:1到99,99的优先级最高,
如果优先级错误,线程无法创建。要包含头文件sched.h。
例子:主线程和第一个创建的子线程都是普通线程,第二个创建的子线程是实时线程。
4 #include <sched.h>
6 void *consumer(void *p)
7 {
8 do{
9 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), (unsigned int)p);
10 }while(1);
11 }
12 int main(int argc, char *argv[])
13 {
14 pthread_t t1, t2, t3;
15 int ret;
16 struct sched_param param;
17 param.sched_priority = 1;
18 pthread_attr_t attr;
19 pthread_attr_init(&attr);
20
21 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
22 pthread_attr_setschedpolicy(&attr, SCHED_RR);
23 pthread_attr_setschedparam(&attr, ¶m);
24
25 ret = pthread_create(&t1, NULL, consumer,(void *)1);
26 sleep(2);
27 ret = pthread_create(&t2, &attr, consumer,(void *)2);
28 if(ret != 0)
29 {
30 printf("create failed,%d\n", ret);
31 exit(1);
32 }
33 pthread_attr_destroy(&attr);
34 sleep(1);
35 printf("main returned\n");
36 return 0;
37 }
运行结果:
前两秒主线程和第一个子线程交替运行,两秒后一直运行第二个子线程.
例子:
6 void *consumer1(void *p)
7 {
8 do{
9 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), 1);
10 }while(1);
11 }
12 void *consumer2(void *p)
13 {
14 do{
15 printf("<<<<<<<<<<<<<<<<<<<<<(%u),%d\n", (unsigned)pthread_self(), 2);
16 struct sched_param sched;
17 sched.__sched_priority = 99;
18 pthread_setschedparam(*(pthread_t *)p, SCHED_FIFO, &sched);
19 }while(1);
20 }
21 int main(int argc, char *argv[])
22 {
23 pthread_t t1, t2, t3;
24 int ret;
25 struct sched_param param;
26 param.sched_priority = 1;
27 pthread_attr_t attr;
28 pthread_attr_init(&attr);
29
30 pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
31 pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
32 pthread_attr_setschedparam(&attr, ¶m);
33
34 ret = pthread_create(&t1, NULL, consumer1, NULL);
35 sleep(2);
36 ret = pthread_create(&t2, &attr, consumer2,(void *)&t1);
37 if(ret != 0)
38 {
39 printf("create failed,%d\n", ret);
40 exit(1);
41 }
42 pthread_attr_destroy(&attr);
43 sleep(1);
44 printf("main returned\n");
45 return 0;
46 }
运行结果:
1.最终一直运行 void *consumer1(void *p),
2.如果把18行注释掉,最终一直运行void *consumer2(void *p),
3.在18行第一个子线程的被改为实时并优先级大于第二个线程,所以它抢占cpu并一直运行。
4.如果第一个线程优先级设置小于第二个线程,还是一直运行void *consumer2(void *p)。
5.如果17, 18行改为
sched.__sched_priority = 0;
pthread_setschedparam(pthread_self(), SCHED_OTHER, &sched);
第二个线程将会变为普通线程
17.初始化互斥锁
切记:互斥锁锁定的是代码块(N行代码区域),而非仅仅锁定一个mutex_t变量或者一个全局变量。举个例子:
91 void *consumer1(void *p)
92 {
93 if(!pthread_mutex_lock(&mu))
94 printf("get mutex(1)\n");
95 else
96 return NULL;
97 sleep(10);
98 pthread_mutex_unlock(&mu);
99 }
100
101 void * consumer2(void *p)
102 {
103 if(!pthread_mutex_lock(&mu))
104 printf("get mutex(2)\n");
105 else
106 return NULL;
107 pthread_mutex_unlock(&mu);
108 }
Int Main(int argc,char * argv[])
{
233 rett = pthread_create(&t1, NULL, consumer1, NULL);
234 rett = pthread_create(&t2, NULL, consumer2,NULL);
235 if(rett != 0)
236 {
237 printf("create failed,%d\n", rett);
238 exit(1);
239 }
240 sleep(2);
241 printf("main returned\n");
242 return 0;
}
运行结果:从结果大家可以看到get mutex(2)根本没有被打印,说明在执行线程A的时候,线程B根本没有被执行。
get mutex (mian)
get mutex (main)
get mutex(1)
main returned
1.动态初始化:
int pthread_mutex_init(pthread_mutex_t *mp,
const pthread_mutexattr_t *mattr);
第一个参数指向互斥锁,第二个参数指向互斥锁的属性对象,将mattr 设置为NULL 的效果与传递缺省互斥锁属性对象的地址相同,但是没有内存开销。
2.静态初始化:
使用PTHREAD_MUTEX_INITIALIZER 宏可以将以静态方式定义的互斥锁初始化为其缺省属性。
返回值
在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。
EBUSY 描述: 该实现已检测到系统尝试重新初始化mp 所引用的对象,即以前进行过初始化但尚未销毁的互斥锁。
EINVAL 描述: mattr 属性值无效。互斥锁尚未修改。
EFAULT 描述: mp 所指向的互斥锁的地址无效。
注意:如果针对以前初始化的互斥锁调用 pthread_mutex_init(),则该互斥锁还会被重新初始化。
18.销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mp);
此函数没意义,执行之后对锁没有影响,可以不用。
例子:
Pthread_mutex_t mu;
7 void *consumer1(void *p)
8 {
9 pthread_mutex_init(&mu, NULL);
10 pthread_mutex_lock(&mu);
11 printf("get mutex(1)\n");
12 pthread_mutex_unlock(&mu);
13 }
14 void *consumer2(void *p)
15 {
16 pthread_mutex_init(&mu, NULL);
17 pthread_mutex_lock(&mu);
18 printf("get mutex(2)\n");
19 pthread_mutex_unlock(&mu);
20 }
21 int main(int argc, char *argv[])
22 {
23 pthread_t t1, t2, t3;
24 int ret;
25
26 pthread_mutex_init(&mu, NULL);
27 pthread_mutex_lock(&mu);
28 ret = pthread_create(&t1, NULL, consumer1, NULL);
29 ret = pthread_create(&t2, NULL, consumer2,NULL);
30 if(ret != 0)
31 {
32 printf("create failed,%d\n", ret);
33 exit(1);
34 }
35 sleep(11);
36 printf("main returned\n");
37 return 0;
38 }
如果互斥锁在使用前都被初始化,则先前线程锁执行的pthread_mutex_lock就已经失去效果了。
所以运行结果:
get mutex(2)
get mutex(1)
main returned
19.锁定互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
当pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。
返回值:
在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。
EAGAIN 描述: 由于已超出了互斥锁递归锁定的最大次数,因此无法获取该互斥锁。
EDEADLK 描述: 当前线程已经拥有互斥锁
20.解除锁定互斥锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
可释放mutex 引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。如果调用pthread_mutex_unlock() 时有多个线程被mutex 对象阻塞,则互斥锁变为可用时调度策略可确定获取该互斥锁的线程。对PTHREAD_MUTEX_ERRORCHECK_NP类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。
返回值
在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。
EPERM 描述: 当前线程不拥有互斥锁。
21. 互斥锁的类型:
1.如果互斥锁类型为PTHREAD_MUTEX_TIMED_NP,则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。(默认类型)
2.如果互斥锁类型为PTHREAD_MUTEX_ERRORCHECK_NP,则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
3.如果互斥锁类型为PTHREAD_MUTEX_RECURSIVE_NP,则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为1。线程每重新锁定该互斥锁一次,锁定计数就增加1。线程每解除锁定该互斥锁一次,锁定计数就减小1。锁定计数达到0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
22. 初始化互斥锁属性对象
int pthread_mutexattr_init(pthread_mutexattr_t *mattr);
pthread_mutexattr_init() 调用会导致分配类型为opaque 的对象。如果未销毁该对象,则会导致内存泄漏。
返回值:pthread_mutexattr_init() 成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。
ENOMEM 描述: 内存不足,无法初始化互斥锁属性对象。
23. 销毁互斥锁属性对象
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
可用来取消分配用于维护pthread_mutexattr_init() 所创建的属性对象的存储空间。
24.设置互斥锁类型的属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);
type 参数指定互斥锁的类型,有一下类型:
PTHREAD_MUTEX_TIMED_NP 不会检测死锁 0(默认类型)
PTHREAD_MUTEX_ERRORCHECK_NP 提供错误检查 2
PTHREAD_MUTEX_RECURSIVE_NP 递归锁 1
具体的区别看:《21. 互斥锁的类型》
返回值:如果运行成功,pthread_mutexattr_settype 函数会返回零。否则,将返回用于指明错误的错误号。
EINVAL 描述: 值为type 无效。
EINVAL 描述: attr 指定的值无效。
例子:下面第二次加锁时将出现死锁,如果改为PTHREAD_MUTEX_ERRORCHECK_NP类型,第二次加锁将会返回非零值,不会出现死锁。
24 int main(int argc, char *argv[])
25 {
26 pthread_t t1, t2, t3;
27 int ret;
28 pthread_mutexattr_t mutexattr;
29 pthread_mutexattr_init(&mutexattr);
30 pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_TIMED_NP);
31
32 pthread_mutex_init(&mu, &mutexattr);
33 if(!pthread_mutex_lock(&mu))
34 printf("get mutex (mian)\n");
35 if(!pthread_mutex_lock(&mu))
36 printf("get mutex (main)\n");
37 ret = pthread_create(&t1, NULL, consumer1, NULL);
38 ret = pthread_create(&t2, NULL, consumer2,NULL);
39 if(ret != 0)
40 {
41 printf("create failed,%d\n", ret);
42 exit(1);
43 }
44 sleep(2);
45 printf("main returned\n");
46 return 0;
47 }
注意:
1. PTHREAD_MUTEX_TIMED_NP类型的锁,一个线程加的锁可以被另一个线程解锁,也可以解除未被锁定的锁,不会返回错误。
2. PTHREAD_MUTEX_ERRORCHECK_NP类型的,加锁和解锁要同一个线程,如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。
例子:
7 pthread_mutex_t mu;
8 void *consumer1(void *p)
9 {
10 if(!pthread_mutex_lock(&mu))
11 printf("get mutex(1)\n");
12 else
13 return NULL;
14 pthread_mutex_unlock(&mu);
15 }
16 void *consumer2(void *p)
17 {
18 if(!pthread_mutex_lock(&mu))
19 printf("get mutex(2)\n");
20 else
21 return NULL;
22 pthread_mutex_unlock(&mu);
23 }
24 int main(int argc, char *argv[])
25 {
26 pthread_t t1, t2, t3;
27 int ret;
28 pthread_mutexattr_t mutexattr;
29 pthread_mutexattr_init(&mutexattr);
30 pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);
31
32 pthread_mutex_init(&mu, &mutexattr);
33 if(!pthread_mutex_lock(&mu))
34 printf("get mutex (mian)\n");
35 if(!pthread_mutex_lock(&mu))
36 printf("get mutex (main)\n");
37
38 pthread_mutex_unlock(&mu);
39 pthread_mutex_unlock(&mu);
40 ret = pthread_create(&t1, NULL, consumer1, NULL);
41 ret = pthread_create(&t2, NULL, consumer2,NULL);
42 if(ret != 0)
43 {
44 printf("create failed,%d\n", ret);
45 exit(1);
46 }
47 sleep(2);
48 printf("main returned\n");
49 return 0;
50 }
运行结果:
get mutex (mian)
get mutex (main)
get mutex(1)
get mutex(2)
main returned
注意:如果将39行注释掉,子线程将得不到锁。加几次锁就要解几次锁,而且要在同一个线程进行。如果解锁次数多于加锁次数,效果和相等一样。
互斥锁动态初始化和静态初始化区别:
互斥锁动态初始化函数:
int __pthread_mutex_init (mutex, mutexattr)
pthread_mutex_t *mutex;
const pthread_mutexattr_t *mutexattr;
{
const struct pthread_mutexattr *imutexattr;
assert (sizeof (pthread_mutex_t) <= __SIZEOF_PTHREAD_MUTEX_T);
imutexattr = (const struct pthread_mutexattr *) mutexattr ?: &default_attr;
/* Clear the whole variable. */
memset (mutex, '\0', __SIZEOF_PTHREAD_MUTEX_T);
/* Copy the values from the attribute. */
mutex->__data.__kind = imutexattr->mutexkind & ~0x80000000;
return 0;
}
互斥锁静态初始化:
#define PTHREAD_MUTEX_INITIALIZER \
{ }
都是把锁结构中的成员变量设置为0.锁结构如下:
typedef union
{
struct
{
int __lock;
unsigned int __count;
int __owner;
/* KIND must stay at this position in the structure to maintain
binary compatibility. */
int __kind;
unsigned int __nusers;
int __spins;
} __data;
char __size[__SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
销毁互斥锁:事实上没做任何销毁操作,如下:
Int __pthread_mutex_destroy (mutex)
pthread_mutex_t *mutex;
{
if (mutex->__data.__nusers != 0)
return EBUSY;
return 0;
}
非递归类型的互斥锁解锁和加锁操作:
解锁操作:就是设置mutex->__data.__owner = 0;所以重复解锁没有问题。
加锁:如果mutex->__data.__lock为0,得到锁,并设置mutex->__data.__owner = id;id即线程id号,否则阻塞在互斥锁。
29.初始化条件变量
动态方式:int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);
静态方式:使用PTHREAD_COND_INITIALIZER 宏可以将以静态方式定义的条件变量初始化为其缺省属性。PTHREAD_COND_INITIALIZER 宏与动态分配具有null 属性的pthread_cond_init() 等效,但是不进行错误检查。
pthread_cond_init() 在成功完成之后会返回零。
30.基于条件变量阻塞
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
阻塞的线程可以通过pthread_cond_signal() 或pthread_cond_broadcast() 唤醒,也可以在信号传送将其中断时唤醒。
不能通过pthread_cond_wait() 的返回值来推断与条件变量相关联的条件的值的任何变化。必须重新评估此类条件。
pthread_cond_wait() 例程每次返回结果时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此。
该条件获得信号之前,该函数一直被阻塞。该函数会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。
通常,对条件表达式的评估是在互斥锁的保护下进行的。如果条件表达式为假,线程会基于条件变量阻塞。然后,当该线程更改条件值时,另一个线程会针对条件变量发出信号。这种变化会导致所有等待该条件的线程解除阻塞并尝试再次获取互斥锁。必须重新测试导致等待的条件,然后才能从pthread_cond_wait() 处继续执行。唤醒的线程重新获取互斥锁并从pthread_cond_wait() 返回之前,条件可能会发生变化。等待线程可能并未真正唤醒。建议使用的测试方法是,将条件检查编写为调用pthread_cond_wait() 的while() 循环。
31.解除阻塞一个线程
int pthread_cond_signal(pthread_cond_t *cv);
应在互斥锁的保护下修改相关条件,该互斥锁用于获得信号的条件变量中。否则,可能在条件变量的测试和pthread_cond_wait() 阻塞之间修改该变量,这会导致无限期等待。
默认设置下还是唤醒第一次被加入的线程,如果没有任何线程基于条件变量阻塞,则调用pthread_cond_signal() 不起作用。
例子:
7 pthread_cond_t cond;
8 pthread_mutex_t mp;
9 void *consumer1(void *p)
10 {
11 pthread_mutex_lock(&mp);
12 printf("wait>>>(1)\n");
13 pthread_cond_wait(&cond, &mp);
14 pthread_mutex_unlock(&mp);
15 printf("wake <<<(1)\n");
16 return NULL;
17 }
18 void *consumer2(void *p)
19 {
20 pthread_mutex_lock(&mp);
21 printf("wait>>>(2)\n");
22 pthread_cond_wait(&cond, &mp);
23 pthread_mutex_unlock(&mp);
24 printf("wake <<<(2)\n");
25 return NULL;
26 }
27 void *consumer3(void *p)
28 {
29 pthread_mutex_lock(&mp);
30 printf("wait>>>(3)\n");
31 pthread_cond_wait(&cond, &mp);
32 pthread_mutex_unlock(&mp);
33 printf("wake <<<(3)\n");
34 return NULL;
35 }
36 int main(int argc, char *argv[])
37 {
38 pthread_t t1, t2, t3;
39 int ret;
40 struct sched_param sched;
41 sched.__sched_priority = 10;
42
43 pthread_cond_init(&cond, NULL);
44 pthread_mutex_init(&mp, NULL);
45
46 ret = pthread_create(&t1, NULL, consumer1, NULL);
47 ret = pthread_create(&t2, NULL, consumer2, NULL);
48 ret = pthread_create(&t3, NULL, consumer3, NULL);
49 sleep(1);
50 pthread_setschedparam(t2, SCHED_FIFO, &sched);
sleep(1);
51 pthread_cond_signal(&cond);
52 sleep(6);
53 printf("main returned\n");
54 return 0;
55 }
运行结果:
wait>>>(3)
wait>>>(2)
wait>>>(1)
wake <<<(3)
main returned
从运行结果看:设置t2为实时线程,但是还是唤醒第一次被加入队列的线程t3
但是,下面这个函数就是按照优先级唤醒。
31.解除阻塞所有线程
int pthread_cond_broadcast(pthread_cond_t *cv);
将上面的51行改为:pthread_cond_broadcast(&cond);运行结果将是:
wait>>>(3)
wait>>>(2)
wait>>>(1)
wake <<<(2)
wake <<<(1)
wake <<<(3)
main returned
例子:
7 pthread_cond_t cond;
8 pthread_mutex_t mp;
9 void *consumer1(void *p)
10 {
11 sleep(1);
12 printf("wait>>>(1)\n");
13 pthread_cond_wait(&cond, &mp);
14 printf("wake <<<(1)\n");
15 return NULL;
16 }
17 int main(int argc, char *argv[])
18 {
19 pthread_t t1, t2, t3;
20 int ret;
21
22 pthread_cond_init(&cond, NULL);
23 pthread_mutex_init(&mp, NULL);
24
25 ret = pthread_create(&t1, NULL, consumer1, NULL);
26 pthread_cond_broadcast(&cond);
27 sleep(6);
28 printf("main returned\n");
29 return 0;
30 }
运行结果:
wait>>>(1)
main returned
注意:如果信号提前发出,而且没线程阻塞,这个信号将会消失。所以没有返回wake <<<(1),因为上面当发出pthread_cond_broadcast(&cond);时,并没有线程阻塞。
33. 在指定的时间之前阻塞
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const struct timespec *abstime);
每次返回时调用线程都会锁定并且拥有互斥锁,即使返回错误时也是如此
例子:
7 pthread_cond_t cond;
8 pthread_mutex_t mp;
9 struct timespec to;
10
11 void *consumer1(void *p)
12 {
13 to.tv_sec = time(NULL) + 4;
14 to.tv_nsec = 0;
15 pthread_mutex_lock(&mp);
16 printf("wait>>>(1)\n");
17 pthread_cond_timedwait(&cond, &mp, &to);
18 pthread_mutex_unlock(&mp);
19 printf("wake <<<(1)\n");
20 return NULL;
21 }
22 int main(int argc, char *argv[])
23 {
24 pthread_t t1, t2, t3;
25 int ret;
26
27 pthread_cond_init(&cond, NULL);
28 pthread_mutex_init(&mp, NULL);
29 ret = pthread_create(&t1, NULL, consumer1, NULL);
30 sleep(6);
31 printf("main returned\n");
32 return 0;
33 }
运行结果:
wait>>>(1)
wake <<<(1)
main returned
阻塞4秒后返回
32.唤醒丢失问题—这里不是很明白
如果线程未持有与条件相关联的互斥锁,则调用pthread_cond_signal() 或
pthread_cond_broadcast() 会产生唤醒丢失错误。
满足以下所有条件时,即会出现唤醒丢失问题:
一个线程调用pthread_cond_signal() 或pthread_cond_broadcast(),另一个线程已经测试了该条件,但是尚未调用pthread_cond_wait(),由于此事没有正在等待的线程,信号不起作用,因此将会丢失。
最终的结果是:需要测试的条件是满足的,但是却有线程被阻塞。
仅当修改所测试的条件但未持有与之相关联的互斥锁时,才会出现此问题。
只要仅在持有关联的互斥锁同时修改所测试的条件,即可调pthread_cond_signal() 和pthread_cond_broadcast(),而无论这些函数是否持有关联的互斥锁。
33. 计数信号量概述: 需要包含boost内的 #include <semaphore.h>
信号量也就是操作系统中所用到的PV原语,它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。信号量相当于pthread_cond_timedwait 或者 pthread_cond_wait对于pthread_cond_t的控制。
PV原语是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止。
PV原语主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥,几个进程(或线程)往往只设置一个信号量sem,它们的操作流程如图9.2 所示。
当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行。
线程在资源增加时会增加计数,在删除资源时会减小计数,这些操作都以原子方式执行。如果信号计数变为零,则表明已无可用资源。计数为零时,尝试减小信号的线程会被阻塞,直到计数大于零为止。
sem_init用于创建一个信号量,并能初始化它的值。
sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。
sem_post相当于V操作,它将信号量的值加一同时发出信号唤醒等待的进程。
sem_getvalue用于得到信号量的值。
sem_destroy用于删除信号量。
sem_init
所需头文件 #include <semaphore.h>
函数原型 int sem_init(sem_t *sem,int pshared,unsigned int value)
函数传入值 sem:信号量
pshared:决定信号量能否在几个进程间共享。由于目前Linux 还没有实现进程间共享信号量,所以这个值只能够取0
value :信号量初始化值
函数返回值 成功:0
出错:-1
sem_wait
函数原型 int sem_wait(sem_t *sem)
int sem_trywait(sem_t *sem)
int sem_post(sem_t *sem)
int sem_getvalue(sem_t *sem)
int sem_destroy(sem_t *sem)
函数传入值 sem:信号量
函数返回值 成功:0
出错:-1
实例:
使用一个信号量实现互斥:
/*sem_mutex.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>
int lock_var;
time_t end_time;
sem_t sem;
void pthread1(void *arg);
void pthread2(void *arg);
int main(int argc, char *argv[])
{
pthread_t id1,id2;
pthread_t mon_th_id;
int ret;
end_time = time(NULL)+30;
/*初始化信号量为1*/
ret=sem_init(&sem,0,1);
if(ret!=0)
{
perror("sem_init");
}
/*创建两个线程*/
ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
if(ret!=0)
perror("pthread cread1");
ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
if(ret!=0)
perror("pthread cread2");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void pthread1(void *arg)
{
int i;
while(time(NULL) < end_time)
{
/*信号量减一,P操作*/
sem_wait(&sem);
for(i=0;i<2;i++)
{
sleep(1);
lock_var++;
printf("lock_var=%d\n",lock_var);
}
printf("pthread1:lock_var=%d\n",lock_var);
/*信号量加一,V操作*/
sem_post(&sem);
sleep(1);
}
}
void pthread2(void *arg)
{
int nolock=0;
int ret;
while(time(NULL) < end_time)
{
/*信号量减一,P操作*/
sem_wait(&sem);
printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
/*信号量加一,V操作*/
sem_post(&sem);
sleep(3);
}
}
使用两个信号量实现两个线程间的同步:
/*sem_syn.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>
int lock_var;
time_t end_time;
sem_t sem1,sem2;
void pthread1(void *arg);
void pthread2(void *arg);
int main(int argc, char *argv[])
{
pthread_t id1,id2;
pthread_t mon_th_id;
int ret;
end_time = time(NULL)+30;
/*初始化两个信号量,一个信号量为1,一个信号量为0*/
ret=sem_init(&sem1,0,1);
ret=sem_init(&sem2,0,0);
if(ret!=0)
{
perror("sem_init");
}
/*创建两个线程*/
ret=pthread_create(&id1,NULL,(void *)pthread1, NULL);
if(ret!=0)
perror("pthread cread1");
ret=pthread_create(&id2,NULL,(void *)pthread2, NULL);
if(ret!=0)
perror("pthread cread2");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void pthread1(void *arg)
{
int i;
while(time(NULL) < end_time)
{
/*P操作信号量2*/
sem_wait(&sem2);
for(i=0;i<2;i++)
{
sleep(1);
lock_var++;
printf("lock_var=%d\n",lock_var);
}
printf("pthread1:lock_var=%d\n",lock_var);
/*V操作信号量1*/
sem_post(&sem1);
sleep(1);
}
}
void pthread2(void *arg)
{
int nolock=0;
int ret;
while(time(NULL) < end_time)
{
/*P操作信号量1*/
sem_wait(&sem1);
printf("pthread2:pthread1 got lock;lock_var=%d\n",lock_var);
/*V操作信号量2*/
sem_post(&sem2);
sleep(3);
}
}
该程序实现了先运行线程二,再运行线程一。
34. 初始化信号
int sem_init(sem_t *sem, int pshared, unsigned int value);
value信号量初始资源个数
如果pshared 的值为零,则不能在进程之间共享信号。如果pshared 的值不为零,则可以在进程之间共享信号。
35. 增加信号
int sem_post(sem_t *sem);
使用sem_post(3RT) 可以原子方式增加sem 所指示的信号。
如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。
36. 基于信号计数进行阻塞
int sem_wait(sem_t *sem);
使用sem_wait(3RT) 可以阻塞调用线程,直到sem 所指示的信号计数大于零为止,之后以原子方式减小计数。
附加:互斥锁与信号灯的区别:
比如说,信号,那是多线程同步用的,一个线程完成了某一个动作就通过信号告诉别的线程,别的线程再进行某些动作。
互斥锁,这是多线程互斥用的,比如说,一个线程占用了某一个资源,那么别的线程就无法访问,知道这个线程离开,其他的线程才开始可以利用这个资源。
互斥锁是为上锁而优化,条件变量是为等待而优化的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性-----摘自《unix网络编程之进程间通讯》192页
37.多线程链表添加删除例子(使用条件变量实现互斥):
1 #include <stdlib.h>
2 #include <pthread.h>
3 #include <stdio.h>
4 #include <sched.h>
5 #include <string.h>
6 typedef struct _list_head list_head;
7 struct _list_head{
8 list_head *next;
9 list_head *prev;
10 int used;
11 int data;
12 };
13
14 list_head _head = {&_head, &_head, 0, 0};
15 list_head *head = &_head;
16
17 void list_add(list_head *entry, list_head *head)
18 {
19 head->next->prev = entry;
20 entry->next = head->next;
21 entry->prev = head;
22 head->next = entry;
23 }
24
25 list_head *list_del(list_head *entry)
26 {
27 entry->next->prev = entry->prev;
28 entry->prev->next = entry->next;
29 return entry;
30 }
31
32 pthread_cond_t cond;
33 pthread_mutex_t lock;
34
35 void *producer(void *arg)
36 {
37 do{
38 list_head *node;
39 if(node = malloc(sizeof(list_head)) )
40 {
41 memset((void *)node, '\0', sizeof(list_head));
42 node->data = rand();
43 }
44 else
45 return NULL;
46
47 pthread_mutex_lock(&lock);
48 while(head->used != 0)
49 {
50 pthread_cond_wait(&cond, &lock);
51 }
52 head->used = 1;
53 pthread_mutex_unlock(&lock);
54
55 list_add(node, head);
56 printf("product %d\n", node->data);
57
58 head->used = 0;
59 pthread_cond_signal(&cond);
60 }while(1);
61
62 }
63
64 void *consumer(void *arg)
65 {
66 do{
67 list_head *node;
68
69 pthread_mutex_lock(&lock);
70 while(head->used != 0 || head->next == head->prev)
71 {
72 pthread_cond_wait(&cond, &lock);
73 }
74 head->used = 1;
75 pthread_mutex_unlock(&lock);
76
77 node = head->next;
78 list_del(node);
79 printf("consumer %d\n", node->data);
80 free(node);
81
82 head->used = 0;
83 pthread_cond_signal(&cond);
84 }while(1);
85 }
86
87 int main(int argc, char *argv[])
88 {
89 pthread_t t1, t2;
90 int ret;
91 pthread_cond_init(&cond, NULL);
92 pthread_mutex_init(&lock, NULL);
93
94 pthread_create(&t1, NULL, producer, NULL);
95 pthread_create(&t2, NULL, consumer, NULL);
96
97 pthread_join(t1, NULL);
98 pthread_join(t2, NULL);
99
100 return 1;
101 }
注意:最后两个join是必须的,否则主线程将提早结束,不会一直执行循环。
38.为线程特定数据创建键
单线程C 程序有两类基本数据:局部数据和全局数据。对于多线程C 程序,添加了第三类数据:线程特定数据。线程特定数据与全局数据非常相似,区别在于前者为线程专有。
int pthread_key_create(pthread_key_t *key,void (*destructor) (void *));
可以使用pthread_key_create(3C) 分配用于标识进程中线程特定数据的键。键对进程中的所有线程来说是全局的。创建线程特定数据时,所有线程最初都具有与该键关联的NULL值。
使用各个键之前,会针对其调用一次pthread_key_create()。不存在对键(为进程中所有的线程所共享)的隐含同步。
创建键之后,每个线程都会将一个值绑定到该键。这些值特定于线程并且针对每个线程单独维护。如果创建该键时指定了destructor 函数,则该线程终止时,系统会调用destructor 函数,传进的参数是绑定的值。
当pthread_key_create() 成功返回时,会将已分配的键存储在key 指向的位置中。调用方必须确保对该键的存储和访问进行正确的同步。
使用可选的析构函数destructor 可以释放过时的存储。如果某个键具有非NULL destructor函数,而线程具有一个与该键关联的非NULL 值,则该线程退出时,系统将使用当前的相关值调用destructor 函数。destructor 函数的调用顺序不确定。
39. 删除线程特定数据键
int pthread_key_delete(pthread_key_t key);
使用pthread_key_delete() 可以销毁现有线程特定数据键。由于键已经无效,因此将释放与该键关联的所有内存。引用无效键将返回错误。
40.设置线程特定数据
int pthread_setspecific(pthread_key_t key, const void *value);
为指定线程特定数据键设置线程特定绑定。
41. 获取线程特定数据
void *pthread_getspecific(pthread_key_t key);
获取调用线程的键绑定。
例子:
9 pthread_key_t key;
10 void destructor(void *data)
11 {
12 if(data != NULL)
13 free(data);
14 printf("thread (%u) do free key\n", (unsigned)pthread_self());
15 }
16 void print_key(void)
17 {
18 char *p;
19 p = (char *)pthread_getspecific(key);
20 printf("(%u) key_value:%s\n", (unsigned)pthread_self(), p);
21 }
22 void *thread1(void *arg)
23 {
24 printf("start thread (%u)\n", (unsigned)pthread_self());
25 char * p = malloc(7*sizeof(char));
26 memset(p, 'a', 6);
27 p[6] = '\0';
28 pthread_setspecific(key, p);
29 printf("(%u)setkey:%s\n", pthread_self(), p);
30 print_key();
31 printf("thread (%u) end\n", pthread_self());
32 }
33 void *thread2(void *arg)
34 {
35 printf("start thread (%u)\n", (unsigned)pthread_self());
36 char * p = malloc(7*sizeof(char));
37 memset(p, 'c', 6);
38 p[6] = '\0';
39 pthread_setspecific(key, p);
40 printf("(%u)setkey:%s\n", pthread_self(), p);
41 print_key();
42 printf("thread (%u) end\n", pthread_self());
43 }
44 int main(int argc, char *argv[])
45 {
46 pthread_t t1, t2, t3;
47
48 pthread_key_create(&key, destructor);
49
50 pthread_create(&t1, NULL, thread1, NULL);
51 pthread_create(&t2, NULL, thread2, NULL);
52
53 pthread_join(t1, NULL);
54 pthread_join(t2, NULL);
55 printf("main end\n");
56 return 0;
57 }
运行结果:
start thread (3069139856)
(3069139856)setkey:cccccc
(3069139856) key_value:cccccc
thread (3069139856) end
thread (3069139856) do free key
start thread (3077532560)
(3077532560)setkey:aaaaaa
(3077532560) key_value:aaaaaa
thread (3077532560) end
thread (3077532560) do free key
main end
注意:
1.在每个线程结束后系统会执行注册的撤销函数。
2.如果使用pthread_exit()提前终止线程,也会调用撤销函数。
读写锁:
抛开读锁,写锁与互斥锁基本一致,读写锁是后来补充的,原来根本没有,自己用mutex,condtion,seme实现的。
读写锁属性初始化:
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
描述:使用默认值初始化一个读写锁属性对象,如果用一个属性对象初始化多个读写锁,改变(包括销毁)属性对象,不会影响已经初始化的读写锁。
返回值:返回0表示正确,返回ENOMEM表示系统没足够的内存。
默认值是:只能在创建它的进程内使用,不能通过共享内存被多个进程共享。
销毁读写锁属性对象:
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
描述:销毁读写锁属性对象。
在linux的POSIX库的实现就是直接返回0,没做任何事情。函数实现如下:
Int pthread_rwlockattr_destroy (attr)
pthread_rwlockattr_t *attr;
{
/* Nothing to do. For now. */
return 0;
}
设置读写锁共享属性:
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
int pshared);
描述:设置PTHREAD_PROCESS_SHARED则读写锁可以通过共享内存被多个进程使用。设置PTHREAD_PROCESS_PRIVATE,则读写锁只能在被创建的进程使用(默认值)。
返回值:成功返回0
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *
restrict attr, int *restrict pshared);
描述:从属性对象中获得共享属性类型,存储在pshared指向的内存中。
返回值: 成功返回0
初始化读写锁:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
描述:初始化一个读写锁,状态为unlock,属性由attr指定,如果attr为NULL,使用默认属性,默认属性为:PTHREAD_PROCESS_PRIVATE。只需初始化的一次,就能被一直使用。如果多次被初始化,结果将不确定,(如果已经有线程阻塞在读写锁,此时重新初始化读写锁,将可能出现死锁)。
返回值:成功返回0
销毁读写锁:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
描述:销毁读写锁。
Linux的实现就是直接返回0, 没做任何事情。函数实现如下:
Int __pthread_rwlock_destroy (rwlock)
pthread_rwlock_t *rwlock;
{
/* Nothing to be done. For now. */
return 0;
}
下面是如果已经有线程阻塞在读写锁,此时重新初始化读写锁,将出现死锁例子。
8 pthread_rwlock_t mutex;
17 void *thread1(void *arg)
18 {
19 printf("start thread (1)\n");
20 sleep(2);
21 pthread_rwlock_init(&mutex, NULL);
22 pthread_rwlock_unlock(&mutex);
23 printf("thread (1) end\n");
24 }
25 int main(int argc, char *argv[])
26 {
27 pthread_t t1, t2, t3;
28 int ret, i;
29 printf("main start\n");
30 pthread_rwlock_init(&mutex, NULL);
31 if(!pthread_rwlock_rdlock(&mutex))
32 printf("main get lock(1)\n");
33
34 pthread_create(&t1, NULL, thread1, NULL);
35 printf("lock wrlock\n");
36 if(!pthread_rwlock_wrlock(&mutex))
37 printf("main get lock(2)\n");
38
39 pthread_join(t1, NULL);
40 printf("main end\n");
41 return 0;
42 }
运行结果:最终在36行出现死锁。如下:
main start
main get lock(1)
lock wrlock
start thread (1)
thread (1) end
如果将21行注释掉,不会出现死锁:结果如下:
main start
main get lock(1)
lock wrlock
start thread (1)
thread (1) end
main get lock(2)
main end
分析原因:读写锁使用到了唤醒等待机制,分析对比源代码
__pthread_rwlock_wrlock()和int__pthread_cond_wait(),
int__pthread_rwlock_unlock()和int__pthread_cond_signal()
函数的实现是相似的,应该可以判断读写锁用到了等待唤醒机制。
如果简单的将互斥锁置0,已经阻塞的线程不会获得锁,必须调用unlock,阻塞线程才能获得锁,也就是,unlock里面不仅将互斥锁置0,还应该给其他线程发送了一个信号。
现在的问题就是:线程在阻塞期间,是否还占用CPU?
可以这样验证:如果阻塞的线程不占用CPU,它必须要其它线程唤醒,才能获得锁,唤醒它的线程要得到
读写锁总结:
读者(获得读锁的线程),写者(获得写锁的线程)
1.在读锁阻塞,加入读等待队列,在写锁阻塞,加入写等待队列。
2.某线程获得读锁,读计数器加1,某线程获得写锁,写计数器置1.
3.Unlock 第一部分:读者释放锁,读计数器减1,写者释放锁,写计数器置0.
Unlock 第二部分:当读计数器为0时开始唤醒操作,先从写等待队列中唤醒一个,如果写等待队列为0,再从读等待队列唤醒一个,都空则不唤醒。
4.如果写计数器为0,可以获得读锁,否则阻塞在读锁,如果写者想获得读锁,返回错误;如果写计数器和读计数器都为0,可以获得写锁,否则阻塞在写锁。
运行结果:28行最终会得到锁。如下:
main start
main get lock(1)
lock wrlock
start thread (1)
main get lock(2)
thread (1) end
main end
获得写锁:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
如果读写锁中的读锁或写锁已经锁定,则调用线程将阻塞,直到释放所有的读锁和写锁为止。如果调用线程已经获得了读锁,则调用线程将出现死锁。
返回值:成功返回0.
下面例子用于验证出现死锁情况:
8 pthread_rwlock_t mutex;
24 int main(int argc, char *argv[])
25 {
26 pthread_t t1, t2, t3;
27 int ret, i;
28 printf("main start\n");
29 pthread_rwlock_init(&mutex, NULL);
30 if(!pthread_rwlock_rdlock(&mutex))
31 printf("main get lock(1)\n");
32 if(!pthread_rwlock_wrlock(&mutex))
33 printf("main get lock(2)\n");
34 printf("main end\n");
35 return 0;
36 }
运行结果:1. 主线程在32行出现死锁。
2. 如果把30行也改为获得写锁,则32行将返回错误,但不会阻塞。
3.如果先获得写锁,再获得读锁,获得读锁返回错误,但不会阻塞。
如果在31行后创建一个子线程,在主线程32行出现死锁后,子线程调用pthread_rwlock_unlock(&mutex)解锁,则主线程死锁被解除,如下:
8 pthread_rwlock_t mutex;
17 void *thread1(void *arg)
18 {
19 printf("start thread (1)\n");
20 sleep(1);
21 pthread_rwlock_unlock(&mutex);
22 }
23 int main(int argc, char *argv[])
24 {
25 pthread_t t1, t2, t3;
26 int ret, i;
27 printf("main start\n");
28 pthread_rwlock_init(&mutex, NULL);
29 if(!pthread_rwlock_rdlock(&mutex))
30 printf("main get lock(1)\n");
31 pthread_create(&t1, NULL, thread1, NULL);
32 if(!pthread_rwlock_wrlock(&mutex))
33 printf("main get lock(2)\n");
34 printf("main end\n");
35 return 0;
36 }
输出结果:死锁情况被解除
main start
main get lock(1)
start thread (1)
main get lock(2)
main end
获得读锁:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
如果读写锁中的写锁已经锁定,则调用线程将阻塞,直到释放写锁为止。否则,将获取读锁。
返回值:rw_rdlock() 在成功完成之后返回零。
如果已经有其它线程阻塞在写锁,调用线程还是可以获得读锁,如下:
8 pthread_rwlock_t mutex;
17 void *thread1(void *arg)
18 {
19 printf("start thread (1)\n");
20 sleep(2);
21 if(!pthread_rwlock_rdlock(&mutex))
22 printf("thread (1) get rd lock\n");
23 printf("thread (1) end\n");
24 pthread_rwlock_unlock(&mutex);
25 }
26 int main(int argc, char *argv[])
27 {
28 pthread_t t1, t2, t3;
29 int ret, i;
30 printf("main start\n");
31 pthread_rwlock_init(&mutex, NULL);
32 if(!pthread_rwlock_rdlock(&mutex))
33 printf("main get lock(1)\n");
34 pthread_create(&t1, NULL, thread1, NULL);
35 if(!pthread_rwlock_wrlock(&mutex))
36 printf("main get lock(2)\n");
37 printf("main end\n");
38 sleep(3);
39 return 0;
40 }
运行结果:子线程可以运行结束,主线程阻塞在35行。
main start
main get lock(1)
start thread (1)
thread (1) get rd lock
thread (1) end
注意:有几个加锁就要对应几个解锁,但不能多解,如下:
8 pthread_rwlock_t mutex;
17 void *thread1(void *arg)
18 {
19 printf("start thread (1)\n");
20 sleep(2);
21 if(!pthread_rwlock_rdlock(&mutex))
22 printf("thread (1) get rd lock\n");
23 pthread_rwlock_unlock(&mutex);
24 pthread_rwlock_unlock(&mutex);
25 printf("thread (1) end\n");
26 }
27 int main(int argc, char *argv[])
28 {
29 pthread_t t1, t2, t3;
30 int ret, i;
31 printf("main start\n");
32 pthread_rwlock_init(&mutex, NULL);
33 if(!pthread_rwlock_rdlock(&mutex))
34 printf("main get lock(1)\n");
35 pthread_create(&t1, NULL, thread1, NULL);
36 if(!pthread_rwlock_wrlock(&mutex))
37 printf("main get lock(2)\n");
38 printf("main end\n");
39 sleep(3);
40 return 0;
41 }
运行结果:子线程调用两次unlock解开读锁,则主线程36行的获得写锁
main start
main get lock(1)
start thread (1)
thread (1) get rd lock
main get lock(2)
main end
thread (1) end
读锁被多解,以后获得写锁将会出现死锁,如下:
8 pthread_rwlock_t mutex;
26 int main(int argc, char *argv[])
27 {
28 pthread_t t1, t2, t3;
29 int ret, i;
30 printf("main start\n");
31 pthread_rwlock_init(&mutex, NULL);
32 if(!pthread_rwlock_rdlock(&mutex))
33 printf("main get lock(1)\n");
34 pthread_rwlock_unlock(&mutex);
35 pthread_rwlock_unlock(&mutex);
36 printf("unlock 2\n");
37 if(!pthread_rwlock_wrlock(&mutex))
38 printf("main get lock(2)\n");
39 printf("main end\n");
40 sleep(3);
41 return 0;
42 }
运行结果:在37行出现死锁
main start
main get lock(1)
unlock 2
注意:读锁或写锁被多解,unlock处不会阻塞,但是以后获得写锁将阻塞,获得读锁将返回错误,但不会阻塞。