0
点赞
收藏
分享

微信扫一扫

【Linux】线程安全(看这一篇就够了)

千妈小语 2022-05-04 阅读 98

目录

线程安全

概念:

举例:

代码:

互斥:

什么是互斥:

互斥锁:

互斥锁的接口:

初始化:

加锁

 解锁:

销毁:

同步

样例引入

条件变量:

 条件变量原理:

条件变量的接口:

条件变量的夺命追问:

条件变量代码:


线程安全

概念:

举例:

线程不安全的本质就是对临界区的非原子性访问导致的。

代码:

互斥:

什么是互斥:

互斥锁:

原理:

互斥锁的计数器如何保证原子性?

具体过程:

互斥锁的接口:

初始化:

动态初始化:

静态初始化:

加锁

验证:

假设这两个线程是线程A和线程B,假设线程A先拿到互斥锁,并没有释放互斥锁,而加锁的接口具有阻塞属性,因此应该会有如下现象:

程序不会退出,处于阻塞状态;整个工作都是由一个线程完成的,另一个线程并没有参与其中。

 解锁:

修改上面的代码:

注意:在线程都有可能退出的地方都进行解锁,否则就有可能导致死锁。 

销毁:

完整代码: 

   2 #include<pthread.h>
    3 #include<stdio.h>
    4 #include<unistd.h>
    5 
    6 int g_t = 10;
    7 pthread_mutex_t g_lock; //互斥锁
    8 
W>  9 void* thread_start(void* arg){
   10     //修改全局变量
   11     while(1){
   12         sleep(1); //让程序结果更加明确
   13         //加锁
   14         pthread_mutex_lock(&g_lock);
   15         if(g_t <= 0){
   16             pthread_mutex_unlock(&g_lock);
   17             break;
   18         }
W> 19         printf("I am %p, i got value is %d\n", pthread_self(), g_t);
   20         g_t--;
   21         pthread_mutex_unlock(&g_lock);
   22     }
W> 23 }
   24 int main(){
   25     //初始化互斥锁
   26     pthread_mutex_init(&g_lock,NULL);
   27     //创建两个线程
   28     pthread_t tid[2];
   29     for(int i = 0; i < 2; i++){
   30         int ret = pthread_create(&tid[i], NULL, thread_start, NULL);
   31         if(ret < 0){
   32             perror("pthread_create");
   33             return 0;
   34         }
   35     }
   36     //主线程进行线程等待
   37     for(int i = 0; i < 2; ++i){
   38         pthread_join(tid[i], NULL);
          }
   40     //销毁互斥锁
   41     pthread_mutex_destroy(&g_lock);
   42     return 0;
   43 }     

同步

样例引入

现在有如下场景:

    1 #include<stdio.h>
    2 #include<pthread.h>
    3 #include<unistd.h>                                                                                                                                                                                            
    4 
    5 #define THREAD_COUNT 1
    6 
    7 int g_bowl = 0;  //0没有面,1有面
    8 pthread_mutex_t g_lock;
    9 
W> 10 void* eat_thread(void* arg){
   11     while(1){
   12         pthread_mutex_lock(&g_lock);
   13         printf("I am eat thread, eat %d\n", g_bowl--);
   14         pthread_mutex_unlock(&g_lock);
   15         usleep(1);
   16     }
   17 }
W> 18 void* make_thread(void* arg){
   19     while(1){
   20         pthread_mutex_lock(&g_lock);
   21         printf("I am make thread, make %d\n", g_bowl++);
   22         pthread_mutex_unlock(&g_lock);
   23         usleep(1);
   24     }
   25 }
   26 int main(){
   27     //初始化互斥锁
   28     pthread_mutex_init(&g_lock, NULL);
   29     pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
   30     //创建吃面和做面线程
   31     for(int i = 0; i < THREAD_COUNT; ++i){
   32         int ret = pthread_create(&eat[i], NULL, make_thread, NULL);
   33         if(ret < 0){
   34             perror("pthread_creat");
   35             return 0;
   36         }
   37         ret = pthread_create(&make[i], NULL, eat_thread, NULL);
   38         if(ret < 0){
   39             perror("pthread_creat");
   40             return 0;
   41         }
   42     }
   43     //主线程等待工作线程
   44     for(int i = 0; i < THREAD_COUNT; ++i){
   45         pthread_join(eat[i], NULL);
   46         pthread_join(make[i], NULL);
   47     }
   48     //销毁互斥锁
   49     pthread_mutex_destroy(&g_lock);
   50     return 0;
   51 }

 

