• 【PTHREAD】线程互斥与同步之信号量


    信号量广泛用于进程或线程建的同步和互斥,信号量本质上是一个非负整数计数器,它被用来控制对公共资源的访问。编程时,可根据操作信号量值的结果判断是否对公共资源具有访问权限,当信号量值大于0时,则可以访问,否则将阻塞。PV原语是对信号量的操作,一次P操作是信号量减1,一次V操作使信号量加1

    1 信号量类型

    #if __WORDSIZE == 64
    # define __SIZEOF_SEM_T	32
    #else
    # define __SIZEOF_SEM_T	16
    #endif
    
    typedef union
    {
      char __size[__SIZEOF_SEM_T];
      long int __align;
    } sem_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2 初始化信号量

    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    • 1
    • 初始化一个无名信号量
    • 参数pshared指示该信号量是在一个进程内的所有线程共享还是在进程间共享
      • 如果该值为0,信号量被一个进程内的所有线程共享
      • 如果该值非0,信号量在进程间共享
    • 参数value指示信号量额初始值
    • 初始化一个已经初始化的信号量,将导致不确定性行为

    3 销毁信号量

    int sem_destroy(sem_t *sem);
    
    • 1
    • 销毁一个无名信号量
    • 仅通过sem_init初始化的信号量,使用该函数进行销毁
    • 销毁一个正在被其他线程或进程使用的信号量,将导致不确定性行为
    • 销毁一个已销毁的信号量,将导致不确定性行为
    • 一个已销毁的信号量,可以被重新初始化

    4 信号量P操作

    int sem_wait(sem_t *sem);
    int sem_trywait(sem_t *sem);
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    
    • 1
    • 2
    • 3
    • sem_wait

      使信号量执行一次P操作

      • 如果信号量的当前值大于零,则将信号量减一后,立即返回
      • 如果信号量的当前值等于零,则阻塞知道信号量变为可执行P操作或者一个信号处理中断被调用
    • sem_trywait

      除了当前信号量不可执行P操作时,该函数直接返回,且将errno设置为EAGAIN外,与sem_wait相同

    • sem_timedwait

      除了当前信号量不可执行P操作时,该函数将等待参数abs_timeout指定的时间外,与sem_wait相同。

      • 如果在等待时间内,能够执行P操作,则正常返回
      • 如果在等待时间内,不能执行P操作,则调用返回,且将errno设置为ETIMEDOUT

    5 信号量V操作

    int sem_post(sem_t *sem);
    
    • 1
    • 将信号量的值加1

    6 案例:信号量之线程互斥

    注意

    • 信号量用于互斥:不管多少个任务互斥,只需要一个信号量。
    • 对于每一个线程中的任务,都是先执行P操作(sem_*wait),后执行V操作(sem_post)
    • 任务(线程)执行的先后顺序是不确定的

    要求

    • 任务(线程)一连续输出0-4
    • 任务(线程)二连续输出10-14
    • 任务(线程)三连续输出20-24
    • 任务(线程)的执行顺序无要求
    • 源码

      #include 
      #include 
      #include 
      #include 
      #include 
      
      sem_t sem;
      void *start_routine_01(void *ptr)
      {
          sem_wait(&sem);
          for (size_t i = 0; i < 5; i++)
          {
              printf("读线程(%lu)获取全局变量当前值(%lu)\n", pthread_self(), i);
              sleep(1);
          }
          sem_post(&sem);
      
          return (void *)NULL;
      }
      
      void *start_routine_02(void *ptr)
      {
          sem_wait(&sem);
          for (size_t i = 10; i < 15; i++)
          {
              printf("读线程(%lu)获取全局变量当前值(%lu)\n", pthread_self(), i);
              sleep(1);
          }
          sem_post(&sem);
      
          return (void *)NULL;
      }
      
      void *start_routine_03(void *ptr)
      {
          sem_wait(&sem);
          for (size_t i = 20; i < 25; i++)
          {
              printf("读线程(%lu)获取全局变量当前值(%lu)\n", pthread_self(), i);
              sleep(1);
          }
          sem_post(&sem);
      
          return (void *)NULL;
      }
      
      int main(int argc, char const *argv[])
      {
          sem_init(&sem, 0, 1);
      
          pthread_t thread_id_01, thread_id_02, thread_id_03;
          pthread_create(&thread_id_01, NULL, start_routine_01, NULL);
          pthread_create(&thread_id_02, NULL, start_routine_02, NULL);
          pthread_create(&thread_id_03, NULL, start_routine_03, NULL);
      
          pthread_join(thread_id_01, NULL);
          pthread_join(thread_id_02, NULL);
          pthread_join(thread_id_03, NULL);
      
          sem_destroy(&sem);
          exit(EXIT_SUCCESS);
      }
      
      • 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
    • 输出

      读线程(139881171838720)获取全局变量当前值(0)
      读线程(139881171838720)获取全局变量当前值(1)
      读线程(139881171838720)获取全局变量当前值(2)
      读线程(139881171838720)获取全局变量当前值(3)
      读线程(139881171838720)获取全局变量当前值(4)
      读线程(139881163446016)获取全局变量当前值(10)
      读线程(139881163446016)获取全局变量当前值(11)
      读线程(139881163446016)获取全局变量当前值(12)
      读线程(139881163446016)获取全局变量当前值(13)
      读线程(139881163446016)获取全局变量当前值(14)
      读线程(139881155053312)获取全局变量当前值(20)
      读线程(139881155053312)获取全局变量当前值(21)
      读线程(139881155053312)获取全局变量当前值(22)
      读线程(139881155053312)获取全局变量当前值(23)
      读线程(139881155053312)获取全局变量当前值(24)

    7 信号量之线程同步

    注意

    • 有多少个任务,就需要多少个信号量
    • 初始化时,最先执行的任务对应的信号量设置为1,其余信号量设置为0
    • 在每个任务(线程)中,先P操作(sem_*wait)自己,后V操作(sem_post)下一个任务

    要求

    • 任务(线程)一连续输出0-4
    • 任务(线程)二连续输出10-14
    • 任务(线程)三连续输出20-24
    • 任务(线程)的执行顺序必须是:
      • 任务(线程)一
      • 任务(线程)二
      • 任务(线程)三
    • 源码

      #include 
      #include 
      #include 
      #include 
      #include 
      
      sem_t sem_01, sem_02, sem_03;
      
      void *start_routine_01(void *ptr)
      {
          sem_wait(&sem_01);
          for (size_t i = 0; i < 5; i++)
          {
              printf("读线程(%lu)获取全局变量当前值(%lu)\n", pthread_self(), i);
              sleep(1);
          }
          sem_post(&sem_02);
      
          return (void *)NULL;
      }
      
      void *start_routine_02(void *ptr)
      {
          sem_wait(&sem_02);
          for (size_t i = 10; i < 15; i++)
          {
              printf("读线程(%lu)获取全局变量当前值(%lu)\n", pthread_self(), i);
              sleep(1);
          }
          sem_post(&sem_03);
      
          return (void *)NULL;
      }
      
      void *start_routine_03(void *ptr)
      {
          sem_wait(&sem_03);
          for (size_t i = 20; i < 25; i++)
          {
              printf("读线程(%lu)获取全局变量当前值(%lu)\n", pthread_self(), i);
              sleep(1);
          }
          sem_post(&sem_01);
      
          return (void *)NULL;
      }
      
      int main(int argc, char const *argv[])
      {
          sem_init(&sem_01, 0, 1);
          sem_init(&sem_02, 0, 0);
          sem_init(&sem_03, 0, 0);
      
          pthread_t thread_id_01, thread_id_02, thread_id_03;
          pthread_create(&thread_id_01, NULL, start_routine_01, NULL);
          pthread_create(&thread_id_02, NULL, start_routine_02, NULL);
          pthread_create(&thread_id_03, NULL, start_routine_03, NULL);
      
          pthread_join(thread_id_01, NULL);
          pthread_join(thread_id_02, NULL);
          pthread_join(thread_id_03, NULL);
      
          sem_destroy(&sem_01);
          sem_destroy(&sem_02);
          sem_destroy(&sem_03);
          exit(EXIT_SUCCESS);
      }
      
      • 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
    • 输出

      读线程(140602965497600)获取全局变量当前值(0)
      读线程(140602965497600)获取全局变量当前值(1)
      读线程(140602965497600)获取全局变量当前值(2)
      读线程(140602965497600)获取全局变量当前值(3)
      读线程(140602965497600)获取全局变量当前值(4)
      读线程(140602957104896)获取全局变量当前值(10)
      读线程(140602957104896)获取全局变量当前值(11)
      读线程(140602957104896)获取全局变量当前值(12)
      读线程(140602957104896)获取全局变量当前值(13)
      读线程(140602957104896)获取全局变量当前值(14)
      读线程(140602948712192)获取全局变量当前值(20)
      读线程(140602948712192)获取全局变量当前值(21)
      读线程(140602948712192)获取全局变量当前值(22)
      读线程(140602948712192)获取全局变量当前值(23)
      读线程(140602948712192)获取全局变量当前值(24)

  • 相关阅读:
    Linux文本管理四剑客003
    如何实现前端单页面应用(SPA)?
    RabbitMQ+SpringBoot企业版队列实战------【华为云版】
    Linux - 进一步理解 文件系统 - inode - 机械硬盘
    Clickhouse 用户自定义外部函数
    【PAT甲级 - C++题解】1054 The Dominant Color
    二叉树经典oj面试题
    2022-11-13
    C语言题解 | 去重数组&&合并数组
    区域自动滚动,循环滚动
  • 原文地址:https://blog.csdn.net/zhy29563/article/details/126670150