参考:
https://blog.csdn.net/benjamin721/article/details/50703692(进程/线程通信:信号量和互斥量总结)
https://blog.csdn.net/qq_46275568/article/details/120630019(信号量及其应用)
https://www.jianshu.com/p/6674400a98b2(信号量与PV操作)
信号量的引入
由于对互斥量的操作在cpu执行时不是原子性的,如果多个进程/线程同时访问互斥量,被cpu分解为一个一个指令时有重叠,就会出问题。由此引入信号量!
信号量:
信号量s是非负整数值的全局变量。只能有两种特殊的操作,这两种操作称为P和V,(再加上一个信号量的初始化)
P(s):如果 s 是非零的,那么 P 将 s 减 1,并且立即返回。(如果 s 为零,那么就挂起这个线程,直到 s 变为非零)。 V(s): V 操作将 s 加 1。(如果有任何线程阻塞在 Р 操作等待s变成非零,那么 V 操作会重启这些线程中的一个)。
用PV操作解决并发互斥问题
用PV操作,解决进程间互斥问题有以下4点:
1.划定临界区
2.对多个进程的临界区,设定一个信号量mutex。mutex这个信号量,初值为1。
1.划定临界区
2.对多个进程的临界区,设定一个信号量mutex。mutex这个信号量,初值为1。
简单说明一下mutex,mutex是常用于解决互斥问题的时候所定义的一个信号量变量。是互斥(mutual exclusion)的缩写。现在已经形成一种约定俗成的互斥的命名。
3.在临界区前实施P(mutex)
4.在临界区之后实施V(mutex)
3.在临界区前实施P(mutex)
4.在临界区之后实施V(mutex)
信号量与互斥锁的区别:
1. 互斥锁用于线程的互斥,信号量用于线程的同步
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
2. 互斥锁量值只能为0/1,信号量值可以为非负整数。
3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
二值信号量也可以理解为互斥锁:show me the code
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<semaphore.h> // global shared varible volatile int cnt = 0; // counter sem_t mutex; void P(sem_t* s) {sem_wait(s);} void V(sem_t* s) {sem_post(s);} // thread routine void *thread(void* arg) { int i, niters = *((int*)arg); for(int i = 0; i < niters; ++i) { P(&mutex); cnt++; V(&mutex); } return NULL; } int main(int argc, char* argv[]) { int niters; pthread_t tid1, tid2; sem_init(&mutex, 0, 1); // mutex = 1,初始化为1,并且先P后V,那么mutex只可能市0或1,即为二值信号量 if(argc != 2) { printf("usage: %s <niters>\n", argv[0]); exit(0); } niters = atoi(argv[1]); // create thread and wait for them to finish pthread_create(&tid1, NULL, thread, &niters); pthread_create(&tid2, NULL, thread, &niters); pthread_join(tid1, NULL); pthread_join(tid2, NULL); printf("cnt = %d\n", cnt); return 0; }
生产者消费者模型(有界缓冲区问题)
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> #include<semaphore.h> #include<time.h> typedef struct { int *buf; int n; int front; // 指向第一个元素前面的位置 int rear; // 指向最后一个元素 sem_t mutex; sem_t slots; sem_t items; }sbuf_t; // PV操作 void P(sem_t *s){sem_wait(s);} void V(sem_t *s){sem_post(s);} // 创建一个大小为 n 个槽,空的缓冲区 void sbuf_init(sbuf_t *sp, int n) { sp->buf = (int*)calloc(n, sizeof(int)); sp->n = n; sp->front = sp->rear = 0; sem_init(&sp->mutex, 0, 1); sem_init(&sp->slots, 0, n); sem_init(&sp->items, 0, 0); } // 释放缓冲区 void sbuf_deinit(sbuf_t *sp) { free(sp->buf); } // 向缓冲区中加入 item void sbuf_insert(sbuf_t *sp, int item) { P(&sp->slots); P(&sp->mutex); sp->buf[++sp->rear % sp->n] = item; V(&sp->mutex); V(&sp->items); } // 从缓冲区中取出 item int sbuf_remove(sbuf_t *sp) { int ret; P(&sp->items); P(&sp->mutex); ret = sp->buf[(++sp->front) % sp->n]; V(&sp->mutex); V(&sp->slots); return ret; } void *producer1(void* arg) { while(1) { usleep(300); int num = rand() % 10000; sbuf_insert((sbuf_t*)arg, num); printf("producer1 生产:%d\n", num); } return NULL; } void *consumer1(void* arg) { while(1) { sleep(1); printf("consumer1 消费 %d\n", sbuf_remove((sbuf_t*)arg)); } return NULL; } void *consumer2(void* arg) { while(1) { sleep(2); printf("consumer2 消费 %d\n", sbuf_remove((sbuf_t*)arg)); } return NULL; } int main() { pthread_t tid1, tid2, tid3; sbuf_t sbuf; srand(time(NULL)); sbuf_init(&sbuf, 5); pthread_create(&tid1, NULL, producer1, &sbuf); pthread_create(&tid2, NULL, consumer1, &sbuf); pthread_create(&tid3, NULL, consumer2, &sbuf); pthread_join(tid1, NULL); pthread_join(tid2, NULL); pthread_join(tid3, NULL); sbuf_deinit(&sbuf); return 0; }