互斥锁、自旋锁、读写锁、条件变量、信号量

  1. 互斥锁
    加锁 -> 阻塞(睡眠等待sleep)-> 解锁。
    阻塞时会进行上下文切换,CPU可进行其他工作。
    函数原型:

    #include 
    #include 
    // 初始化一个互斥锁。
    int pthread_mutex_init(pthread_mutex_t *mutex, 
    					   const pthread_mutexattr_t *attr);
    
    // 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,
    // 直到互斥锁解锁后再上锁。
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    
    // 调用该函数时,若互斥锁未加锁,则上锁,返回 0;
    // 若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    
    // 当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock 互斥量
    // 原语允许绑定线程阻塞时间。即非阻塞加锁互斥量。
    int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
    							const struct timespec *restrict abs_timeout);
    
    // 对指定的互斥锁解锁。
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    // 销毁指定的一个互斥锁。互斥锁在使用完毕后,
    // 必须要对互斥锁进行销毁,以释放资源。
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

    使用例程:

    //使用互斥量解决多线程抢占资源的问题
    #include 
    #include 
    #include 
    #include 
    #include 
     
    char* buf[10]; //字符指针数组  全局变量
    int pos; //用于指定上面数组的下标
     
    //1.定义互斥量
    pthread_mutex_t mutex;
     
    void *task(void *p)
    {
        //3.使用互斥量进行加锁
        pthread_mutex_lock(&mutex);
     
        buf[pos] = (char *)p;
        sleep(1);
        pos++;
     
        //4.使用互斥量进行解锁
        pthread_mutex_unlock(&mutex);
    }
     
    int main(void)
    {
        //初始化互斥量, 默认属性
        pthread_mutex_init(&mutex, NULL);
     
        //启动两个线程 轮流向数组中存储内容
        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, task, (void *)"hello");
        pthread_create(&tid2, NULL, task, (void *)"world");
        
        //主线程进程等待,并且打印最终的结果
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
     
        //销毁互斥量
        pthread_mutex_destroy(&mutex);
    
        return 0;
    }
    
  2. 自旋锁
    加锁 -> 阻塞(循环检测等待running)-> 解锁
    自旋锁与互斥量功能一样,唯一不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待直到获得锁,减少了线程从睡眠到唤醒的资源消耗。
    自旋锁在用户态使用的比较少,在内核使用的比较多,使用场景:资源的锁被持有的时间短或者说小于2次上下文切换的时间,而又不希望在线程的唤醒上花费太多资源的情况。
    自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_xxx()中mutex换成spin,如:pthread_spin_init()。

  3. 读写锁
    只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因(允许多个线程读但只允许一个线程写)。
    适合资源的读操作远多于写操作的情况。
    1)多个读者可以同时进行读
    2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
    3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
    函数原型:

    #include 
    // 初始化读写锁
    int pthread_rwlock_init(pthread_rwlock_t *rwlock, 
    						const pthread_rwlockattr_t *attr); 
    
    // 申请读锁
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock ); 
    
    // 申请写锁
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock ); 
    
    // 尝试以非阻塞的方式来在读写锁上获取写锁,
    // 如果有任何的读者或写者持有该锁,则立即失败返回。
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 
    
    // 解锁
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 
    
    // 销毁读写锁
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

    使用例程:

    // 一个使用读写锁来实现 3 个线程读写一段数据是实例。
    // 在此示例程序中,共创建了 3 个线程,
    // 其中一个线程用来写入数据,两个线程用来读取数据
    #include   
    #include   
    #include   
    
    pthread_rwlock_t rwlock; //读写锁  
    int num = 1;  
      
    //读操作,其他线程允许读操作,却不允许写操作  
    void *fun1(void *arg)  
    {  
        while(1)  
        {  
            pthread_rwlock_rdlock(&rwlock);
            printf("read num first == %d\n", num);
            pthread_rwlock_unlock(&rwlock);
            sleep(1);
        }
    }
      
    //读操作,其他线程允许读操作,却不允许写操作  
    void *fun2(void *arg)
    {
        while(1)
        {
            pthread_rwlock_rdlock(&rwlock);
            printf("read num second == %d\n", num);
            pthread_rwlock_unlock(&rwlock);
            sleep(2);
        }
    }
     
    //写操作,其它线程都不允许读或写操作  
    void *fun3(void *arg)
    {
        while(1)
        {
            pthread_rwlock_wrlock(&rwlock);
            num++;
            printf("write thread first\n");
            pthread_rwlock_unlock(&rwlock);
            sleep(2);
        }
    }
      
    int main()  
    {  
        pthread_t ptd1, ptd2, ptd3;  
          
        pthread_rwlock_init(&rwlock, NULL);//初始化一个读写锁  
          
        //创建线程  
        pthread_create(&ptd1, NULL, fun1, NULL);  
        pthread_create(&ptd2, NULL, fun2, NULL);  
        pthread_create(&ptd3, NULL, fun3, NULL);   
          
        //等待线程结束,回收其资源  
        pthread_join(ptd1, NULL);  
        pthread_join(ptd2, NULL);  
        pthread_join(ptd3, NULL);  
          
        pthread_rwlock_destroy(&rwlock);//销毁读写锁  
          
        return 0;  
    }  
    
  4. 条件变量
    条件变量用来自动阻塞一个线程,直到某特殊情况发生,通常条件变量和互斥锁同时使用。
    条件变量使我们可以睡眠等待某条件出现。
    条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
    1)一个线程因 “条件不成立” 而挂起等待
    2)另一个线程使 “条件成立”, 并发出信号

    函数原型:

    #include 
    #include 
    #include 
    #include 
     
    int travelercount = 0;
    pthread_cond_t taxicond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t taximutex = PTHREAD_MUTEX_INITIALIZER;
     
    void *traveler_arrive(void *name)
    {
        char *p = (char *)name;
     
        pthread_mutex_lock(&taximutex);
     
        printf ("traveler: %s need a taxi now!\n", p);
        travelercount++;
        pthread_cond_wait(&taxicond, &taximutex);
                
        pthread_mutex_unlock(&taximutex);
        printf ("traveler: %s now got a taxi!\n", p);
        pthread_exit(NULL);
    }
     
    void *taxi_arrive(void *name)
    {
        char *p = (char *)name;
        printf ("Taxi: %s arrives.\n", p);
        for(;;)
        {
            if(travelercount)
            {
                pthread_cond_signal(&taxicond);
                travelercount--;
                break;
            }
        }
        pthread_exit(NULL);
    }
     
    int main (int argc, char **argv)
    {
        pthread_t thread;
        pthread_attr_t threadattr;
        pthread_attr_init(&threadattr);
     
        pthread_create(&thread, &threadattr, taxi_arrive,"taxi1");
        sleep(1);
        pthread_create(&thread, &threadattr, traveler_arrive,"traveler");
        sleep(3);
        pthread_create(&thread, &threadattr, taxi_arrive,"taxi2");
        sleep(4);
     
        return 0;
    }
    
  5. 信号量
    信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
    信号量适合用于上锁时间长的情况。
    当数量等于1 时称为二值信号量,当数量大于1时称为计数信号量。
    当信号量值大于 0 时,则可以访问,否则将阻塞进入等待队列。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
    函数原型:

    #include 
    // 初始化信号量
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    // 信号量 P 操作(减 1)
    int sem_wait(sem_t *sem);
    
    // 以非阻塞的方式来对信号量进行减 1 操作
    int sem_trywait(sem_t *sem);
    
    // 信号量 V 操作(加 1)
    int sem_post(sem_t *sem);
    
    // 获取信号量的值
    int sem_getvalue(sem_t *sem, int *sval);
    
    // 销毁信号量
    int sem_destroy(sem_t *sem);
    

    同步例程:

    // 信号量用于同步实例
    #include 
    #include 
    #include 
    #include 
     
    sem_t sem_g,sem_p;   //定义两个信号量
    char ch = 'a';
     
    void *pthread_g(void *arg)  //此线程改变字符ch的值
    {
        while(1)
        {
            sem_wait(&sem_g);
            ch++;
            sleep(1);
            sem_post(&sem_p);
        }
    }
     
    void *pthread_p(void *arg)  //此线程打印ch的值
    {
        while(1)
        {
            sem_wait(&sem_p);
            printf("%c",ch);
            sem_post(&sem_g);
        }
    }
     
    int main(int argc, char *argv[])
    {
        pthread_t tid1,tid2;
        sem_init(&sem_g, 0, 0); // 初始化信号量为0
        sem_init(&sem_p, 0, 1); // 初始化信号量为1
        
        // 创建两个线程
        pthread_create(&tid1, NULL, pthread_g, NULL);
        pthread_create(&tid2, NULL, pthread_p, NULL);
        
        // 回收线程
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        
        return 0;
    }
    

    互斥例程:

    // 信号量用于互斥实例
    #include 
    #include 
    #include 
    #include 
     
    sem_t sem; //信号量
     
    void printer(char *str)
    {
        sem_wait(&sem);//减一,p操作
        while(*str) // 输出字符串(如果不用互斥,此处可能会被其他线程入侵)
        {
            putchar(*str);  
            str++;
            sleep(1);
        }
        sem_post(&sem);//加一,v操作
    }
     
    void *thread_fun1(void *arg)
    {
        char *str1 = "hello";
        printer(str1);
    }
     
    void *thread_fun2(void *arg)
    {
        char *str2 = "world";
        printer(str2);
    }
     
    int main(void)
    {
        pthread_t tid1, tid2;
        
        sem_init(&sem, 0, 1); //初始化信号量,初始值为 1
        
        //创建 2 个线程
        pthread_create(&tid1, NULL, thread_fun1, NULL);
        pthread_create(&tid2, NULL, thread_fun2, NULL);
        
        //等待线程结束,回收其资源
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL); 
        
        sem_destroy(&sem); //销毁信号量
        
        return 0;
    }
    

避免死锁:
1.使用嵌套的锁时必须保证以相同的顺序获取锁,最好记录下锁的顺序以便其他人也能照此顺序使用。
2.防止发生饥饿,要试问该情况若不发生,是否一直等待下去?
3.不要重复请求同一个锁
4.加锁方案应该应当力求设计的简单

锁使用场景:
互斥体包含计数信号量和互斥锁。除非互斥锁某个条件约束妨碍你使用,否则优先考虑使用互斥锁而不是信号量,一般只有很底层的代码需要用到信号量。
自旋锁:中断上下文加锁、低开销加锁、短期加锁
互斥体:长期加锁、持有锁需要睡眠

你可能感兴趣的:(linux)