信号量

信号量可以用于进程与进程之间,或者进程内线程之间的通信。每个信号量都有一个不会小于0的信号量值,对应信号量的可用数量。调用sem_init()或者sem_open()给信号量值赋初值,调用sem_post()函数可以让信号量值加1,调用sem_wait()可以让信号量值减1,如果当前信号量为0,调用sem_wait()的线程被挂起在该信号量的等待队列上,直到信号量值大于0,处于可用状态。

根据信号量的值(代表可用资源的数目)的不同,POSIX信号量可以分为:

  • 二值信号量:信号量的值只有0和1,初始值指定为1。这和互斥锁一样,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1。相当于只有一把钥匙,线程拿到钥匙后,完成了对共享资源的访问后需要解锁,把钥匙再放回去,给其他需要此钥匙的线程使用。使用方法和互斥锁一样,等待信号量函数必须和发送信号量函数成对使用,不能单独使用,必须先等待后发送。

  • 计数信号量:信号量的值在0到一个大于1的限制值(POSIX指出系统的最大限制值至少要为32767)。该计数表示可用信号量个数。此时,发送信号量函数可以被单独调用发送信号量,相当于有多把钥匙,线程拿到一把钥匙就消耗了一把,使用过的钥匙不必在放回去。

POSIX信号量又分为有名信号量和无名信号量:

  • 有名信号量:其值保存在文件中,一般用于进程间同步或互斥。

  • 无名信号量:其值保存在内存中,一般用于线程间同步或互斥。

RT-Thread操作系统的POSIX信号量主要是基于RT-Thread内核信号量的一个封装,主要还是用于系统内线程间的通讯。使用方式和RT-Thread内核的信号量差不多。

信号量控制块

每个信号量对应一个信号量控制块,创建一个信号量前需要先定义一个sem_t信号量控制块。sem_t是posix_sem结构体类型的重定义,定义在semaphore.h头文件里。

  1. struct posix_sem
  2. {
  3. rt_uint16_t refcount;
  4. rt_uint8_t unlinked;
  5. rt_uint8_t unamed;
  6. rt_sem_t sem; /* RT-Thread 信号量 */
  7. struct posix_sem* next; /* 指向下一个信号量控制块 */
  8. };
  9. typedef struct posix_sem sem_t;
  10.  
  11. rt_sem_tRT-Thread信号量控制块,定义在rtdef.h头文件里。
  12.  
  13. struct rt_semaphore
  14. {
  15. struct rt_ipc_object parent;/*继承自ipc_object类*/
  16. rt_uint16_t value; /* 信号量的值 */
  17. };
  18. /* rt_sem_t是指向semaphore结构体的指针类型 */
  19. typedef struct rt_semaphore* rt_sem_t;

无名信号量

无名信号量的值保存在内存中,一般用于线程间同步或互斥。在使用之前,必须先调用sem_init()初始化。

无名信号量初始化

函数原型

  1. int sem_init(sem_t *sem, int pshared, unsigned int value);

  1. 参数 描述

  1. sem 信号量句柄
  2.  
  3. value 信号量初始值,表示信号量资源的可用数量
  4.  
  5. pshared RT-Thread未实现参数

函数返回

初始化成功返回0,否则返回-1。

此函数初始化一个无名信号量sem,根据给定的或默认的参数对信号量相关数据结构进行初始化,并把信号量放入信号量链表里。初始化后信号量值为给定的初始值value。此函数是对rt_sem_create()函数的封装。

销毁无名信号量

函数原型

  1. int sem_destroy(sem_t *sem);

  1. 参数 描述

  1. sem 信号量句柄

函数返回

销毁成功返回0,否则返回-1。

此函数会销毁一个无名信号量sem,并释放信号量占用的资源。

有名信号量

有名信号量,其值保存在文件中,一般用于进程间同步或互斥。两个进程可以操作相同名称的有名信号量。RT-Thread操作系统中的有名信号量实现和无名信号量差不多,都是设计用于线程间的通信,使用方法也类似。

创建或打开有名信号量

函数原型

  1. sem_t *sem_open(const char *name, int oflag, ...);

  1. 参数 描述

  1. name 信号量名称
  2.  
  3. oflag 信号量的打开方式

