Skip to content

Latest commit

 

History

History
executable file
·
275 lines (187 loc) · 7.29 KB

线程同步.md

File metadata and controls

executable file
·
275 lines (187 loc) · 7.29 KB

线程同步

  • 线程的销毁

    • 自然死亡

      从线程函数返回,线程正常退出,被主线程调用pthread_join回收。

    • 自杀

      调用 pthread_exit 自杀。

    • 他杀

      调用 pthread_cancel 来终止某个线程。

    • 非正常死亡

      在线程的函数里面抛出异常,或者segfault的信号的时候。

  • 互斥锁

    下面是一个例子,3个线程如果不加锁,就会出现race condition。

    #include <iostream>
    #include <pthread.h>
    using namespace std;
    
    const int thread_num = 3;
    pthread_mutex_t mutex;
    
    int sum = 0;
    
    //注意,线程调用的函数的参数和返回值,必须是 void * arg 和 void * 
    void* thread_fun_with_lock(void * args)
    {
        // int ret = pthread_mutex_lock(&mutex);  //返回0为成功  
        // if(ret)  
        // {  
        //     printf("Thread %d lock failed\n",no);  
        //     pthread_exit(NULL);  
        // } 
        for(int i=0;i<1000000;i++)
        {
            pthread_mutex_lock(&mutex);  
            sum=sum+1;
            pthread_mutex_unlock(&mutex); 
        }
    }
    
    void* thread_fun(void * args)
    {
        for(int i=0;i<1000000;i++)  //100w
        { 
            sum=sum+1;
        }
    }
    
    
    int main()
    {
        pthread_t thread[thread_num];
        pthread_mutex_init(&mutex,NULL);
    
        for(int i=0;i<thread_num;i++)
        {
            pthread_create(&thread[i],NULL,thread_fun_with_lock,NULL);
        }
    
    
        for(int i=0;i<thread_num;i++)
        {
            pthread_join(thread[i],NULL);
        }
        pthread_mutex_destroy(&mutex);
    
        cout<<"sum : "<<sum<<endl;
        return 0;
    }
    
  • 读写锁

  • 条件变量

    条件变量适合的场景是wait和notify的场景。

    给一个条件变量的例子。

    #include <iostream>
    #include <queue>
    #include <cstdlib>
    
    #include <unistd.h>
    #include <pthread.h>
    
    using namespace std;
    
    //把共享数据和它们的同步变量集合到一个结构中,这往往是一个较好的编程技巧。
    struct{
        pthread_mutex_t mutex;
        pthread_cond_t cond;
        queue<int> product;
    }sharedData = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
    
    void * produce(void *ptr)
    {
        for (int i = 0; i < 10; ++i)
        {
            pthread_mutex_lock(&sharedData.mutex);
            sharedData.product.push(i);
            pthread_mutex_unlock(&sharedData.mutex);
    
            //如果队列里面有一个元素,就通知消费者线程去消耗
            if (sharedData.product.size() == 1)
                pthread_cond_signal(&sharedData.cond);
        }
    }
    
    void * consume(void *ptr)
    {
        for (int i = 0; i < 10;)
        {
            pthread_mutex_lock(&sharedData.mutex);
    
            //如果队列是空的,那么就等待
            while(sharedData.product.empty())
                pthread_cond_wait(&sharedData.cond, &sharedData.mutex);
            ++i;
            cout<<"consume:"<<sharedData.product.front()<<endl;
            sharedData.product.pop();
            pthread_mutex_unlock(&sharedData.mutex);
        }
    }
    
    int main()
    {
        pthread_t tid1, tid2;
    
        pthread_create(&tid1, NULL, consume, NULL);
        pthread_create(&tid2, NULL, produce, NULL);
    
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
    
        return 0;
    }
    
    
  • 信号量

    多线程同步的信号量和ipc用于进程同步的信号量的语义是不一样的。

    在陈硕的《linux多线程服务器编程》第二章里面,并不提倡使用这样方式进行线程同步。同时,也不提倡使用读写锁在同步。

  • 多线程环境

    • 可重入函数

      如果一个函数可以被多个线程调用,但是不发生race_condititon ,那么这个函数就是可重入的函数。

    • 线程和进程

      在一个线程的函数里面执行fork的操作(在linux的环境下),它只会复制当前的thread,而不会克隆其他的线程。

      这个时候,有一个很危险的场景。一个线程之后了fork之后,fork产生的子进程只有一个线程。如果其他线程正好拿到一个锁,而他突然消失,就再也没有机会解锁。就会造成死锁。

    • 线程和信号

      每个线程可以独立的设置信号的掩码。

      int pthread_sigmask( int how, const sigset_t * newmask, sigset_t * oldmask );

    个人的一个理解,在多线程的环境下 使用fork和信号,都是很危险的动作。

  • 线程池

进阶

  • 互斥锁的属性

    • PTHREAD_PROCESS_SHARED

      互斥锁可以被跨进程共享

    • PTHREAD_PROCESS_PRIVATE

      互斥锁只能在同一个进程下面的线程之间共享

  • 互斥锁的type类型

    看这个之前,思考一个问题,如果在一个函数里面lock之后,再lock一次,会出现什么情况?这个好像叫做锁的可重入性

    比如,如下的代码:

    void* test_re_lock(void * args)
    {
        //获得锁
        pthread_mutex_lock(&mutex); 
        //再一次获得锁
        pthread_mutex_lock(&mutex);  
        cout<<"test"<<endl;
    }
    

    因为linux下面默认的锁的类型就是普通锁,如果执行上面的代码,将会出现死锁的情况。

    Linux下面有4中类型的锁。

    • PTHREAD_PROCESS_NORMAL 普通锁,这个是默认的锁的类型。

      • 如果一个线程获得锁了之后,再一次执行获得锁的操作,那么就会导致死锁。

      • 如果一个线程获得了锁,其他线程请求锁,那么会形成一个阻塞队列。

      • 如果一个线程获得了锁,其他线程再次加锁/或是其他线程解锁,将导致不可预期的结果。

      • 如果对一个已经解锁的锁,再一次解锁,将会导致不可预期的操作。

    • PTHREAD_MUTEX_RECURSIVE 递归锁(或者是嵌套锁)

      这种锁允许一个线程在获得锁之后,再次申请锁,而不导致死锁。不过,当前拥有锁的线程,必须进行相应数量的解锁操作。

      将锁设置为递归锁的类型之后,就不会出现死锁的情况了。

      void* test_re_lock(void * args)
      {
          pthread_mutex_lock(&mutex); 
          pthread_mutex_lock(&mutex);  
          cout<<"test"<<endl;
      
          pthread_mutex_unlock(&mutex); 
          pthread_mutex_unlock(&mutex); 
      }
      
      int main()
      {
          pthread_t thread[thread_num];
          
          // 设置线程的属性 , 设置锁的类型为递归锁
          pthread_mutexattr_t attr;
          pthread_mutexattr_init(&attr);
          pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
      
          pthread_mutex_init(&mutex,&attr);
      
          for(int i=0;i<thread_num;i++)
          {
              pthread_create(&thread[i],NULL,test_re_lock,NULL);
          }
      
      
          for(int i=0;i<thread_num;i++)
          {
              pthread_join(thread[i],NULL);
          }
          pthread_mutex_destroy(&mutex);
      
          cout<<"sum : "<<sum<<endl;
          return 0;
      }
      
    • PTHREAD_MUTEX_ERRORLOCK 默认锁

    • PTHREAD_MUTEX_DEFAULT 检错锁