一 POSIX 标准
#include <semaphore.h>
sem_t:信号量的数据结构
int sem_init (sem_t *sem, int pshared, unsigned int value)
无名信号量(也称为基于内存的信号量)sem初始化,设置共享选项pshared,并指定一个整数类型的初始值为value。pshared参数控制着信号量的类型。如果 pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量。
sem_t*sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
有名信号量(返回值)的创建和初始化。出错时返回为SEM_FAILED。
intsem_destroy(sem_t *sem);
释放无名信号量。
int sem_close(sem_t *sem);
对应有名信号量的关闭,注意不是销毁,是关闭。
int sem_unlink(count char*name);
对应有名信号量的销毁,每个信号都有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
对应信号量的P操作。 sem_wait和sem_trywait的差别是:当所指定信号灯的值已是0时,后者并不将调用线程投入睡眠。相反,他返回一个EAGAIN错误。
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *valp);
对应信号量的V操作。
注意:posix基于内存的信号灯和posix有名信号灯有一些区别,我们必须注意到这些。
1.sem_open不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的。
2.sem_init不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号灯的值。因此,对于一个给定的信号灯,我们必须小心保证只调用一次sem_init。
3.sem_open返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。
4.posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。 posix基于内存的信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。
5.基于内存的信号灯应用于线程很麻烦,而有名信号灯却很方便,基于内存的信号灯比较适合应用于一个进程的多个线程。
二 System V接口
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
sembuf结构体:
struct sembuf {
shortsem_num;
shortsem_op;
shortsem_flg;
};
sem_num是信号量的编号。
sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO。
int semget(key_t key, int nsems, intsemflg);
用来创建和访问一个信号量集,key:信号集的名字,nsems:信号集中信号量的个数,semflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样。成功返回一个非负整数,即该信号集的标识码,失败返回-1。
int semctl(int semid, int semnum, int cmd,...);
信号集的控制操作,包括销毁、设置信号量的值、读出信号量的值。Semid:由semget返回的信号集标识码。Semnum:信号集中信号量的序号。Cmd:将要采取的动作(有三个可取值)。最后一个参数根据命令不同而不同。
int semop(int semid, struct sembuf *sops,unsigned nsops);
用来修改一个信号集,进行PV操作。关键在于理解sembuf结构体。Semid:是该信号量的标识码,也就是semget函数的返回值。Sops:是个指向一个结构数值的指针。Nsops:信号量的个数。
三 总结
在用法方面,SystemV更突出一个集合的概念,因此,可以同时操作多个信号量,例如需要等待2个信号都满足条件才允许运行临界代码时,使用System V的信号更简易,但这不代表POSIX的信号量不能实现。下面以哲学家就餐问题(需要同时等待左右叉子),分别使用两种方法实现,体会其中的差别。
System V实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#ifdef _SEM_SEMUN_UNDEFINED
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
#endif
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int
wait_1fork(int no,int semid)
{
//int left = no;
//int right = (no + 1) % 5;
struct sembuf sb = {no,-1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}
int
free_1fork(int no,int semid)
{
struct sembuf sb = {no,1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}
//这里表明叉子是一个临界资源
#define DELAY (rand() % 5 + 1)
//相当于P操作
void
wait_for_2fork(int no,int semid)
{
//哲学家左边的刀叉号数
int left = no;
//右边的刀叉
int right = (no + 1) % 5;
//刀叉值是两个
//注意第一个参数是编号
struct sembuf buf[2] = {
{left,-1,0},
{right,-1,0}
};
//信号集中有5个信号量,只是对其中的
//资源sembuf进行操作
semop(semid,buf,2);
}
//相当于V操作
void
free_2fork(int no,int semid)
{
int left = no;
int right = (no + 1) % 5;
struct sembuf buf[2] = {
{left,1,0},
{right,1,0}
};
semop(semid,buf,2);
}
void philosophere(int no,int semid)
{
srand(getpid());
for(;;) {
#if 1
//这里采取的措施是当两把刀叉都可用的时候
//哲学家才能吃饭,这样不相邻的哲学家就可
//吃上饭
printf("%d is thinking\n",no);
sleep(DELAY);
printf("%d is hungry\n",no);
wait_for_2fork(no,semid);//拿到叉子才能吃饭
printf("%d is eating\n",no);
sleep(DELAY);
free_2fork(no,semid);//释放叉子
#else
//这段代码可能会造成死锁
int left = no;
int right = (no + 1) % 5;
printf("%d is thinking\n",no);
sleep(DELAY);
printf("%d is hungry\n",no);
wait_1fork(left,semid);
sleep(DELAY);
wait_1fork(right,semid);
printf("%d is eating\n",no);
sleep(DELAY);
free_2fork(no,semid);
#endif
}
}
int
main(int argc,char *argv[])
{
int semid;
//创建信号量
semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666);
if(semid < 0) {
ERR_EXIT("semid");
}
union semun su;
su.val = 1;
int i;
for(i = 0;i < 5;++i) {
//注意第二个参数也是索引
semctl(semid,i,SETVAL,su);
}
//创建4个子进程
int num = 0;
pid_t pid;
for(i = 1;i < 5;++i) {
pid = fork();
if(pid < 0) {
ERR_EXIT("fork");
}
if(0 == pid) {
num = i;
break;
}
}
//这里就是哲学家要做的事情
philosophere(num,semid);
return 0;
}
POSIX实现(关键在于在尝试获得第二把叉子时,使用的是sem_trywait否则会造成死锁)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#define PEOPLE_NUM 5
#define THINKING 1
#define HUNGRY 2
#define EATING 3
sem_t chopsticks[PEOPLE_NUM];
pthread_mutex_t mutex;
void *philosopher(void *arg){
int id = (int) arg;
int state = THINKING;
int right = (id + 1) % PEOPLE_NUM;
int left = (id + PEOPLE_NUM - 1) % PEOPLE_NUM;
char ptrState[32];
while(1){
switch(state){
case THINKING:
usleep(300);
state = HUNGRY;
strcpy(ptrState,"Thinking before eat");
break;
case HUNGRY:
strcpy(ptrState,"Hungry");
if(sem_wait(&chopsticks[left]) == 0){//阻塞状态
if(sem_trywait(&chopsticks[right]) == 0){//非阻塞
strcpy(ptrState,"I will Eating");
state = EATING;
}else{
state = THINKING;
strcpy(ptrState,"I have not chopsticks");
sem_post(&chopsticks[left]);//释放请求的得到的left筷子
printf("Philosopher right chopsticks is busy,right=%d,thread id is %d\n",right,id);
}
}else{
printf("Philosopher left chopsticks is busy,left=%d,thread id is %d\n",left,id);//这句话由于上面被阻塞永远不会输出
}
break;
case EATING:
printf("Philosopher fetch left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);
sem_post(&chopsticks[left]);
sem_post(&chopsticks[right]);
printf("Philosopher release left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);
usleep(500);
state = THINKING;
strcpy(ptrState,"Thinking after eat");
break;
}
pthread_mutex_lock(&mutex);
printf("Philosopher is %s, thread id is %d\n",ptrState,id);
pthread_mutex_unlock(&mutex);
usleep(1000);
}
pthread_exit((void*)0);
}
int main(){
pthread_t tid[PEOPLE_NUM];
int i;
pthread_mutex_init(&mutex,NULL);
for(i = 0 ; i < PEOPLE_NUM ; i ++){
sem_init(&chopsticks[i],0,1);
}
for(i = 0 ; i < PEOPLE_NUM ; i ++){
pthread_create(&tid[i],NULL,philosopher,(void*)i);
}
for(i = 0 ; i < PEOPLE_NUM ; i ++){
pthread_join(tid[i],NULL);
}
return 0;
}
另外,也给出使用POSIX 互斥量的方法解决(与POSIX信号量,有相似之处)
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
//筷子作为mutex
pthread_mutex_t chopstick[6] ;
void *eat_think(void *arg)
{
char phi = *(char *)arg;
int left,right; //左右筷子的编号
switch (phi){
case 'A':
left = 5;
right = 1;
break;
case 'B':
left = 1;
right = 2;
break;
case 'C':
left = 2;
right = 3;
break;
case 'D':
left = 3;
right = 4;
break;
case 'E':
left = 4;
right = 5;
break;
}
int i;
for(;;){
usleep(3); //思考
pthread_mutex_lock(&chopstick[left]); //拿起左手的筷子
printf("Philosopher %c fetches chopstick %d\n", phi, left);
if (pthread_mutex_trylock(&chopstick[right]) == EBUSY){ //拿起右手的筷子
pthread_mutex_unlock(&chopstick[left]); //如果右边筷子被拿走放下左手的筷子
continue;
}
// pthread_mutex_lock(&chopstick[right]); //拿起右手的筷子,如果想观察死锁,把上一句if注释掉,再把这一句的注释去掉
printf("Philosopher %c fetches chopstick %d\n", phi, right);
printf("Philosopher %c is eating.\n",phi);
usleep(3); //吃饭
pthread_mutex_unlock(&chopstick[left]); //放下左手的筷子
printf("Philosopher %c release chopstick %d\n", phi, left);
pthread_mutex_unlock(&chopstick[right]); //放下左手的筷子
printf("Philosopher %c release chopstick %d\n", phi, right);
}
}
int main(){
pthread_t A,B,C,D,E; //5个哲学家
int i;
for (i = 0; i < 5; i++)
pthread_mutex_init(&chopstick[i],NULL);
pthread_create(&A,NULL, eat_think, "A");
pthread_create(&B,NULL, eat_think, "B");
pthread_create(&C,NULL, eat_think, "C");
pthread_create(&D,NULL, eat_think, "D");
pthread_create(&E,NULL, eat_think, "E");
pthread_join(A,NULL);
pthread_join(B,NULL);
pthread_join(C,NULL);
pthread_join(D,NULL);
pthread_join(E,NULL);
return 0;
}
参考文献
http://blog.csdn.net/jasenwan88/article/details/7766808
http://blog.csdn.net/acceptedxukai/article/details/8307247
http://www.oschina.net/code/snippet_724028_36857
http://blog.chinaunix.net/uid-26746982-id-3388652.html