函数返回

成功则返回信号量句柄,否则返回NULL。

销毁成功返回0,否则返回-1。

此函数会根据信号量名字name创建一个新的信号量或者打开一个已经存在的信号量。Oflag的可选值有0、O_CREAT或O_CREAT|O_EXCL。如果Oflag设置为O_CREAT则会创建一个新的信号量。如果Oflag设置O_CREAT|O_EXCL,如果信号量已经存在则会返回NULL,如果不存在则会创建一个新的信号量。如果Oflag设置为0,信号量不存在则会返回NULL。

分离有名信号量

函数原型

  1. int sem_unlink(const char *name);

  1. 参数 描述

  1. name 信号量名称

函数返回

分离成功返回0,若信号量不存在则返回-1。

此函数会根据信号量名称name查找该信号量,若信号量存在,则将该信号量标记为分离状态。之后检查引用计数,若值为0,则立即删除信号量,若值不为0,则等到所有持有该信号量的线程关闭信号量之后才会删除。

关闭有名信号量

函数原型

  1. int sem_close(sem_t *sem);

  1. 参数 描述

  1. sem 信号量句柄

函数返回

成功关闭返回0,否则返回-1。

当一个线程终止时,会对其占用的信号量执行此关闭操作。不论线程是自愿终止还是非自愿终止都会执行这个关闭操作,相当于是信号量的持有计数减1。若减1后持有计数为0且信号量已经处于分离状态,则会删除sem信号量并释放其占有的资源。

获取信号量值

函数原型

  1. int sem_getvalue(sem_t *sem, int *sval);

  1. 参数 描述

  1. sem 信号量句柄,不能为NULL
  2.  
  3. sval 保存获取的信号量值地址,不能为NULL

函数返回

成功返回0,否则返回-1。

此函数可以获取sem信号量的值,并保存在sval指向的内存里,可以知道信号量的资源数量。

阻塞方式等待信号量

函数原型

  1. int sem_wait(sem_t *sem);

  1. 参数 描述

  1. sem 信号量句柄,不能为NULL

函数返回

成功返回0,否则返回-1。

线程调用此函数获取信号量,是rt_sem_take(sem,RT_WAITING_FOREVER)函数的封装。若信号量值大于零,表明信号量可用,线程获得信号量,信号量值减1。若信号量值等于0,表明信号量不可用,线程阻塞进入挂起状态,并按照先进先出的方式排队等待,直到信号量可用。

非阻塞方式获取信号量

函数原型

  1. int sem_trywait(sem_t *sem);

  1. 参数 描述

  1. sem 信号量句柄,不能为NULL

函数返回

成功返回0,否则返回-1。

此函数是sem_wait()函数的非阻塞版,是rt_sem_take(sem,0)函数的封装。当信号量不可用时,线程不会阻塞,而是直接返回。

指定阻塞时间等待信号量

