信号量
信号量主要用于进程和线程间的同步,信号量保存一个整数值来控制对资源的访问,当值大于0时,表示资源空闲可以访问,等于0时表示资源分配完毕无法访问,小于0时表示有至少1个线程(进程)正在等待资源。如果信号量的值只为0或1,那么它就是一个二元信号量,功能就想当于一个互斥锁。
信号量的P,V操作
信号量只有两种操作:等待和发送信号,分别用P(s), V(s)表示。P,V操作是不可分割的。
P(s): 如果s的值大于0,那么P将s的值减1。如果s为0,那么就挂起这个线程知道s变为非零。一个V操作会唤醒这个线程。
V(s):将s加1,如果有线程等待s变为非0,那么唤醒该线程,该线程将s减1。
函数解析
信号量有两种实现:System V信号量和Posix信号量,System V信号量由semget、semop、semctl这几个函数来实现,具体参考: System V信号量函数。下面重点介绍Posix的信号量函数。
#include<semaphore.h> 包含在头文件
int sem_init(sem_t *sem, int pshared, unsigned int value)
sem:要初始化的信号量;
pshared:此信号量是在进程间共享还是线程间共享,0则为线程共享;
value:信号量的初始值;
成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值
int sem_destroy(sem_t *sem)
sem是要销毁的信号量。
只有用sem_init初始化的信号量才能用sem_destroy销毁。
成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值
int sem_wait(sem_t *sem)
等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。
如果信号量的值为0,则线程阻塞。相当于P操作。
成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值
int sem_post(sem_t *sem)
释放信号量,让信号量的值加1。相当于V操作。
成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值
信号量处理生产者消费者问题
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
struct buffer
{
char data[20];
int n; //number of product in data
int p; //index of producer
int c; //index of consumer
buffer():n(20),p(-1),c(-1){}
};
int bk = 1;
buffer mybuf;
sem_t mutex, prod, cons; // three semaphore
void* producer_thread(void* arg)
{
char ch = 'A';
while(bk)
{
sem_wait(&prod); //can produce
sem_wait(&mutex); //mutex the buff
mybuf.data[(++mybuf.p)%mybuf.n] = ch;
sem_post(&mutex);
sem_post(&cons); //tell consumer there is new product
printf("%lu produce: %c\n", pthread_self(), ch);
if(ch>='Z')
ch = 'A';
else
ch = ch + 1;
sleep(1); //relax
}
return NULL;
}
void* consumer_thread(void* arg)
{
char ch;
while(bk)
{
sem_wait(&cons); //wait for product
sem_wait(&mutex);
ch = mybuf.data[(++mybuf.c)%mybuf.n];
sem_post(&mutex);
sem_post(&prod); //tell producer there is a new buff to produce
printf("%lu consume: %c\n", pthread_self(), ch);
sleep(2); //digest
}
return NULL;
}
int main()
{
pthread_t pidp[5];
pthread_t pidc[5];
sem_init(&mutex, 0, 1);
sem_init(&cons, 0, 0); //at the begin there is 0 product ot consume
sem_init(&prod, 0, 20); //at the begin there are 20 product to produce
for(int i=0; i<3; i++)
pthread_create(&pidp[i], NULL, producer_thread, NULL);
for(int i=0; i<3; i++)
pthread_create(&pidc[i], NULL, consumer_thread, NULL);
sleep(10);
bk = 0; //after 60s, stop the will
int x = 3;
while(x)
{
sem_post(&prod);
printf("prod----\n");
x--;
}
x = 3;
while(x)
{
printf("cons----\n");
sem_post(&cons);
x--;
}
for(int i=0; i<3; i++)
pthread_join(pidp[i], NULL);
for(int i=0; i<3; i++)
pthread_join(pidc[i], NULL);
sem_destroy(&mutex);
sem_destroy(&cons);
sem_destroy(&prod);
printf("finished\n");
return 0;
}
信号量与互斥锁条件变量的区别
信号量与线程锁、条件变量相比有以下几点不同:
1. 锁必须是同一个线程获取以及释放,否则会死锁。而条件变量和信号量则不必。
2. 信号的递增与减少会被系统自动记住,系统内部有一个计数器实现信号量,不必担心会丢失,而唤醒一个条件变量时,如果没有相应的线程在等待该条件变量,这次唤醒将被丢失。
3. 信号量可以允许多个线程访问资源,互斥锁一个时刻只有一个线程访问资源。