可以看到现在的程序访问碗这个临界资源是不合理的,因为我们只有一个碗,应该做一碗吃一碗。

结论:

应该如何实现同步呢?我们可以通过判断的方式解决。

  

结果貌似符合要求,但是存在问题,将代码改一下:

要解决这个问题,我们就需要了解条件变量,使用条件变量接口来解决。

条件变量:

 条件变量原理:

条件变量的接口:

 1、初始化接口

动态初始化:

静态初始化:

2、等待接口(哪个线程调用就将哪个线程放到条件变量对应的PCB等待队列中)

 3、唤醒接口

4、销毁接口

   1 #include<stdio.h>
    2 #include<pthread.h>
    3 #include<unistd.h>
    4 
    5 #define THREAD_COUNT 1
    6                                                                                                                                                                                                                                                                                                                                                                                    
    7 int g_bowl = 0;  //0没有面,1有面
    8 pthread_mutex_t g_lock; //互斥锁
    9 pthread_cond_t g_cond; //条件变量
   10 
W> 11 void* eat_thread(void* arg){
   12     while(1){
   13         pthread_mutex_lock(&g_lock);
   14         if(g_bowl == 0){
   15             printf("我是吃面人,碗里面没面我就不吃了\n");
   16             pthread_cond_wait(&g_cond, &g_lock);
   17             //pthread_mutex_unlock(&g_lock);
   18             //continue;
   19         }
   20         printf("I am eat thread, eat %d\n", g_bowl--);
   21         //解锁
   22         pthread_mutex_unlock(&g_lock);
   23         //通知做面线程
   24         pthread_cond_signal(&g_cond);
   25        // usleep(1);
   26     }
   27 }
W> 28 void* make_thread(void* arg){
   29     while(1){
   30         pthread_mutex_lock(&g_lock);
   31         if(g_bowl == 1){
   32             printf("我是做面人,碗里面有面,我就不做了。。。\n");
   33             pthread_cond_wait(&g_cond, &g_lock);
   34             //pthread_mutex_unlock(&g_lock);
   35             //continue;
   36         }
   37         printf("I am make thread, make %d\n", g_bowl++);
   38         //解锁
   39         pthread_mutex_unlock(&g_lock);
   40         //通知吃面线程
   41         pthread_cond_signal(&g_cond);
   42         //usleep(1);
   43     }
   44 }
   45 int main(){
   46     //初始化互斥锁
   47     pthread_mutex_init(&g_lock, NULL);
   48     //初始化条件变量
   49     pthread_cond_init(&g_cond,  NULL);
   50 
   51     pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
   52     //创建吃面和做面线程
   53     for(int i = 0; i < THREAD_COUNT; ++i){
   54         int ret = pthread_create(&eat[i], NULL, make_thread, NULL);
   55         if(ret < 0){
   56             perror("pthread_creat");
   57             return 0;
   58         }
   59         ret = pthread_create(&make[i], NULL, eat_thread, NULL);
   60         if(ret < 0){
   61             perror("pthread_creat");
   62             return 0;
   63         }
   64     }
   65     //主线程等待工作线程
   66     for(int i = 0; i < THREAD_COUNT; ++i){
   67         pthread_join(eat[i], NULL);
   68         pthread_join(make[i], NULL);
   69     }
   70     //销毁互斥锁
   71     pthread_mutex_destroy(&g_lock);
   72     //销毁条件变量
   73     pthread_cond_destroy(&g_cond);
   74     return 0;
   75 }

对于多个吃面,结果又是如何呢?

可以看到当多个吃面线程和做面线程的时候,又出现了临界区资源访问不合理的现象。要想弄清楚这里的原由,我们需要先搞清楚pthread_cond_wait这个接口都做了些什么。

条件变量的夺命追问:

1、条件变量的等待接口第二个参数为什么会有互斥锁?

 为什么要解锁呢?以一个吃面线程和一个做面线程为例:

