信号量是用于提供不用进程或者不同线程之间的同步手段的原语,分为:
另外信号量同时根据取值不同可分为:
计数信号量具备两种操作动作,称为V(signal())与P(wait())(即部分参考书常称的“PV操作”)。V操作会增加信号标S的数值,P操作会减少它。
S <= 0
,则阻塞直到S > 0
,并将信标值S=S-1
;S > 0
,则直接S=S-1
;S=S+1
。PV操作某种程度上等价于如下代码只不过是原子的.
int p(int seq)
{
while(seq <= 0) ;
return --seq;
}
int v(int seq)
{
return ++seq;
}
互斥锁,信号量和条件变量的区别:
#include
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);
sem_open
:创建或者打开一个信号量;
name
:信号量在文件系统上的文件名;oflag
:可以为0,O_CREAT或者O_CREAT|O_EXCL
;mode
:信号量的权限位;value
:信号量的初值不能大于SEM_VALUE_MAX
;sem_t
是一个该信号量的标识符的指针;sem_close
:关闭一个信号量,但是并不删除信号量,另外进程终止系统会自动关闭当前进程打开的信号量;
sem
:信号量的指针,通过sem_open
获得;sem_unlink
:删除信号量,需要注意的是信号量同样维护了一个引用计数,只有当引用计数为0时才会显示的删除;
name
:信号量的在文件系统上的唯一标示;sem_post
:V操作,将信号量的值+1,并唤醒等待信号量的任意线程;
sem
:信号量的指针,通过sem_open
获得;sem_wait
:P操作,如果当前信号量小于等于0则阻塞,将信号量的值-1,否则直接将信号量-1;
sem
:信号量的指针,通过sem_open
获得;sem_trywait
:非阻塞的sem_wait
;
sem
:信号量的指针,通过sem_open
获得;sem_getvalue
:获取当前信号量的值,如果当前信号量上锁则返回0或者负值,其绝对值为信号量的值;
sem
:信号量的指针,通过sem_open
获得;sval
:信号量的值;sem_open
失败返回SEM_FAILED
,均是成功返回0,失败返回-1并且设置errno
。简单的消费者-生产者问题:
#define BUFF_SIZE 5
typedef struct named_share
{
char buff[BUFF_SIZE];
sem_t *lock;
sem_t *nempty;
sem_t *nstored;
int items;
}named_share;
void* named_produce(void *arg)
{
named_share *ptr = arg;
for(int i = 0;i < ptr->items;i++)
{
lsem_wait(ptr->nempty);
lsem_wait(ptr->lock); //锁
//生产物品
ptr->buff[i % BUFF_SIZE] = rand() % 1000;
printf("produce %04d into %2d\n", ptr->buff[i % BUFF_SIZE], i % BUFF_SIZE);
sleep(rand()%2);
lsem_post(ptr->lock); //解锁
lsem_post(ptr->nstored);
}
return NULL;
}
void* named_consume(void *arg)
{
named_share *ptr = arg;
for(int i = 0;i < ptr->items;i++)
{
lsem_wait(ptr->nstored);
lsem_wait(ptr->lock); //锁
//生产物品
printf("consume %04d at %2d\n", ptr->buff[i % BUFF_SIZE], i % BUFF_SIZE);
ptr->buff[i % BUFF_SIZE] = -1;
sleep(rand()%2);
lsem_post(ptr->lock); //解锁
lsem_post(ptr->nempty);
//iterator
}
return NULL;
}
void named_sem_test()
{
char *nempty_name = "nempty";
char *nstored_name = "nstored";
char *lock_name = "lock";
int items = 10;
int flag = O_CREAT | O_EXCL;
named_share arg;
srand(time(NULL));
arg.items = items;
memset(arg.buff, -1, sizeof(int) * BUFF_SIZE);
arg.nempty = lsem_open(lpx_ipc_name(nempty_name), flag, FILE_MODE, BUFF_SIZE);
arg.nstored = lsem_open(lpx_ipc_name(nstored_name), flag, FILE_MODE, 0);
arg.lock = lsem_open(lpx_ipc_name(lock_name), flag, FILE_MODE, 1);
pthread_t pid1, pid2;
int val = 0;
lsem_getvalue(arg.nstored, &val);
lsem_getvalue(arg.nempty, &val);
pthread_setconcurrency(2);
lpthread_create(&pid1, NULL, named_produce, &arg);
lpthread_create(&pid2, NULL, named_consume, &arg);
lpthread_join(pid1, NULL);
lpthread_join(pid2, NULL);
lsem_unlink(lpx_ipc_name(nempty_name));
lsem_unlink(lpx_ipc_name(nstored_name));
lsem_unlink(lpx_ipc_name(lock_name));
}
结果:
➜ build git:(master) ✗ ./main
produce 0038 into 0
produce 0022 into 1
produce 0049 into 2
produce 0060 into 3
produce 0090 into 4
consume 0038 at 0
consume 0022 at 1
consume 0049 at 2
consume 0060 at 3
consume 0090 at 4
produce 0031 into 0
produce -056 into 1
produce -103 into 2
produce -047 into 3
produce -100 into 4
consume 0031 at 0
consume -056 at 1
consume -103 at 2
consume -047 at 3
consume -100 at 4
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
sem
:信号量的指针;pshared
:标志是否共享:
pshared==0
:该信号量只能在同一进程不同线程之间共享,当进程终止则消失;pshared!=0
:该信号量驻留与共享内存区,可以在不同进程之间进行共享;value
:信号量的初值;sem_destroy
:销毁信号量。成功返回0,失败返回-1。上面的程序的简易修改:
void unnamed_sem_test()
{
int items = 10;
named_share arg;
srand(time(NULL));
arg.items = items;
memset(arg.buff, -1, sizeof(int) * BUFF_SIZE);
arg.lock = (sem_t*)malloc(sizeof(sem_t));
arg.nempty = (sem_t*)malloc(sizeof(sem_t));
arg.nstored = (sem_t*)malloc(sizeof(sem_t));
lsem_init(arg.lock, 0, 1);
lsem_init(arg.nempty, 0, BUFF_SIZE);
lsem_init(arg.nstored, 0, 0);
pthread_t pid1, pid2;
pthread_setconcurrency(2);
lpthread_create(&pid1, NULL, named_produce, &arg);
lpthread_create(&pid2, NULL, named_consume, &arg);
lpthread_join(pid1, NULL);
lpthread_join(pid2, NULL);
lsem_destroy(arg.lock);
lsem_destroy(arg.nempty);
lsem_destroy(arg.nstored);
SAFE_RELEASE(arg.lock);
SAFE_RELEASE(arg.nempty);
SAFE_RELEASE(arg.nstored);
}
➜ build git:(master) ✗ ./main
produce -120 into 0
produce -098 into 1
produce 0123 into 2
produce -058 into 3
produce 0028 into 4
consume -120 at 0
consume -098 at 1
consume 0123 at 2
consume -058 at 3
consume 0028 at 4
produce 0110 into 0
produce 0034 into 1
produce -068 into 2
produce -115 into 3
produce 0004 into 4
consume 0110 at 0
consume 0034 at 1
consume -068 at 2
consume -115 at 3
consume 0004 at 4
多生产者单消费者需要关注的是不同生产者之间的进度同步。
typedef struct multp_singc_share
{
int i;
sem_t nempty;
sem_t nstored;
sem_t lock;
int items;
char buff[BUFF_SIZE];
}multp_singc_share;
void* multp_singc_produce(void *arg)
{
multp_singc_share *ptr = arg;
for(;;)
{
lsem_wait(&(ptr->nempty));
lsem_wait(&(ptr->lock));
if(ptr->i >= ptr->items)
{
lsem_post(&(ptr->lock));
lsem_post(&(ptr->nempty));
return NULL;
}
ptr->buff[ptr->i % BUFF_SIZE] = rand() % 100;
printf("produce %d at %d\n", ptr->buff[ptr->i * BUFF_SIZE], ptr->i % BUFF_SIZE);
ptr->i++;
lsem_post(&ptr->lock);
lsem_post(&ptr->nstored);
}
}
void* multp_singc_consume(void *arg)
{
multp_singc_share *ptr = arg;
for(int i = 0;i < ptr->items;i ++)
{
lsem_wait(&(ptr->nstored));
lsem_wait(&(ptr->lock));
printf("consume %d at %d\n", ptr->buff[i % BUFF_SIZE], i % BUFF_SIZE);
lsem_post(&(ptr->lock));
lsem_post(&(ptr->nempty));
}
}
void multp_singc_test()
{
multp_singc_share arg;
pthread_t pro_th[THREAD_SIZE], con_th;
arg.items = 10;
arg.i = 0;
memset(arg.buff, 0, BUFF_SIZE * sizeof(int));
lsem_init(&(arg.lock), 0, 1);
lsem_init(&(arg.nempty), 0, BUFF_SIZE);
lsem_init(&(arg.nstored), 0, 0);
pthread_setconcurrency(THREAD_SIZE + 1);
for(int i = 0;i < THREAD_SIZE;i ++)
{
lpthread_create(&pro_th[i], NULL, multp_singc_produce, &arg);
}
lpthread_create(&con_th, NULL, multp_singc_consume, &arg);
for(int i = 0;i < THREAD_SIZE;i ++)
{
lpthread_join(pro_th[i], NULL);
}
lpthread_join(con_th, NULL);
lsem_destroy(&(arg.lock));
lsem_destroy(&(arg.nempty));
lsem_destroy(&(arg.nstored));
}
➜ build git:(master) ✗ ./main
produce 83 at 0
produce 0 at 1
produce 0 at 2
produce 0 at 3
consume 83 at 0
consume 86 at 1
produce 48 at 4
produce 127 at 0
produce 64 at 1
consume 77 at 2
consume 15 at 3
produce 0 at 2
consume 93 at 4
consume 35 at 0
produce -4 at 3
produce 31 at 4
consume 86 at 1
consume 92 at 2
consume 49 at 3
consume 21 at 4
多个生产者和消费者互相产生和读取数据。
typedef struct multp_multc_share
{
int pi;
int ci;
sem_t nempty;
sem_t nstored;
sem_t lock;
int items;
char buff[BUFF_SIZE];
}multp_multc_share;
void* multp_multc_produce(void *arg)
{
multp_multc_share *ptr = arg;
for(;;)
{
lsem_wait(&(ptr->nempty));
lsem_wait(&(ptr->lock));
if(ptr->pi >= ptr->items)
{
lsem_post(&(ptr->nstored));
lsem_post(&(ptr->nempty));
lsem_post(&(ptr->lock));
return NULL;
}
ptr->buff[ptr->pi % BUFF_SIZE] = rand() % 100;
printf("produce %d at %d\n", ptr->buff[ptr->pi * BUFF_SIZE], ptr->pi % BUFF_SIZE);
ptr->pi++;
lsem_post(&ptr->lock);
lsem_post(&ptr->nstored);
}
}
void* multp_multc_consume(void *arg)
{
multp_multc_share *ptr = arg;
for(;;)
{
lsem_wait(&(ptr->nstored));
lsem_wait(&(ptr->lock));
if(ptr->ci >= ptr->items)
{
lsem_post(&(ptr->nstored));
lsem_post(&(ptr->lock));
return NULL;
}
printf("consume %d at %d\n", ptr->buff[ptr->ci % BUFF_SIZE], ptr->ci % BUFF_SIZE);
ptr->ci++;
lsem_post(&(ptr->lock));
lsem_post(&(ptr->nempty));
}
}
void multp_multc_test()
{
multp_multc_share arg;
pthread_t pro_th[THREAD_SIZE], con_th[THREAD_SIZE];
arg.items = 10;
arg.pi = 0;
arg.ci = 0;
memset(arg.buff, 0, BUFF_SIZE * sizeof(int));
lsem_init(&(arg.lock), 0, 1);
lsem_init(&(arg.nempty), 0, BUFF_SIZE);
lsem_init(&(arg.nstored), 0, 0);
pthread_setconcurrency(THREAD_SIZE + 1);
for(int i = 0;i < THREAD_SIZE;i ++)
{
lpthread_create(&pro_th[i], NULL, multp_multc_produce, &arg);
}
for(int i = 0;i < THREAD_SIZE;i ++)
{
lpthread_create(&con_th[i], NULL, multp_multc_consume, &arg);
}
for(int i = 0;i < THREAD_SIZE;i ++)
{
lpthread_join(pro_th[i], NULL);
}
for(int i = 0;i < THREAD_SIZE;i ++)
{
lpthread_join(con_th[i], NULL);
}
lsem_destroy(&(arg.lock));
lsem_destroy(&(arg.nempty));
lsem_destroy(&(arg.nstored));
}
➜ build git:(master) ✗ ./main
produce 83 at 0
produce 0 at 1
produce 0 at 2
produce 0 at 3
consume 83 at 0
consume 86 at 1
consume 77 at 2
produce 1 at 4
produce 0 at 0
produce 0 at 1
produce 0 at 2
consume 15 at 3
consume 93 at 4
consume 35 at 0
consume 86 at 1
produce -2 at 3
consume 92 at 2
produce 98 at 4
consume 49 at 3
consume 21 at 4
通过多缓冲将数据从一个文件写入到另一个文件。
typedef struct
{
struct
{
char buff[MAX_LEN + 1];
int len;
}buff[BUFF_SIZE];
sem_t lock;
sem_t nempty;
sem_t nstored;
int items;
int readfd;
int writefd;
}wr_share;
//将buffer中的数据写入文件
void *write_buff(void *arg)
{
wr_share* ptr = arg;
int i = 0;
while(1)
{
lsem_wait(&ptr->lock);
//获取当前缓冲区的操作
lsem_post(&ptr->lock);
lsem_wait(&ptr->nstored);
lwrite(ptr->writefd, ptr->buff[i].buff, ptr->buff[i].len);
lwrite(STDOUT_FILENO, ptr->buff[i].buff, ptr->buff[i].len);
i++;
if(i >= ptr->items)
{
i = 0;
}
lsem_post(&ptr->nempty);
}
}
//从文件中读取数据
void *read_buff(void *arg)
{
wr_share* ptr = arg;
int i = 0;
while(1)
{
lsem_wait(&ptr->lock);
//获取当前缓冲区的操作
lsem_post(&ptr->lock);
lsem_wait(&ptr->nempty);
int n = lread(ptr->readfd, ptr->buff[i].buff, MAX_LEN);
ptr->buff[i].len = n;
i++;
if(i >= ptr->items)
{
i = 0;
}
lsem_post(&ptr->nstored);
}
}
void read_write_test()
{
wr_share arg;
arg.items = BUFF_SIZE;
#if 0
char *readfile = "build/CMakeCache.txt";
char *writefile = "build/mktmp";
#else
char *readfile = "CMakeCache.txt";
char *writefile = "mktmp";
#endif
arg.readfd = lopen(readfile, O_RDONLY);
arg.writefd = lopen(writefile, O_WRONLY | O_CREAT);
lsem_init(&arg.lock, 0, 1);
lsem_init(&arg.nempty, 0, arg.items);
lsem_init(&arg.nstored, 0, 0);
pthread_t read_th, write_th;
lpthread_create(&read_th, 0, read_buff, &arg);
lpthread_create(&write_th, 0, write_buff, &arg);
lpthread_join(read_th, NULL);
lpthread_join(write_th, NULL);
lsem_destroy(&arg.lock);
lsem_destroy(&arg.nempty);
lsem_destroy(&arg.nstored);
}
上面提到的Posix信号量有二值信号量和计数信号量,而System V信号量是信号量集,即一个或者多个信号量构成的集合,这个集合中的信号量都是计数信号量。
对于信号量集,内核维护的信息结构如下:
/* Data structure describing a set of semaphores. */
struct semid_ds
{
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};
//每个信号量包含的值
unsigned short semval; /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /* ID of process that did last op */
上面的第一个结构是书上提到的,下面的第二个结构是我主机上的结构,我猜测原理都差不多只是实现进行了修改,因此以书本上的描述为主。
sem_perm
:用户的操作权限;sem_nsems
:当前数据结构中信号量的数量;sem_otime
:上一次调用apisem_op
的时间;sem_ctime
:信号量创建的时间或上次调用IPC_SET
的时间。int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);
semget
:创建一个或者访问一个已经存在的信号量集;
key
:键值;nsems
:希望初始化的信号量数目,初始化之后不可修改,如果只是访问已经存在的信号量集则设为0;semflg
:可以为SEM_R,SEM_A
分别表示读和修改,也可以为IPC_CREAT, IPC_EXCL
;semop
:操作信号量集,内核能够保证当前操作的原子性;
semid
:通过sem_get
获得的信号量标识符;sops
:是一个如下结构数据的数组,因为有些系统不会定义该结构,因此有些时候需要用户自己定义:struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
sem_num
:需要操作的信号量在信号量集中的下标;sem_flg
:进行操作的设置:
0
;IPC_NOWAIT
:不阻塞;SEM_UNDO
;sem_op
:具体的操作方式
sem_op > 0
:
SEM_UNDO
:将当前值加到sem_val
上,即sem_val += sem_op
,等同于V操作,只不过释放线程量可能大于1;SEM_UNDO
:从相应的信号量的进程调整值(由内核维护)中减去sem_op
;sem_op == 0
:调用者希望sem_val==0
:
sem_val == 0
:立即返回;sem_val != 0
:对应信号量的semzcnt += 1
,阻塞至为0为止,除非设置了IPC_NOWAIT
,则不阻塞直接返回错误;sem_op < 0
:调用者希望等待sem_val >= |sem_op|
用于等待资源:
sem_val >= |sem_op|
,则sem_val -= |sem_op|
,若设置了SEM_UNDO
,则将|sem_op|
加到对应信号量的进程调整值上;sem_val < |sem_op|
,则相应信号量的semncnt += 1
,线程被阻塞到满足条件为止。等到解阻塞时semval-=|sem_op
,并且semncnt -= 1
,如果制定了SEM_UNDO
则sem_op
的绝对值加到对应信号量的调整值上。如果指定了IPC_NOWAIT
则线程不会阻塞。sem_op
或者信号量被删除,则该函数会过早的返回一个错误。semctl
:控制信号量集;
semid
:信号量集的标识符;semnum
:要操作的信号量的标号;cmd
:命令;
GETVAL
:semval
作为返回值返回,-1表示失败;SETVAL
:semval
设置为arg.val
,成功的话相应信号量在进程中的信号量调整值会设置为0;GETPID
:返回sempid
;GETNCNT
:返回semncnt
;GETZCNT
:返回semzcnt
;GETALL
:返回所有信号量的semval
,存入arg.array
;SETALL
:按照arg.array
设置所有信号量的semval
;IPC_RMID
:删除指定信号量集;IPC_SET
:根据arg.buf
设置信号量集的sem_perm.uid,sem_perm.gid,sem_perm.mode
;IPC_STAT
:返回当前信号量集的semid_ds
结构,存入arg.buf
,空间需要用户分配。arg
:可选,根据cmd
指定。union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
存在的一些限制:
semmni
:系统范围内最大信号量集数;semmsl
:每个信号量集最大信号量数;semmns
:系统范围内最大信号量数;semopm
:每个semop
调用最大操作数;semmnu
:系统范围内最大复旧结构数;semume
:每个复旧结构最大复旧项数;semvmx
:任何信号量的最大值;semaem
:最大退出时调整值。int vsem_create(key_t key)
{
int semid = lsemget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
return semid;
}
int vsem_open(key_t key)
{
int semid = lsemget(key, 0, 0);//创建一个信号量集
return semid;
}
int vsem_p(int semid)//
{
struct sembuf sb = {0, -1, /*IPC_NOWAIT*/SEM_UNDO};//对信号量集中第一个信号进行操作,对信号量的计数值减1,
lsemop(semid, &sb, 1);//用来进行P操作
return 0;
}
int vsem_v(int semid)
{
struct sembuf sb = {0, 1, /*0*/SEM_UNDO};
lsemop(semid, &sb, 1);
return 0;
}
int vsem_d(int semid)
{
int ret = semctl(semid, 0, IPC_RMID, 0);//删除一个信号量集
return ret;
}
int vsem_setval(int semid, int val)
{
union semun su;
su.val = val;
semctl(semid, 0, SETVAL, &su);//给信号量计数值
printf("value updated...\n");
return 0;
}
int vsem_getval(int semid)//获取信号量集中信号量的计数值
{
int ret = semctl(semid, 0, GETVAL, 0);//返回值是信号量集中
printf("current val is %d\n", ret);
return ret;
}
int vsem_getmode(int semid)
{
union semun su;
struct semid_ds sem;
su.buf = &sem;
semctl(semid, 0, IPC_STAT, su);
printf("current permissions is %o\n", su.buf->sem_perm.mode);
return 0;
}
int vsem_setmode(int semid, char *mode)
{
union semun su;
struct semid_ds sem;
su.buf = &sem;
semctl(semid, 0, IPC_STAT, su);
printf("current permissions is %o\n", su.buf->sem_perm.mode);
sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);
semctl(semid, 0, IPC_SET, &su);
printf("permissions updated...\n");
return 0;
}
void usage(void)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "semtool -c\n");
fprintf(stderr, "semtool -d\n");
fprintf(stderr, "semtool -p\n");
fprintf(stderr, "semtool -v\n");
fprintf(stderr, "semtool -s \n" );
fprintf(stderr, "semtool -g\n");
fprintf(stderr, "semtool -f\n");
fprintf(stderr, "semtool -m \n" );
}
void v_test(int argc, char *argv[])
{
int opt;
opt = getopt(argc, argv, "cdpvs:gfm:");//解析参数
if (opt == '?')
exit(EXIT_FAILURE);
if (opt == -1)
{
usage();
exit(EXIT_FAILURE);
}
key_t key = ftok(".", 's');
int semid;
switch (opt)
{
case 'c'://创建信号量集
vsem_create(key);
break;
case 'p'://p操作
semid = vsem_open(key);
vsem_p(semid);
vsem_getval(semid);
break;
case 'v'://v操作
semid = vsem_open(key);
vsem_v(semid);
vsem_getval(semid);
break;
case 'd'://删除一个信号量集
semid = vsem_open(key);
vsem_d(semid);
break;
case 's'://对信号量集中信号量设置初始计数值
semid = vsem_open(key);
vsem_setval(semid, atoi(optarg));
break;
case 'g'://获取信号量集中信号量的计数值
semid = vsem_open(key);
vsem_getval(semid);
break;
case 'f'://查看信号量集中信号量的权限
semid = vsem_open(key);
vsem_getmode(semid);
break;
case 'm'://更改权限
semid = vsem_open(key);
vsem_setmode(semid, argv[2]);
break;
}
return;
}