Linux : 线程的同步与互斥(模拟买票系统)

线程的同步与互斥

进程线程间的互斥相关背景概念

【临界资源】:多线程执行流共享的资源就叫做临界资源
【临界区】:每个线程内部,访问临界自娱的代码,就叫做临界区
【互斥】:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
【原子性】:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互
  • 多个线程并发的操作共享变量,会带来一些问题(看下面的代码,思考为什么会出现这种问题呢?)
      //模拟实现一个购票系统,发现现象
 #include 
 #include 
 #include 
      
 int tickets = 1000;
 void *BuyTicket(void *arg)
 {    
     for(;;){
         if( tickets > 0 ){                                                           
             usleep(1000);
             std::cout << "Get ticket success :" << tickets--   << std::endl;
         }else{
             break;
         }
     }
     std::cout << "thread" << pthread_self << "quit..." << std::endl;
 }    
          
 int main()
 {    
     int i = 0;
     pthread_t tid[5];
     for(i = 0; i < 5; i++);
     //for(auto i = 0; i < 5; i++){
          pthread_create(tid+i, NULL, BuyTicket, NULL);
      }
      for( i = 0; i < 5; i++){
          pthread_join(tid[i], NULL);
      }
      return 0;
  }                                      

运行结果图 :
Linux : 线程的同步与互斥(模拟买票系统)_第1张图片
出现这种现象主要是因为 :

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟业务的过程中,可能有很多个线程进入该代码段
  • ticket-- 操作本身就不是一个原子操作

要解决以上问题,需要做到以下三点:
1> 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区
2> 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
3> 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点,本质上就是需要一把锁,Linux 里把提供的这把锁叫互斥量

Linux : 线程的同步与互斥(模拟买票系统)_第2张图片

互斥量的接口
  • 初始化互斥量

方法1 : 静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

方法2 : 动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
                       const pthread_mutexattr_t*restrict attr);

参数
mutex ——> 要初始化的互斥量
attr ——> NULL

  • 销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t *mutex) ;

销毁互斥量需要注意:
– 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
– 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

  • 互斥量加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex)

返回值 : 成功返回0,失败返回错误号
调用 pthread_ lock 时,可能会遇到以下情况 :
– 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
– 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么 pthread_ lock 调用会陷入阻塞(执行流被挂起),等待互斥量解锁

优化上面的代码:

   #include                                                              
   #include 
   #include 
             
   int tickets = 1000;
   pthread_mutex_t lock;
             
   void *BuyTicket(void *arg)
   {         
      for(;;){
          pthread_mutex_lock(&lock);     //加锁
          if(tickets>0){
              usleep(1000);
              std::cout << "Get ticket success :" << tickets--   << std::endl;
          }else{
              pthread_mutex_unlock(&lock); //解锁(“锁”放到循环里面的原因:防止一个进程独占临界资源的现象(一个人抢完所有的票))
              break;
          } 
           pthread_mutex_unlock(&lock);    //解锁
      }     
      std::cout << "thread" << pthread_self << "quit..." << std::endl;
  }         
                     
  int main()
  {        
      pthread_mutex_init(&lock, NULL);
      int i = 0;
      pthread_t tid[5];
      for(i = 0; i < 5; i++){
           
      //for(auto i = 0; i < 5; i++){    //C++11 应用
         pthread_create(tid+i, NULL, BuyTicket, NULL);
      }    
      for( i = 0; i < 5; i++){
          pthread_join(tid[i], NULL);
      }    
      pthread_mutex_destroy(&lock);
      return 0;
  }

Linux : 线程的同步与互斥(模拟买票系统)_第3张图片
综上 :加锁之后,会使性能降低(加锁之后,智能串行运行),但是增强了安全性

互斥量实现原理探究

  • 单纯的i++或者++i都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期
    Linux : 线程的同步与互斥(模拟买票系统)_第4张图片

你可能感兴趣的:(Linux)