2、pthread_cond_wait的内部针对互斥锁做了什么操作?先释放互斥锁还是先将线程放到PCB等待队列?

3、线程被唤醒之后会执行什么代码?

条件变量代码:

 现在来分析为什么吃面线程和做面线程从1变为2后,程序就出错了:

假设有两个吃面线程A和B,两个做面线程C和D

下面的场景是有可能存在的:

解决方法:只需要将线程入口函数处的条件判断改为while,在等待接口退出的时候就会循环上去判断临界资源是否可用,进而保证临界资源访问的合理性:

用pstack查看以下线程的状态:

 

可以看到工作线程都处在了PCB等待队列中。

原因是:在线程通知PCB等待队列中的线程的时候,将同种类的线程通知出来了,然后判断临界资源是不可用状态,因此刚被通知出来的线程什么也没做,就又进入PCB等待队列中了。

如何解决:

只需要将吃面线程和做面线程分开就行。这样吃面线程通知的永远是做面线程,做面线程统治的永远是吃面线程。

代码:

    1 #include<stdio.h>                                                                                                                                                                                                                                                                                                                                                                  
    2 #include<pthread.h>
    3 #include<unistd.h>
    4 
    5 #define THREAD_COUNT 2
    6 
    7 int g_bowl = 0;  //0没有面,1有面
    8 pthread_mutex_t g_lock; //互斥锁
    9 pthread_cond_t g_eat_cond; //条件变量
   10 pthread_cond_t g_make_cond;
   11 
W> 12 void* eat_thread(void* arg){
   13     while(1){
   14         pthread_mutex_lock(&g_lock);
   15         while(g_bowl == 0){
   16             printf("我是吃面人,碗里面没面我就不吃了\n");
   17             pthread_cond_wait(&g_eat_cond, &g_lock);
   18             //pthread_mutex_unlock(&g_lock);
   19             //continue;
   20         }
   21         printf("I am eat thread, eat %d\n", g_bowl--);
   22         //解锁
   23         pthread_mutex_unlock(&g_lock);
   24         //通知做面线程
   25         pthread_cond_signal(&g_make_cond);
   26        // usleep(1);
   27     }
   28 }
W> 29 void* make_thread(void* arg){
   30     while(1){
   31         pthread_mutex_lock(&g_lock);
   32         while(g_bowl == 1){
   33             printf("我是做面人,碗里面有面,我就不做了。。。\n");
   34             pthread_cond_wait(&g_make_cond, &g_lock);
   35             //pthread_mutex_unlock(&g_lock);
   36             //continue;
   37         }
   38         printf("I am make thread, make %d\n", g_bowl++);
   39         //解锁
   40         pthread_mutex_unlock(&g_lock);
   41         //通知吃面线程
   42         pthread_cond_signal(&g_eat_cond);
   43         //usleep(1);
   44     }
   45 }
   46 int main(){
   47     //初始化互斥锁
   48     pthread_mutex_init(&g_lock, NULL);
   49     //初始化条件变量
   50     pthread_cond_init(&g_eat_cond,  NULL);
   51     pthread_cond_init(&g_make_cond, NULL);
   52 
   53     pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
   54     //创建吃面和做面线程
   55     for(int i = 0; i < THREAD_COUNT; ++i){
   56         int ret = pthread_create(&eat[i], NULL, make_thread, NULL);
   57         if(ret < 0){
   58             perror("pthread_creat");
   59             return 0;
   60         }
   61         ret = pthread_create(&make[i], NULL, eat_thread, NULL);
   62         if(ret < 0){
   63             perror("pthread_creat");
   64             return 0;
   65         }
   66     }
   67     //主线程等待工作线程
   68     for(int i = 0; i < THREAD_COUNT; ++i){
   69         pthread_join(eat[i], NULL);
   70         pthread_join(make[i], NULL);
   71     }
   72     //销毁互斥锁
   73     pthread_mutex_destroy(&g_lock);
   74     //销毁条件变量
   75     pthread_cond_destroy(&g_eat_cond);
   76     pthread_cond_destroy(&g_make_cond);
   77     return 0;
                     

 

举报

相关推荐

0 条评论