文章目录
一、semaphore 简介
信号量广泛用于进程或线程间的同步和互斥,本质上是一个非负整数计数器,当信号量值大于 0 时,则可以访问公共资源,否则将阻塞访问
PV 是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1
linux 信号量分两种:
- 内核信号量
- 用户态信号量,又分两种:
- POSIX信号量,又分为两种:
- 无名信号量,一般用于线程间和父子进程间同步或互斥,保存在内存中
- 有名信号量,一般用于普通进程间同步或互斥,创建一个文件保存在文件中
- SYSTEM V信号量,是一个或多个信号量的集合,具体操作和 POSIX 信号量大同小异
- POSIX信号量,又分为两种:
二、无名信号量基本函数
1. int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared:
控制信号量的类型,0表示线程间共享,其它表示进程间共享
value:
信号量的初始值
2. int sem_post(sem_t *sem);
是以原子操作的方式给信号量的值加1(V操作),并发出信号唤醒等待线程 sem_wait
3. int sem_wait(sem_t *sem);
以原子操作的方式给信号量的值减1(P操作),如果信号量的值为0函数将会等待,直到有线程增加了该信号量的值使其不再为0
4. int sem_trywait(sem_t *sem);
非阻塞方式,如果信号量的值为0立即返回,返回错误 EAGAIN
5. int sem_getvalue(sem_t *sem, int *sval);
获取信号量的值,保存在 sval 中
6. int sem_destory(sem_t *sem);
用于销毁信号量,释放所有相关联的资源(由 sem_init
自动申请的资源)
三、有名信号量基本函数
1. sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name:
不能写路径,默认创建的文件放在 “/dev/shm/sem.xxx”
oflag:
O_CREAT: 若信号量不存在则创建,且mode和value必须有效。若信号量已存在则直接打开,且忽略mode和value参数。O_CREAT|O_EXCL: 若信号量已存在,该函数会直接返回error
value:
信号量的初始值
2. int sem_close(sem_t *sem);
关闭有名信号量
3. int sem_unlink(const char *name);
删除有名信号量文件和回收分配的资源,调用 sem_unlink
回收有名信号量资源时,要确定所有对这个有名信号量的引用都已经通过 sem_close
关闭了,如果有任何的进程或是线程还在引用这个信号量,sem_unlink
不会起到任何的作用,即必须是最后一个使用该信号量的进程来执行才有效,因为每个信号量都有一个引用计数器记录当前的打开次数,sem_unlink
必须等待计数为0时才把信号量从文件系统中删除
四、SYSTEM V 信号量基本函数
1. struct semid_ds
信号量集合数据结构,此数据结构中定义了整个信号量集的基本属性
struct semid_ds
{
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array */
};
2. struct sem
信号量数据结构,此数据结构中定义了单个信号量的基本属性
struct sem
{
int semval; /* current value */
int sempid; /* pid of last operation */
struct list_head sem_pending; /* pending single-sop operations */
};
3. int semget(key_t key, int nsems, int semflg);
创建或打开一个信号量集合,该集合中可以包含多个信号量
key:
进程间通信键值,通过调用 ftok() 函数得到
nsems:
创建的信号量的个数。如果只是访问可以指定为 0,一旦创建了该信号量集合就不能更改其信号量个数
semflg:
标识函数的行为及信号量的权限,IPC_CREAT:
创建信号量;IPC_EXCL:
检测信号量是否存在
4. int semctl(int semid, int semnum, int cmd, xxx);
对信号量集合以及集合中的信号量进行控制
semnum:
集合中信号量的序号,指定对哪个信号量操作, 只对某些 cmd 操作有意义
cmd:
信号量控制类型,semctl 参数由 cmd 决定,可能有3个或4个。当4个参数时第4个为自定义联合体,用于设置或获取信息
union semun{
int val; /*信号量值*/
struct semid_ds *buf; /*信号量集合信息*/
unsigned short *array; /*信号量值数组*/
struct seminfo *__buf; /*信号量信息数组*/
};
5. int semop(int semid, struct sembuf *sops, unsigned nsops);
操作信号量,主要进行信号量加减操作
sem_op > 0:
信号量的值在原来的基础上加上此值
sem_op < 0:
如果信号量的值大于等于sem_op绝对值,则信号量的值减去绝对值,如果信号量的值小于绝对值,则挂起操作进程
sem_op = 0:
对信号量的值进行是否为 0 测试。若为 0 则函数立即返回,若不为 0 则阻塞调用进程
sem_flag = IPC_NOWAIT:
在对信号量的操作不能执行的情况下使函数立即返回
sem_flag = SEM_UNDO:
当进程退出后,该进程对信号量进行的操作将被撤销
struct sembuf{
unsigned short sem_num; /*信号量序号*/
short sem_op; /*信号量操作值*/
short sem_flg; /*信号量操作标识*/
};
五、无名信号量多线程应用
1. 无名信号量多线程互斥
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number = 1;
sem_t sem;
void* thread_fun1(void *arg)
{
sem_wait(&sem);
number++;
printf("thread_fun1: number = %d\n", number);
sem_post(&sem);
}
void* thread_fun2(void *arg)
{
sem_wait(&sem);
number--;
printf("thread_fun2: number = %d\n", number);
sem_post(&sem);
}
int main(int argc, char *argv[])
{
pthread_t id1, id2;
sem_init(&sem, 0, 1);
pthread_create(&id1, NULL, thread_fun1, NULL);
pthread_create(&id2, NULL, thread_fun2, NULL);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
sem_destroy(&sem);
return 0;
}
上面的例程,哪个线程先申请到信号量资源是随机的:
2. 无名信号量多线程同步
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number = 1;
sem_t sem1, sem2;
void* thread_fun1(void *arg)
{
sem_wait(&sem1);
number++;
printf("thread_fun1: number = %d\n", number);
sem_post(&sem2);
}
void* thread_fun2(void *arg)
{
sem_wait(&sem2);
number--;
printf("thread_fun2: number = %d\n", number);
sem_post(&sem1);
}
int main(int argc,char *argv[])
{
pthread_t id1, id2;
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 1);
pthread_create(&id1, NULL, thread_fun1, NULL);
pthread_create(&id2, NULL, thread_fun2, NULL);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
如果想按特定顺序执行,可以使用2个信号量实现同步。上面例程约束线程2先执行完,然后线程1才继续执行:
3. 无名信号量多线程同步 - 生产者消费者
有一个长度为N的缓冲池为生产者和消费者所共有
只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息
生产者往缓冲池放信息的时候,消费者不可操作缓冲池,反之亦然
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define BUFF_SIZE 5
char count = 0;
pthread_mutex_t mutex;
sem_t semCanCustom;
sem_t semCanProduct;
void* thread_producer(void* arg)
{
while (1)
{
sem_wait(&semCanProduct);
pthread_mutex_lock(&mutex);
count++;
printf("count %d producer\n", count);
pthread_mutex_unlock(&mutex);
sem_post(&semCanCustom);
sleep(0.5);
}
}
void* thread_consumer(void* arg)
{
while (1)
{
sem_wait(&semCanCustom);
pthread_mutex_lock(&mutex);
count--;
printf("count %d consumer\n", count);
pthread_mutex_unlock(&mutex);
sem_post(&semCanProduct);
sleep(0.5);
}
}
int main()
{
pthread_t ptid, ctid;
pthread_mutex_init(&mutex, NULL);
sem_init(&semCanCustom, 0, 0);
sem_init(&semCanProduct, 0, BUFF_SIZE);
pthread_create(&ptid, NULL, thread_producer, NULL);
pthread_create(&ctid, NULL, thread_consumer, NULL);
pthread_join(ptid, NULL);
pthread_join(ctid, NULL);
pthread_mutex_destroy(&mutex);
sem_destroy(&semCanCustom);
sem_destroy(&semCanProduct);
return 0;
}
4. 无名信号量父子进程互斥
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
int fd, count=0, loop=5;
void* ptr;
pid_t pid;
sem_t sem;
fd = open("log.txt", O_RDWR|O_CREAT, S_IRWXU);
write(fd, &count, sizeof(int));
ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
sem_init(&sem, 1, 1);
pid = fork();
if (0 == pid)
{
for (int i = 0; i < loop; i++)
{
sem_wait(&sem);
printf("child: %d\n", (*(int*)ptr)++);
sleep(rand()%3);
sem_post(&sem);
}
}
else
{
for (int i = 0; i < loop; i++)
{
sem_wait(&sem);
printf("parent: %d\n", (*(int*)ptr)++);
sleep(rand()%3);
sem_post(&sem);
}
}
sem_destroy(&sem);
return 0;
}
5. 无名信号量父子进程同步
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
int fd, count=0, loop=5;
void* ptr;
pid_t pid;
sem_t sem1, sem2;
fd = open("log.txt", O_RDWR|O_CREAT, S_IRWXU);
write(fd, &count, sizeof(int));
ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
sem_init(&sem1, 1, 1);
sem_init(&sem2, 1, 0);
pid = fork();
if (0 == pid)
{
for (int i = 0; i < loop; i++)
{
sem_wait(&sem1);
printf("child: %d\n", (*(int*)ptr)++);
sleep(rand()%3);
sem_post(&sem2);
}
}
else
{
for (int i = 0; i < loop; i++)
{
sem_wait(&sem2);
printf("parent: %d\n", (*(int*)ptr)++);
sleep(rand()%3);
sem_post(&sem1);
}
}
sem_destroy(&sem1);
sem_destroy(&sem2);
return 0;
}
运行结果如下,无打印输出,待调试。。。
六、有名信号量多进程应用
1. 有名信号量进程间互斥
#include<stdio.h>
#include<semaphore.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/types.h>
void printer(sem_t *sem, const char *str)
{
sem_wait(sem);
while (*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
sem_post(sem);
}
int main(int argc, char *argv[])
{
pid_t pid;
sem_t *sem = NULL;
pid = fork();
if (0 == pid)
{
// 不同进程只要名字一样,打开的就是同一个有名信号量
sem = sem_open("wenhchenSem", O_CREAT|O_RDWR, 0666, 1);
const char *str1 = "hello";
printer(sem, str1);
sem_close(sem);
_exit(1);
}
else
{
sem = sem_open("wenhchenSem", O_CREAT|O_RDWR, 0666, 1);
const char *str2 = "world";
printer(sem, str2);
sem_close(sem);
wait(NULL);
}
sem_unlink("wenhchenSem");
return 0;
}
2. 有名信号量进程间同步
// print1.cpp
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print1);
i++;
printf("print1 i = %d\n", i);
sem_post(print2);
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
print1 = sem_open("printSem1", O_CREAT, 0666, 0);
print2 = sem_open("printSem2", O_CREAT, 0666, 1);
print(print1, print2);
return 0;
}
// print2.cpp
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print2);
i++;
printf("print2 i = %d\n", i);
sleep(1);
sem_post(print1);
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
print1 = sem_open("printSem1", O_CREAT, 0666, 0);
print2 = sem_open("printSem2", O_CREAT, 0666, 1);
print(print1, print2);
return 0;
}
当 print1 先启动时,没有打印,只有当 print2 启动后才开始有打印:
七、SYSTEM V进程间互斥
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
int P(int semid)
{
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO;
semop(semid, &sb, 1);
return 0;
}
int V(int semid)
{
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
semop(semid, &sb, 1);
return 0;
}
int main(int argc, char **argv)
{
pid_t pid;
int i, shmid, semid;
int *ptr = NULL;
union semun semopts;
shmid = shmget(0x33, sizeof(int), IPC_CREAT|0600); // 创建一块共享内存, 存一个 int 变量
ptr = (int*)shmat(shmid, NULL, 0); // 将共享内存映射到进程
*ptr = 0; // 赋值为 0
semid = semget(0x44, 1, IPC_CREAT|0600); // 创建一个信号量用来同步共享内存的操作
semopts.val = 1; // 初始化信号量值为 1
semctl(semid, 0, SETVAL, semopts);
pid = fork();
if (0 == pid)
{
for (i = 0; i < 100000; i++)
{
P(semid);
(*ptr)++;
V(semid);
printf("child: %d\n", *ptr);
}
}
else
{
for (i = 0; i < 100000; i++)
{
P(semid);
(*ptr)--;
V(semid);
printf("parent: %d\n", *ptr);
}
wait(NULL);
printf("finally: %d\n", *ptr); // 如果同步成功, 共享内存的值为 0
}
semctl(semid, 0, IPC_RMID); // IPC_RMID 删除信号量集合
return 0;
}