ToB企服应用市场:ToB评测及商务社交产业平台

标题: Linux线程-互斥与同步 [打印本页]

作者: 钜形不锈钢水箱    时间: 2022-6-26 12:25
标题: Linux线程-互斥与同步
Linux线程-互斥与同步



零、前言

   本章主要讲解学习Linux中对多线程的执行中的同步与互斥
  一、Linux线程互斥

1、基本概念及引入


     
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<pthread.h>
  4. int thickets=100;//100张票
  5. //thickets--表示抢票
  6. void* Routine(void* arg)
  7. {
  8.     while(1)
  9.     {
  10.         if(thickets>0)
  11.         {
  12.             usleep(30000);//抢票时间
  13.             printf("%s get a thickets, now thickets' number:%d\n",(char*)arg,--thickets);
  14.         }
  15.         else
  16.             break;
  17.     }
  18.     return NULL;
  19. }
  20. int main()
  21. {
  22.     pthread_t tid1,tid2,tid3;
  23.     pthread_create(&tid1,NULL,Routine,(void*)"thread 1");
  24.     pthread_create(&tid2,NULL,Routine,(void*)"thread 2");
  25.     pthread_create(&tid3,NULL,Routine,(void*)"thread 3");
  26.     pthread_join(tid1,NULL);
  27.     pthread_join(tid2,NULL);
  28.     pthread_join(tid3,NULL);
  29.     return 0;
  30. }
复制代码

   注:变量tickets被多个执行流同时访问,所以thickets就是一个临界资源,当访问临界资源时,判断tickets是否大于0、打印剩余票数以及--tickets的代码也就是临界区
  
   if语句判断条件为真以后,代码可以并发的切换到其他线程
  usleep用于模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  –ticket操作本身就不是一个原子操作,可能在执行当中也被切换成其他线程
  
   当thickets为1时,一个线程进行if判断为真,进入代码段,当执行到usleep进行系统调用休眠,返回时到用户态时线程发生切换,多个线程此时也进行if判断为真(thickets还是1),这些线程当进行打印的时候进行了多次的减减操作,也就造成了负数的情况
  
     
  1. 152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>
  2. 153 400651: 83 e8 01 sub $0x1,%eax
  3. 154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>
复制代码
  注:因为减减操作并不是原子的,当减减操作第一步执行完(thickets=100),可能该线程的时间片到了(寄存器中的数据被保存eax=100),其他线程切入,而切入的线程执行了多次减减并写会到内存(thickets=80),当切出的线程切回时,恢复线程上下文数据(eax=100),再进行减减(eax=99),把数据写回到内存时(thickets=99),此时的数据的值只达到了一次减减的效果,此时的资源并不安全
  2、互斥量mutex介绍


     
     注:要做到这三点,本质上就是需要一把锁,Linux上提供的这把锁叫互斥量
  
3、互斥量的使用


  1. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
复制代码
  1. int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrictattr);
复制代码
  参数:mutex:要初始化的互斥量;attr:互斥量的属性,一般设置为NULL
  
  1. int pthread_mutex_destroy(pthread_mutex_t *mutex);
复制代码

     
  1. int pthread_mutex_lock(pthread_mutex_t *mutex);
  2. int pthread_mutex_unlock(pthread_mutex_t *mutex);
复制代码
  返回值:成功返回0,失败返回错误号
  
     
  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<pthread.h>
  4. int thickets=100;//100张票
  5. //thickets--表示抢票
  6. pthread_mutex_t lock;//线程共用一个互斥锁
  7. void* Routine(void* arg)
  8. {
  9.     while(1)
  10.     {
  11.         pthread_mutex_lock(&lock);
  12.         if(thickets>0)
  13.         {
  14.             usleep(100000);//抢票时间
  15.             printf("%s get a thickets, now thickets' number:%d\n",(char*)arg,--thickets);
  16.             pthread_mutex_unlock(&lock);
  17.         }
  18.         else
  19.         {
  20.             pthread_mutex_unlock(&lock);
  21.             break;
  22.         }
  23.         usleep(100000);
  24.     }
  25.     return NULL;
  26. }
  27. int main()
  28. {
  29.     pthread_mutex_init(&lock,NULL);
  30.     pthread_t tid1,tid2,tid3;
  31.     pthread_create(&tid1,NULL,Routine,(void*)"thread 1");
  32.     pthread_create(&tid2,NULL,Routine,(void*)"thread 2");
  33.     pthread_create(&tid3,NULL,Routine,(void*)"thread 3");
  34.     pthread_join(tid1,NULL);
  35.     pthread_join(tid2,NULL);
  36.     pthread_join(tid3,NULL);
  37.     pthread_mutex_destroy(&lock);
  38.     return 0;
  39. }
复制代码

4、互斥量原理


     
   注:在交换和赋值的过程中本质就是让竞争的多线程中保证中有一个线程的交换得到的寄存器数据为1,即保证同一时刻只有一个竞争的线程为1,由此才能往下执行,否则只能进行等待
  二、可重入/线程安全

