【Linux】多线程与线程同步

88 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

线程同步

下面的函数都是成功返回0,失败返回错误号。

1. mutext

  • 初始化

         #include <pthread.h>
     ​
         int pthread_mutex_destroy(pthread_mutex_t *mutex);
         int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                                const pthread_mutexattr_t *restrict attr);
         pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    

    互斥量初始化后是1。

  • lock/unlock

         #include <pthread.h>
     ​
         int pthread_mutex_lock(pthread_mutex_t *mutex);     // 堵塞
         int pthread_mutex_trylock(pthread_mutex_t *mutex);  // 不堵塞
         int pthread_mutex_unlock(pthread_mutex_t *mutex);
    

    注意这是锁是为了保护共享数据区域,应该使得锁的粒度越小越好。 比如:

         while(1) {
             pthread_mutex_lock(&mutex);
             printf("h ");       // 1
             sleep(rand() % 3);
             printf("w.\n");     // 2
             pthread_mutex_unlock(&mutex); 
             sleep(rand() % 3);
         }
    

    上面的锁是为了保护stdout。unlock应该放在第二个print的位置,而不是放在最后一个sleep的后面。

  • 死锁

    • 同一个线程试图对同一个互斥量加锁两次,即两次调用pthread_mutex_lock,而中间没有调用pthread_mutex_unlock

    • 线程1拥有A锁,请求B锁,线程2拥有B锁,请求A锁

      • pthread_mutex_trylock:当拿不到锁时,就会放弃所有的锁,就不会有死锁。

2. 读写锁

  • 写独占,读共享,写锁优先级更高。相比较互斥量锁,效率更高,因为读时可以共享。

  • 函数

         int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                                 const pthread_rwlockattr_t *restrict attr);
         
         int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
         int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
     ​
         int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
         int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
     ​
         int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
         int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

3. 条件变量

  • 条件变量不是锁,但它可以造成线程堵塞。通常和mutex配合使用,给多线程提供一个会和的场所。

  • 函数

         #include <pthread.h>
     ​
        int pthread_cond_destroy(pthread_cond_t *cond);
        int pthread_cond_init(pthread_cond_t *restrict cond,
                              const pthread_condattr_t *restrict attr);
     ​
         int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                                    pthread_mutex_t *restrict mutex,
                                    const struct timespec *restrict abstime);
        int pthread_cond_wait(pthread_cond_t *restrict cond,
                              pthread_mutex_t *restrict mutex);
     ​
        int pthread_cond_broadcast(pthread_cond_t *cond);
        int pthread_cond_signal(pthread_cond_t *cond);
    
    • pthread_cond_wait:堵塞等待一个条件变量。

      • 堵塞等待条件变量cond满足
      • 释放已掌握的互斥锁,相当于pthread_mutex_unlock(&mutex)。这两步是一个原子操作。
      • 当被唤醒,pthread_cond_wait函数返回时,解除堵塞并且重新申请获取互斥锁。
    • pthread_cond_broadcast/pthread_cond_signal:唤醒堵塞中的pthread_cond_wait

      • pthread_cond_broadcast:唤醒所有堵塞的条件量
      • pthread_cond_signal: 至少唤醒一个条件量
    • pthread_cond_timedwait:

      • 绝对时间的获取:

         time_t cur = time(NULL); // 获取当前的时间
         timespec t;
         t.tv_sec = cur + secons; // 在当前时间的基础上向后偏移才能获取绝对时间 
        
  • 生产者与消费者设计模式

  • 条件量的优点: 相较于mutex可以减少竞争。

    如果直接使用mutex,除了生产者和消费者之间要竞争互斥量之外,消费者之间也需要竞争互斥量,但是如果链表中没有数据,消费者之间的竞争是没有意义的。通过条件变量的机制,只有在生产者完成生产之后,消费者之间才会开始竞争,提高了效率。