函数原型

  1. int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

  1. 参数 描述

  1. sem 信号量句柄,不能为NULL
  2.  
  3. abs_timeout 指定的等待时间,单位是操作系统时钟节拍(OS Tick

函数返回

成功返回0,否则返回-1。

此函数和sem_wait()函数的区别在于,若信号量不可用,线程将阻塞abs_timeout时长,超时后函数返回-1,线程将被唤醒由阻塞态进入就绪态。

发送信号量

函数原型

  1. int sem_post(sem_t *sem);

  1. 参数 描述

  1. sem 信号量句柄,不能为NULL

函数返回

成功返回0,否则返回-1。

此函数将释放一个sem信号量,是rt_sem_release()函数的封装。若等待该信号量的线程队列不为空,表明有线程在等待该信号量,第一个等待该信号量的线程将由挂起状态切换到就绪状态,等待系统调度。若没有线程等待该信号量,该信号量值将加1。

无名信号量使用示例代码

信号量使用的典型案例是生产者消费者模型。一个生产者线程和一个消费者线程对同一块内存进行操作,生产者往共享内存填充数据,消费者从共享内存读取数据。

此程序会创建2个线程,2个信号量,一个信号量表示共享数据为空状态,一个信号量表示共享数据不为空状态,一个互斥锁用于保护共享资源。生产者线程生产好数据后会给消费者发送一个full_sem信号量,通知消费者线程有数据可用,休眠2秒后会等待消费者线程发送的empty_sem信号量。消费者线程等到生产者发送的full_sem后会处理共享数据,处理完后会给生产者线程发送empty_sem信号量。程序会这样一直循环。

  1. #include <pthread.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <semaphore.h>
  6.  
  7. /* 静态方式初始化一个互斥锁用于保护共享资源*/
  8. static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  9. /* 2个信号量控制块,一个表示资源空信号,一个表示资源满信号 */
  10. static sem_t empty_sem,full_sem;
  11.  
  12. /* 指向线程控制块的指针 */
  13. static pthread_t tid1;
  14. static pthread_t tid2;
  15.  
  16. /* 函数返回值检查 */
  17. static void check_result(char* str,int result)
  18. {
  19. if (0 == result)
  20. {
  21. printf("%s successfully!\n",str);
  22. }
  23. else
  24. {
  25. printf("%s failed! error code is %d\n",str,result);
  26. }
  27. }
  28.  
  29. /* 生产者生产的结构体数据,存放在链表里 */
  30. struct node
  31. {
  32. int n_number;
  33. struct node* n_next;
  34. };
  35. struct node* head = NULL; /* 链表头,是共享资源 */
  36.  
  37. /* 消费者线程入口函数 */
  38. static void* consumer(void* parameter)
  39. {
  40. struct node* p_node = NULL;
  41.  
  42. while (1)
  43. {
  44. sem_wait(&full_sem);
  45. pthread_mutex_lock(&mutex); /* 对互斥锁上锁, */
  46.  
  47. while (head != NULL) /* 判断链表里是否有元素 */
  48. {
  49. p_node = head; /* 拿到资源 */
  50. head = head->n_next; /* 头指针指向下一个资源 */
  51. /* 打印输出 */
  52. printf("consume %d\n",p_node->n_number);
  53.  
  54. free(p_node); /* 拿到资源后释放节点占用的内存 */
  55. }
  56.  
  57. pthread_mutex_unlock(&mutex); /* 临界区数据操作完毕,释放互斥锁 */
  58.  
  59. sem_post(&empty_sem); /* 发送一个空信号量给生产者 */
  60. }
  61. }
  62. /* 生产者线程入口函数 */
  63. static void* product(void* patameter)
  64. {
  65. int count = 0;
  66. struct node *p_node;
  67.  
  68. while(1)
  69. {
  70. /* 动态分配一块结构体内存 */
  71. p_node = (struct node*)malloc(sizeof(struct node));
  72. if (p_node != NULL)
  73. {
  74. p_node->n_number = count++;
  75. pthread_mutex_lock(&mutex); /* 需要操作head这个临界资源,先加锁 */
  76.  
  77. p_node->n_next = head;
  78. head = p_node; /* 往链表头插入数据 */
  79.  
  80. pthread_mutex_unlock(&mutex); /* 解锁 */
  81. printf("produce %d\n",p_node->n_number);
  82.  
  83. sem_post(&full_sem); /* 发送一个满信号量给消费者 */
  84. }
  85. else
  86. {
  87. printf("product malloc node failed!\n");
  88. break;
  89. }
  90. sleep(2); /* 休眠2秒 */
  91. sem_wait(&empty_sem); /* 等待消费者发送空信号量 */
  92. }
  93. }
  94.  
  95. int rt_application_init()
  96. {
  97. int result;
  98.  
  99. sem_init(&empty_sem,NULL,0);
  100. sem_init(&full_sem,NULL,0);
  101. /* 创建生产者线程,属性为默认值,入口函数是product,入口函数参数为NULL*/
  102. result = pthread_create(&tid1,NULL,product,NULL);
  103. check_result("product thread created ",result);
  104.  
  105. /* 创建消费者线程,属性为默认值,入口函数是consumer,入口函数参数是NULL */
  106. result = pthread_create(&tid2,NULL,consumer,NULL);
  107. check_result("consumer thread created ",result);
  108.  
  109. return 0;
  110. }