Linux线程同步笔记

互斥锁

  • 创建互斥锁

    • pthread_mutex_t mutex;
  • 使用例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //创建一把互斥锁
    pthread_mutex_t mutex;
    void* funcA_num(void* arg)
    {
    for(int i=0;i<MAX; ++i)
    {
    // 访问全局变量之前加锁
    //如果Mutex被锁上了,代码阻塞在当前位置
    pthread_mutex_lock(&mutex);
    int cur = number;
    cur++;
    number = cur;
    printf("Thread A,id = %lu,number = %d\n",pthread_self(),number);
    //解锁
    pthread_mutex_unlock(&mutex);
    usleep(10);
    }
    return NULL;
    }
  • 互斥锁的特点

    • 多个线程访问共享数据的时候是串行的
  • 使用互斥锁的缺点

    • 效率低
  • 互斥锁使用步骤:

    • 创建互斥锁:pthread_mutex_t mutex;
    • 初始化这把锁:pthread_mutex_init(&mutex,NULL);
    • 寻找共享资源:
      • 操作共享资源代码之前加锁,操作完解锁
        • pthread_mutex_lock(&mutex);
        • 。。。临界区,尽可能小
        • pthread_mutex_unlock(&mutex);

死锁

造成死锁原因:

  • 自己锁自己两次
    • 会阻塞在第二次锁的位置
    • 操作完成后一定要解锁
  • 多个线程多个锁,两个共享资源互锁
    • 线程1对共享资源A加锁成功——A锁
    • 线程2对共享资源B加锁成功——B锁
      • 线程1访问共享资源B,对b加锁——线程1阻塞在B锁上
      • 线程2访问共享资源A,对A加锁——线程2阻塞在A锁上
    • 如何解决:
      • 让线程都按照一定的顺序去访问共享资源
      • 去访问其它锁的时候,先将自己的锁解开
      • trylock,如果失败就进行处理,比如sleep一下,在尝试加锁访问数学,不要阻塞在那里

读写锁

  • 读写锁的类型

    • 读锁
  • 只要没有线程持有给定的读写锁用于写,那么任意数目的线程可以持有读写锁用于读。

  • 仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配读写锁用于写。

  • 读写锁用于读称为共享锁,读写锁用于写称为排它锁。

  • 函数原型:

    1
    2
    3
    4
    5
    6
    pthread_rwlock lock;
    pthread_rwlock_init(&lock,NULL);
    pthread_rwlock_destroy(pthread_rwlock *rwlock);
    int pthread_rwlock_rdlock(pthread_rwlock *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock *rwlock);
  • 特点

    • 读共享
    • 写独占
    • 读写不能同时,写的优先级高
  • 几种场景

    • 线程A加写锁成功,线程B请求读锁
      • 线程B阻塞
    • 线程A持有读锁,线程B请求写锁
      • 线程B阻塞
    • 线程A拥有读锁,线程B请求读锁
      • 线程B加锁成功
    • 线程A持有读锁,然后线程B请求写锁,然后线程C请求读锁
      • B阻塞,C阻塞——因为写的优先级高
      • A解锁,B线程加写锁成功,C继续阻塞
      • B解锁,C加读锁成功
    • 线程A持有写锁,然后线程B请求读锁,然后线程C请求写锁
      • B阻塞,C阻塞
      • A解锁,C加写锁成功,B继续阻塞
      • C解锁,B加锁成功
  • 使用场景

    • 互斥锁:读写都串行
    • 读写锁:
      • 读:并行
      • 写:串行
    • 读写场景,读多写少

自旋锁

自旋锁类似于互斥锁,它的性能比互斥锁更高。
自旋锁与互斥锁很重要的一个区别在于,线程在申请自旋锁的时候,线程不会被挂起,它处于忙等待的状态,一般用于等待时间比较短的情形。

1
2
3
4
pthread_spin_init
pthread_spin_destroy
pthread_spin_lock
pthread_spin_unlock

条件变量

多线程中经常需要使用到锁(pthread_mutex_t)来完成多个线程之间的互斥操作。

但是互斥锁有一个明显到缺点: 只有两种状态,锁定和非锁定。

