从来就没有救世主  也不靠神仙皇帝  要创造人类的幸福  全靠我们自己  

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的  

  

 

 

   

 

 

posted @ 2020-03-20 15:48  T,X  阅读(153)  评论(0编辑  收藏  举报