信号量广泛用于进程或线程间的同步和互斥(一般用于线程,进程间常用有名管道),信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
用于进程或线程间互斥:
- 初始化信号量为1;
- 执行需要互斥的操作前先进行P操作(减 1),其他线程或进程会阻塞在这里;
- 执行完后进行V操作(加 1),其他进程或线程可访问。
用于进程或线程间同步:
函数
#include <semaphore.h>
/* 注意:编译信号量操作函数时,需要加上参数-lpthread。
信号量数据类型为:sem_t。 */
/* 创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。*/
int sem_init(sem_t *sem, int pshared, unsigned int value);
/* 参数:
sem:信号量的地址。
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
value:信号量的初始值。
*/
/* 返回值:
成功:0
失败:-1
*/
/* 将信号量的值减 1。操作前,先检查信号量(sem)的值是否为 0,若信号量为 0,
此函数会阻塞,直到信号量大于 0 时才进行减 1 操作。*/
int sem_wait(sem_t *sem);
/* 参数:
sem:信号量的地址。
*/
/* 返回值:
成功:0
失败:-1
*/
int sem_trywait(sem_t *sem);
/* 以非阻塞的方式来对信号量进行减 1 操作。若操作前,信号量的值等于 0,
则对信号量的操作失败,函数立即返回。*/
/* 将信号量的值加 1 并发出信号唤醒等待线程(sem_wait())。*/
int sem_post(sem_t *sem);
/* 参数:
sem:信号量的地址。
*/
/* 返回值:
成功:0
失败:-1
*/
/* 获取 sem 标识的信号量的值,保存在 sval 中。*/
int sem_getvalue(sem_t *sem, int *sval);
/* 参数:
sem:信号量地址。
sval:保存信号量值的地址。
*/
/* 返回值:
成功:0
失败:-1
*/
/* 删除 sem 标识的信号量。*/
int sem_destroy(sem_t *sem);
/* 参数:
sem:信号量地址。
*/
/* 返回值:
成功:0
失败:-1
*/
线程间互斥
信号量在线程间互斥的用法与互斥锁相似,即:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
/* 定义一个信号量 */
sem_t sem;
void* call_back(void* arg)
{
sem_wait(&sem); //减一操作,后面的线程会阻塞在这里
char* str = (char*)arg;
while(*str != '\0')
{
putchar(*str);
str++;
}
putchar(' ');
sem_post(&sem); //加一操作,后面的线程可以从sem_wait()开始往下执行
}
int main()
{
pthread_t pthrd1,pthrd2;
sem_init(&sem, 0, 1); //初始化一个信号量
pthread_create(&pthrd1,NULL,call_back,"hello");
pthread_create(&pthrd2,NULL,call_back,"world");
pthread_join(pthrd1,NULL);
pthread_join(pthrd2,NULL);
sem_destroy(&sem); //销毁一个信号量
return 0;
}
╭─lingyun@manjaro ~/Document/codes
╰─➤ vim exam.c
╭─lingyun@manjaro ~/Document/codes
╰─➤ gcc exam.c -lpthread
╭─lingyun@manjaro ~/Document/codes
╰─➤ ./a.out
hello world //如果不互斥,这几个字母就会乱序输出
线程间同步
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem1, sem2;
void* call_back1(void* arg)
{
sem_wait(&sem1); //检测sem1是否非零,非零则不阻塞,并进行减操作
char* str = (char*)arg;
printf("%s ",str);
sem_post(&sem2); //对sem2进行加操作
}
void* call_back2(void* arg)
{
sem_wait(&sem2); //检测sem2是否为零,为零则阻塞在这里,直到sem2在其他线程被加操作变为非零
char* str = (char*)arg;
printf("%s ",str);
sem_post(&sem1); //对sem1进行加操作
}
int main()
{
pthread_t pthrd1,pthrd2;
/* 初始化两个信号量 */
sem_init(&sem1, 0, 1);
sem_init(&sem2, 0, 0);
pthread_create(&pthrd1,NULL,call_back1,"hello");
pthread_create(&pthrd2,NULL,call_back2,"world");
pthread_join(pthrd1,NULL);
pthread_join(pthrd2,NULL);
/* 销毁两个信号量 */
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
╭─lingyun@manjaro ~/Document/codes
╰─➤ gcc exam.c -lpthread
╭─lingyun@manjaro ~/Document/codes
╰─➤ ./a.out
hello world //用同步规定先打印 hello, 再打印 world