1、基本概念


     
     
     2、线程安全


     
     3、重入函数


     
     4、联系与区别


     
     三、常见锁概念


   死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态
  
     注:对于死锁,四个条件缺一不可
  
     
     四、Linux线程同步

1、基本概念


     
     
   当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中
  2、条件变量的使用


  1. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
复制代码

  1. int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);
复制代码

     
  1. int pthread_cond_destroy(pthread_cond_t *cond)
复制代码

     
  1. int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
复制代码

     
  1. int pthread_cond_broadcast(pthread_cond_t *cond);
  2. int pthread_cond_signal(pthread_cond_t *cond);
复制代码

     
  1. #include<stdio.h>
  2. #include<pthread.h>
  3. #include<unistd.h>
  4. pthread_cond_t cond;
  5. pthread_mutex_t mutex;
  6. void* Routine1(void* arg)
  7. {
  8.     //被调度线程执行
  9.     while(1)
  10.     {
  11.         pthread_cond_wait(&cond,&mutex);
  12.         printf("%s is running...\n",(char*)arg);
  13.     }
  14. }
  15. void* Routine2(void* arg)
  16. {
  17.     int cnt=0;
  18.     while(1)
  19.     {
  20.         if(cnt%3!=0)
  21.             pthread_cond_signal(&cond);
  22.         else
  23.             pthread_cond_broadcast(&cond);
  24.         cnt++;
  25.         sleep(1);
  26.     }
  27. }
  28. int main()
  29. {
  30.     pthread_cond_init(&cond,NULL);
  31.     pthread_mutex_init(&mutex,NULL);
  32.     pthread_t tid1,tid2,tid3,tid4;
  33.     pthread_create(&tid1,NULL,Routine1,(void*)"thread 1");
  34.     pthread_create(&tid2,NULL,Routine1,(void*)"thread 2");
  35.     pthread_create(&tid3,NULL,Routine1,(void*)"thread 3");
  36.     pthread_create(&tid4,NULL,Routine2,(void*)"thread 4");
  37.     pthread_join(tid1,NULL);
  38.     pthread_join(tid2,NULL);
  39.     pthread_join(tid3,NULL);
  40.     pthread_join(tid4,NULL);
  41.    
  42.     pthread_mutex_destroy(&mutex);
  43.     pthread_cond_destroy(&cond);
  44.     return 0;
  45. }
复制代码

3、条件变量等待


     
   注:如果不释放互斥锁,那么其他线程无法成功申请到锁进而改变数据,也就没有办法通知等待的线程,那么申请到锁的线程一直等待,别的线程无法获取锁也无法通知,也就会造成死锁
  
  1. pthread_mutex_lock(&mutex);
  2. while (condition_is_false)
  3. {
  4.     pthread_mutex_unlock(&mutex);
  5.     //解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
  6.     pthread_cond_wait(&cond);
  7.     pthread_mutex_lock(&mutex);
  8. }
  9. pthread_mutex_unlock(&mutex);
复制代码

     4、条件变量使用规范

  1. pthread_mutex_lock(&mutex);
  2. while (条件为假){
  3.     pthread_cond_wait(cond, mutex);
  4. }
  5. //修改条件
  6. pthread_mutex_unlock(&mutex);
复制代码
  注:这里可能存在被伪唤醒的情况,当唤醒的时候可能竞争锁失败,继续等待,其他线程竞争成功执行后并释放锁,此时条件判断为假,但是该线程竞争到锁后会继续往下执行,如果没有再次进行判断可能造成错误,使用while循环判断保证醒来后条件一定为真才往下走
  
  1. pthread_mutex_lock(&mutex);
  2. //设置条件为真
  3. pthread_cond_signal(cond);
  4. pthread_mutex_unlock(&mutex);
复制代码
五、POSIX信号量

1、信号量概念及介绍


     
     
     
     2、信号量的使用


  1. #include <semaphore.h>
  2. int sem_init(sem_t *sem, int pshared, unsigned int value);
复制代码

     
  1. int sem_destroy(sem_t *sem);
复制代码

   参数:sem:需要销毁的信号量
  返回值:销毁信号量成功返回0,失败返回-1
  
  1. int sem_wait(sem_t *sem); //P()
复制代码

     
  1. int sem_post(sem_t *sem);//V()
复制代码

     
[code]#include #include #include #include #include class Sem{public:        Sem(int num)        {                sem_init(&_sem, 0, num);        }        ~Sem()        {                sem_destroy(&_sem);        }        void P()        {                sem_wait(&_sem);        }        void V()        {                sem_post(&_sem);        }private:        sem_t _sem;};Sem sem(1); //二元信号量int tickets = 2000;void* TicketGrabbing(void* arg){        std::string name = (char*)arg;        while (true){                sem.P();                if (tickets > 0){                        //usleep(3000);                        std::cout




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4