linux-------线程教程
编译时要连接库pthread:gcc main.cpp -o main -lpthread
1. 线程标识
进程ID在整个系统中唯一,但线程ID只在所属进程上下文中有意义
类型:pthread\_t ,要用专用函数比较(不同类型的系统实现不一样,linux用无符号长整型数、solaris用无符号整型、FresBSD和Mac用指向pthread的指针标表示)
(1)比较线程ID
相等返回非0;不等返回0
(2)获取自身ID
2. 创建线程
成功返回0;失败返回错误编号
参数:
attr用来设置线程属性
start\_routine:新创建的线程从这个函数开始运行,参数是arg,如有多个参数,则把参数放在一个结构中,然后把结构地址传给arg
3. 线程终止
单个进程退出的方式:
①从启动程序返回
②被同一进程中其它线程取消
③线程调用 pthread\_exit
(1)线程退出
正常退出,retval存储返回码;被其它线程取消,retval被设置为 PTHREAD\_CANCELED
(2)等待线程终止
(3)线程取消
请求取消同一进程中的其它线程。成功返回0;失败返回错误编号
thread对应的线程可以选择忽略取消或控制如何被取消
(4)线程清理程序/线程退出执行程序
线程执行如下动作时,由pthread\_cleanup\_push调度清理函数routine:(线程在其启动程序中用 return 返回,则清理函数不会被调用)
①线程调用pthread\_exit
②响应取消请求
③用非0 execute参数 调用pthread\_cleanup\_pop时
execute为0,则删除顶端的一个清理函数
测试例程:
1 #include <stdio.h>
2 #include <pthread.h>
3
4 void cleanup(void *arg)
5 {
6 printf("cleanup:%s\n",(char *)arg);
7 }
8
9 void thr_fn1(void *arg)
10 {
11 printf("thread 1 start\n");
12 pthread_cleanup_push(cleanup,"thread 1 first handler");
13 pthread_cleanup_push(cleanup,"thread 1 second handler");
14 printf("thread 1 push complete\n");
15 if(arg) {
16 return ((void *)1);
17 }
18 pthread_cleanup_pop(0);
19 pthread_cleanup_pop(0);
20 return ((void *)1);
21 }
22
23 void thr_fn2(void *arg)
24 {
25 printf("thread 2 start\n");
26 pthread_cleanup_push(cleanup,"thread 2 first handler");
27 pthread_cleanup_push(cleanup,"thread 2 second handler");
28 printf("thread 2 push complete\n");
29 if(arg) {
30 pthread_exit((void *)2);
31 }
32 pthread_cleanup_pop(0);
33 pthread_cleanup_pop(0);
34 pthread_exit((void *)2);
35 }
36
37 int main(void)
38 {
39 int err;
40 pthread_t tid1,tid2;
41 void *tret;
42 err = pthread_create(&tid1,NULL,thr_fn1,(void *)1);
43 if(err != 0) {
44 printf("cant create thread 1\n");
45 exit(1);
46 }
47 err = pthread_create(&tid2,NULL,thr_fn2,(void *)1);
48 if(err != 0) {
49 printf("cant create thread 2\n");
50 exit(2);
51 }
52 err = pthrad_join(tid1,&tret);
53 if(err != 0) {
54 printf("cant join thread 1\n");
55 exit(1);
56 }
57 printf("thread 1 exit code:%ld\n",(long)tret);
58 err = pthrad_join(tid2,&tret);
59 if(err != 0) {
60 printf("cant join thread 1\n");
61 exit(1);
62 }
63 printf("thread 2 exit code:%ld\n",(long)tret);
64 return 0;
65 }
(5)线程分离
默认情况线程终止状态会保存到对该线程调用 pthread\_join ,若线程被分离,则线程的底层存储资源可在线程终止时被立即收回。被分离的线程不能对它用 pthread\_join
4. 线程同步
每个线程都有线程ID、一组寄存器值、栈、调度优先级、信号屏蔽字、errno变量。进程的所有信息对该进程的所有线程都是共享的,包括可执行代码、全局北村、堆内存、栈、文件描述符。
没有同步的例程:两个线程对全局变量ticket\_num进行操作
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5
6 int ticket_num = 10;
7
8 void *sell_ticket(void *arg)
9 {
10 int i = 0;
11 for(;i<10;i++) {
12 if(ticket_num>0) {
13 sleep(1);
14 printf("sell the %dth ticket\n",10-ticket_num+1);
15 ticket_num--;
16 }
17 }
18 return 0;
19 }
20
21 int main()
22 {
23 int flag,i;
24 pthread_t tids[2];
25 for(i=0;i<2;i++) {
26 flag = pthread_create(&tids[i],NULL,sell_ticket,NULL);
27 if(flag != 0) {
28 printf("pthread create error,flag:%d\n",flag);
29 exit(flag);
30 }
31 }
32 sleep(10);
33 for(i=0;i<2;i++) {
34 flag = pthread_join(tids[i],NULL);
35 }
36 return 0;
37 }
结果:
4.1 互斥量mutex
本质是锁,在访问共享资源前对互斥量加锁,访问完成后解锁。
(1)
静态分配的互斥量,初始化时可设为常量 PTHREAD\_MUTEX\_INITIALIZER,或通过 pthread\_mutex\_init 函数初始化
动态分配的(如malloc分配的),释放内存前要调用 pthread\_mutex\_destroy
abstime指定了愿意等待/阻塞的绝对时间(如果超过此时间,不再对互斥量加锁,而是返回错误码 ETIMEDOUT)
(2)互斥锁属性
设置和获取互斥锁属性:
pshared:互斥锁的共享属性 ①PTHREAD\_PROCESS\_PRIVATE,只能用于一个进程内两个线程互斥
②PTHREAD\_PROCESS\_SHARED,可用于不同进程的线程间互斥,使用时要在进程共享内存中分配互斥锁,然后设置该锁的这个属性
(3)类型
kind:互斥锁类型
①PTHREAD\_MUTEX\_NOMAL,标准互斥锁
②PTHREAD\_MUTEX\_RECURSICE,递归互斥锁(内部有计数器,加一次锁计数器加1,解一个锁计数器减1)
③PTHREAD\_MUTEX\_ERRORCHECK,检查互斥锁,上锁成功后再上锁,出错返回错误信息,不阻塞
④PTHREAD\_MUTEX\_DEFAULT,默认互斥锁
(4)特点
①原子性:一个线程锁定了一个互斥量,则没有其它线程能在同一时间成功锁定这个互斥量
②唯一性:互斥量被一个线程锁定了,在解除锁定之前,其它线程不能锁定这个互斥量
③非繁忙等待:互斥量被第一个线程锁定,第二个线程又试图锁定,则第二个线程挂起,直到第一个线程接触锁定,第二个线程被唤醒
(4)操作流程
①访问共享资源后临界资源前,加锁
②访问完成后释放互斥量上的锁
(5)死锁
①一个线程试图对同一个互斥量第二次加锁
②两个互斥量,线程A占有第一个互斥量,在试图锁第二个互斥量时被挂起;线程B占用第二个互斥量,试图锁第一个互斥量,被挂起。于是线程A、B都无法继续运行,产生死锁
解决:使用trylock方法,如果成功,则继续运行;如果trylock失败,则先释放已拥有的锁,过一段时间之后再重新尝试
使用互斥锁的例程:
变化:在对共享资源ticket\_num测试、操作前获取锁,测试、操作完后释放锁 行7、14、20
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5
6 int ticket_num = 10;
7 pthread_mutex_t mutexa = PTHREAD_MUTEX_INITIALIZER; //静态初始化互斥锁
8
9 void *sell_ticket(void *arg)
10 {
11 int i = 0;
12 for(;i<10;i++) {
13 //在对竞争资源操作、测试前获取锁
14 pthread_mutex_lock(&mutexa);
15 if(ticket_num>0) {
16 sleep(1);
17 printf("sell the %dth ticket\n",10-ticket_num+1);
18 ticket_num--;
19 }
20 pthread_mutex_unlock(&mutexa);
21 }
22 return 0;
23 }
24
25 int main()
26 {
27 int flag,i;
28 pthread_t tids[2];
29 for(i=0;i<2;i++) {
30 flag = pthread_create(&tids[i],NULL,sell_ticket,NULL);
31 if(flag != 0) {
32 printf("pthread create error,flag:%d\n",flag);
33 exit(flag);
34 }
35 }
36 sleep(10);
37 for(i=0;i<2;i++) {
38 flag = pthread_join(tids[i],NULL);
39 }
40 return 0;
41 }
运行结果:
4.2 条件变量
互斥锁用于上锁,条件变量用于等待。
使用情况:线程A需要等待某个条件成立后才能继续往下执行,如果条件不满足,这个线程就一直阻塞等待。如果某时刻条件满足了,就唤醒A继续执行(若用互斥量,需要线程不断获取锁然后判断条件,效率低)
(1)初始化
(2) 等待条件成立
wait函数传入了一个锁住的互斥量,函数自动把调用线程放到等待条件的线程列表之上,对互斥量解锁。wait函数返回时,互斥量再次被锁住。
(3)条件满足后通知线程
(4)简答例子
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <pthread.h>
5
6 int ticket_num = 10;
7 pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; //静态初始化互斥锁
8 pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
9 int x = 10,y = 20;
10
11 void *fun1(void *arg)
12 {
13 printf("thread 1 start\n");
14 pthread_mutex_lock(&qlock);
15 while(x<y) {
16 pthread_cond_wait(&qready,&qlock);
17 }
18 sleep(3);
19 printf("thread 1 exit\n");
20 pthread_exit(NULL);
21 }
22
23 void *fun2(void *arg)
24 {
25 printf("thread 2 start\n");
26
27 pthread_mutex_lock(&qlock);
28 x = 20;
29 y = 10;
30 printf("change:x=%d y=%d\n",x,y);
31 pthread_mutex_unlock(&qlock);
32 if(x>y) {
33 pthread_cond_signal(&qready);
34 }
35
36 printf("thread 2 exit\n");
37 pthread_exit(NULL);
38 }
39
40 int main()
41 {
42 pthread_t tids[2];
43 int flag;
44 flag = pthread_create(&tids[0],NULL,fun1,NULL);
45 if(flag != 0) {
46 printf("pthread 1 create error\n");
47 exit(flag);
48 }
49 sleep(2);
50
51 flag = pthread_create(&tids[1],NULL,fun2,NULL);
52 if(flag != 0) {
53 printf("pthread 2 create error\n");
54 exit(flag);
55 }
56 sleep(5);
57 return 0;
58 }
线程1先执行,但是由于条件不满足,于是获取互斥锁并调用wait
线程2开始执行了,改变了条件使条件满足了,于是调用signal通知线程1,线程1得以继续运行
4.3 读写锁
(1)读写锁与互斥量
互斥量只有锁住状态和不加锁状态, 且一次只有一个线程可对其加锁
类似互斥量,但读写锁允许多个线程同是占有读模式的读写锁。只允许一个线程占有写模式的读写锁。(适合于对数据结构读比写次数多的情况)
读写锁有3种状态:读模式下加锁、写模式下加锁、不加锁
(2)特点
有线程在读,允许其它线程读,不允许写
有线程在写,不允许其它线程读、写
(3)
①初始化、销毁
②申请读锁
③申请写锁
④解锁
⑤带超时的加锁
获取锁失败时只阻塞设定的时间,而不是一直阻塞
4.4 信号量(信号量集)
<semaphore.h>
互斥量只允许一个线程进入临界区,而信号量可允许多个线程进入
4.5 自旋锁
当锁持有时间短,且不希望线程(阻塞-----恢复)这样花时间在重新调度上,可用自旋锁。线程自旋等待锁可用时,是独占CPU的