信号量广泛用于进程或线程建的同步和互斥,信号量本质上是一个非负整数计数器,它被用来控制对公共资源的访问。编程时,可根据操作信号量值的结果判断是否对公共资源具有访问权限,当信号量值大于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;
2 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 初始化一个
无名信号量
- 参数
pshared
指示该信号量是在一个进程内的所有线程
共享还是在进程间
共享- 如果该值为
0
,信号量被一个进程内的所有线程共享 - 如果该值
非0
,信号量在进程间共享
- 如果该值为
- 参数
value
指示信号量额初始值 - 初始化一个已经初始化的信号量,将导致不确定性行为
3 销毁信号量
int sem_destroy(sem_t *sem);
- 销毁一个
无名信号量
- 仅通过
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);
-
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
6 案例:信号量之线程互斥
注意
- 信号量用于互斥:不管多少个任务互斥,只需要一个信号量。
- 对于每一个线程中的任务,都是先执行
P操作(sem_*wait)
,后执行V操作(sem_post)
- 任务(线程)执行的先后顺序是不确定的
要求
- 任务(线程)一连续输出
0-4
- 任务(线程)二连续输出
10-14
- 任务(线程)三连续输出
20-24
- 任务(线程)的执行顺序无要求
-
源码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> 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); }
-
输出
读线程(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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> 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); }
-
输出
读线程(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)