在进程同步,并发运行时,保证按序地访问共享资源是十分重要的。因此引入了临界区的概念,一次只能有一个线程进入临界区完成他的指令。而信号量(semaphore)的作用,类似于一个交通信号灯,它负责进程协作,因此信号量又称为信号灯。
在Linux系统中,它提供两种信号量:
内核信号量,由内核控制路径使用
-
用户态进程使用的信号量,这种信号量有两种接口,
POSIX
信号量和SYSTEM V
信号量。信号量的本质是一个计数器。一个较为常见的用法,是为每个资源都会分配一个信号量。记信号量为S,除了初始化之外,有两个标准原子操作:
wait()
和signal()
。
System V信号量接口
-
semget
创建一个新信号量或取得一个已有信号量
int semget(key_t key, int num_sems, int sem_flags);
key
是一个整数值(唯一非零),可以理解成是信号量的标识符。num_sems
指定了需要的信号量数目,通常为1。sem_flags
是一组标志,当创建一个新的信号量时,设定权限与值IPC_CREAT
做按位或操作。设置了IPC_CREAT
标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL
则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。函数成功返回一个相应信号标识符(非零),失败返回
-1
。 -
semctl
直接控制信号量信息
int semctl(int sem_id, int sem_num, int command, ...);
第二个参数是操作信号在信号集中的编号,第一个信号的编号是
0
。第三个参数
command
通常是下面两个值中的其中一个:SETVAL
:用来把信号量初始化为一个已知的值。IPC_RMID
:用于删除一个已经无需继续使用的信号量标识符。如果有第四个参数,它通常是一个
union semum
结构,定义如下:union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
}; -
semop
改变信号量的值
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id
是由semget
返回的信号量标识符,sembuf
结构的定义如下:struct sembuf{
short sem_num;//除非使用一组信号量,否则为0
short sem_op;//信号量在一次操作中需要改变的数据,-1即P(等待)操作,+1即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量
};
进程同步实例
无信号量实例
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork();
srand(pid);
if(pid > 0) // parent process
{
char a = 'A'; // char to print
for(int i = 0; i < 10; ++i)
{
printf("%c", a);
fflush(stdout); // flush stdout buffer
sleep(1);
printf("%c", a);
fflush(stdout);
sleep(1);
}
}
else // child process
{
char b = 'B';
for(int i = 0; i < 10; ++i)
{
printf("%c", b);
fflush(stdout);
sleep(1);
printf("%c", b);
fflush(stdout);
sleep(1);
}
}
printf("\n%d - finished\n", getpid());
sleep(3);
return 0;
}
运行结果
有信号量实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SEMKEY 0x00002222 // set a key for semaphore
union semun // union for semaphore
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
struct sembuf p = { 0, -1, SEM_UNDO};
struct sembuf v = { 0, +1, SEM_UNDO};
int main()
{
int sem_id = semget(SEMKEY, 1, 0666 | IPC_CREAT); // get semaphore
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id, 0, SETVAL, sem_union) < 0)
{
perror("semctl error");
return -1;
}
int pid;
pid = fork();
srand(pid);
if(pid > 0) // parent process
{
char a = 'A'; // char to print
for(int i = 0; i < 10; ++i)
{
if(semop(sem_id, &p, 1) < 0) // P operation
{
perror("semop p error");
return -1;
}
printf("%c", a);
fflush(stdout); // flush stdout buffer
sleep(1);
printf("%c", a);
fflush(stdout);
if(semop(sem_id, &v, 1) < 0) // V operation
{
perror("semop v error");
return -1;
}
sleep(1);
}
}
else // child process
{
char b = 'B'; // char to print
for(int i = 0; i < 10; ++i)
{
if(semop(sem_id, &p, 1) < 0) // P operation
{
perror("semop p error");
return -1;
}
printf("%c", b);
fflush(stdout); // flush stdout buffer
sleep(1);
printf("%c", b);
fflush(stdout);
if(semop(sem_id, &v, 1) < 0) // V operation
{
perror("semop v error");
return -1;
}
sleep(1);
}
}
printf("\n%d - finished\n", getpid());
sleep(3);
if (pid > 0)
{
system("ipcrm -S 0x00002222");
}
return 0;
}
运行结果
因为设定信号量的关系,一个线程在临界区内一定会执行两次print()
操作,所以A或B一定成对出现。