而条件变量则通过允许线程阻塞并等待另一个线程发送唤醒信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。

  • 条件变量不是锁,但是条件变量能够阻塞线程

  • 使用条件变量+互斥量,才能实现同步

    • 互斥量:保护一块共享数据
    • 条件变量:引起阻塞
      • 生产者和消费者模型
  • 条件变量的两个动作

    • 条件不满足,线程阻塞
    • 当条件满足,通知阻塞线程开始工作
  • 条件变量的类型:pthread_cond_t;

  • 主要函数:

    • 初始化一个条件变量

    • 1
      2
      3
      4
      pthread_cond_init(
      pthread_cond_t *restrict cond,
      const pthread_condattr_t *restrict
      );
    • 1
      int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
      • 阻塞线程
      • 将已经上锁的mutex解锁
      • 当该函数收到信号,解除阻塞,会对互斥锁加锁
    • 1
      2
      3
      4
      int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
      int pthread_cond_destroy(pthread_cond_t *cond);
      int pthread_cond_signal(pthread_cond_t *cond);
      int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
  • 具体实现生产者消费者模型

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      #include <pthread.h>
      #include <errno.h>
      #include <stdlib.h>
      #include <stdio.h>
      #include <unistd.h>
      #define MAXITEMS 1000000 /* 需要生产n个item */
      #define MAXNTHREADS 10 /* 生产线n条 */
      int buff[MAXITEMS];
      struct {
      pthread_mutex_t mutex;
      int nput;
      int nval;
      } put = {
      PTHREAD_MUTEX_INITIALIZER
      };
      struct {
      pthread_mutex_t mutex;
      pthread_cond_t cond;
      int nready;
      } nready = {
      PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER
      };
      //创建线程
      int Pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
      int res = pthread_create(thread, attr, start_routine, arg);
      if (res != 0) {
      perror("pthread_create fail");
      exit(EXIT_FAILURE);
      }
      }
      //互斥锁
      int Pthread_mutex_lock(pthread_mutex_t *mutex) {
      int res = pthread_mutex_lock(mutex);
      if (res != 0) {
      perror("Pthread_mutex_lock fail");
      }
      }
      //互斥解锁
      int Pthread_mutex_unlock(pthread_mutex_t *mutex) {
      int res = pthread_mutex_unlock(mutex);
      if (res != 0) {
      perror("Pthread_mutex_unlock fail");
      }
      }
      //发送线程信号
      int Pthread_cond_signal(pthread_cond_t *cond) {
      int res = pthread_cond_signal(cond);
      if (res != 0) {
      perror("Pthread_cond_signal fail");
      }
      }
      /*
      pthread_cond_wait会释放mutex,并进入阻塞状态,
      一旦获取到信号则会将mutex锁住,并返回
      */
      int Pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) {
      int res = pthread_cond_wait(cond, mutex);
      if (res != 0) {
      perror("pthread_cond_wait fail");
      }
      }
      //线程函数声明
      void* produce(void*);
      void* consume(void*);
      int main(int argc, char const *argv[])
      {
      int i, count[MAXNTHREADS];
      pthread_t tid_produce[MAXNTHREADS], tid_consume;
      // n个生产者
      for (i=0; i<MAXNTHREADS; ++i) {
      count[i] = 0;
      Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
      }
      // 1个消费者,也可以是n个
      Pthread_create(&tid_consume, NULL, consume, NULL);
      for (i=0; i<MAXNTHREADS; ++i) {
      pthread_join(tid_produce[i], NULL);
      printf("count[%d] = %d\n", i, count[i]);
      }
      pthread_join(tid_consume, NULL);
      return 0;
      }
      // 生产者
      void* produce(void* arg) {
      int dosignal;
      for ( ; ; ) {
      Pthread_mutex_lock(&put.mutex);
      if (put.nput >= MAXITEMS) {
      Pthread_mutex_unlock(&put.mutex);
      return(NULL); /* 数据填满了 */
      }
      buff[put.nput] = put.nval;
      put.nput++;
      put.nval++;
      Pthread_mutex_unlock(&put.mutex);
      Pthread_mutex_lock(&nready.mutex);
      dosignal = (nready.nready == 0); /* 是否需要唤醒 */
      nready.nready++;
      Pthread_mutex_unlock(&nready.mutex);
      if (dosignal) {
      Pthread_cond_signal(&nready.cond);
      }
      *((int *)arg) += 1; /* 当前线程生产了多少个数据 */
      }
      return(NULL);
      }
      // 消费者
      void* consume(void* arg) {
      int i;
      for (i=0; i<MAXITEMS; i++) {
      Pthread_mutex_lock(&nready.mutex);
      while (nready.nready == 0) { /* while 循环是防止wait返回时发生虚假唤醒 */
      Pthread_cond_wait(&nready.cond, &nready.mutex);
      }
      nready.nready--;
      Pthread_mutex_unlock(&nready.mutex);
      //如果线程没同步成功,则会有输出
      if (buff[i] != i)
      printf("buff[%d] = %d\n", i, buff[i]);
      }
      return(NULL);
      }

      信号量

      对于信号量,可以理解成带计数的互斥量。

      具体应用的话,我没想到特别的用法,google了一下,多是照本宣科。

本文标题:Linux线程同步笔记

文章作者:Yang Shuai

发布时间:2019年03月12日 - 17:03

最后更新:2019年03月12日 - 17:03

原始链接:https://ysbbswork.github.io/2019/03/12/Linux线程同步笔记/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!