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的