在linxu的多线程机制中,由于可能同时对临界资源进行读写操作,因此互斥锁经常被用来进行互斥操作。
初次之外信号量和环形队列等互斥机制则使用起来更为简单。
一、基于环形队列的生产-消费模型(信号量)
////////////////////////////////////
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#define SIZE 1024
//环形队列
int arr[SIZE] = {0};
sem_t sem_pro; //描述环形队列中的空位置
sem_t sem_con; //描述唤醒队列中的数据
//生产者,只要环形队列有空位,便不断生产
void*productor(void*arg){
int data = 0;
int proIndex = 0;
while(1){
//有空位便生产,没空位便阻塞等消费者消费
sem_wait(&sem_pro);
data = rand()%1234;
arr[proIndex] = data;
printf("product done %d\n",data);
proIndex = (proIndex+1)%SIZE;
//供消费者消费的数据加1
sem_post(&sem_con);
}
}
//消费者,只要环形队列中有数据,就不断消费
void*consumer(void*arg){
int data = 0;
int conIndex = 0;
while(1){
//环形队列中存在数据则消费,不存在数据则阻塞,直到有数据为止
sem_wait(&sem_con);
data = arr[conIndex];
printf("consume done %d\n",data);
conIndex = (conIndex+1)%SIZE;
//最后,消费了一个数据,空位加1
sem_post(&sem_pro);
}
}
int main(){
pthread_t pro,con;
sem_init(&sem_pro,0,SIZE-1); //一开始有很多空位置
sem_init(&sem_con,0,0); //但并没有数据
pthread_create(&pro,NULL,productor,NULL);
pthread_create(&con,NULL,consumer,NULL);
pthread_join(pro,NULL);
pthread_join(con,NULL); //这里为了演示,更好的是设置线程 分离pthread_detach(con)
sem_destroy(&sem_pro); sem_destroy(&sem_con); return 0;}
二、环形队列
队列具有天然的互斥特性,被广泛地应用到linux内核 和python中,可以创建工作队列。另一篇博客 管道的环形设计方法
有详细的介绍。
1. 环形队列
在队列为空时,生产者必须先运行,消费者不能越过生产者
在队列为满时,消费者先运行,生产者不能超过消费者一圈,
不能同时访问同一位置:
- 1
- 2
- 3
- 4
2. 信号量
sem_t sem;//POSIX的线程标准下的
信号量机制通过信号量的值控制可用资源的数量。线程访问共享资源前,需要申请获取一个信号量,如果信号量为0,说明当前无可用的资源,线程无法获取信号量,则该线程会等待其他资源释放信号量(信号量加1)。如果信号量不为0,说明当前有可用的资源,此时线程占用一个资源,对应信号量减1。
举例:
停车场有5个停车位,汽车可使用停车位。在这里5个停车位是共享的资源,汽车是线程。开始信号量为5,表明此时有5个停车位可用。一辆汽车进入停车场前,先查询信号量的值,不为0表明有可用停车位,汽车进入停车场并使用一个停车位,信号量减1,表明占用一个停车位,可用数减少。
信号量初始化函数:
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem: 信号量
pshared:参数表示这个信号量是否在进程的线程之间共享进程之间
value:初始值
nt sem_wait(sem_t *sem);
该函数申请一个信号量,当前无可用信号量则等待,有可用信号量时占用一个信号量,对信号量的值减1。
信号量加1:
int sem_post(sem_t *sem);
该函数释放一个信号量,信号量的值加1。
销毁信号量:
int sem_destory(sem_t *sem);
该函数销毁信号量。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
sem_init(&blankSem, 0, SIZE);//初始化
sem_init(&dataSem, 0, 0);
- 1
- 2
3.临界资源
创建全局数组 int ring[SIZE] = 0;
- 1
- 2
这样信号量不仅可以实现生产者与消费者的同步
,而且可以实现互斥
(两者不可能同时访问同一资源);
1. 基于环形队列的单生产者单消费者模型
void* product(void *arg)//生产者
{
int i = 0;
int n = 0;
while(1)
{
sem_wait(&blankSem);//申请格子放数据
ring[i] = n++;//减一成功后,放数据
sem_post(&dataSem);//数据放完成后,数据数目加1,
printf("product doing! %d\n",ring[i++]);
i = i%SIZE;
}
return NULL;
}
void* consume(void *arg) //消费者
{
int i = 0;
int data = 0;
while(1)
{ sem_wait(&dataSem);//请求拿数据,阻塞方式
data = ring[i]; //
sem_post(&blankSem);//拿到数据后,空格加1
printf("consume doing %d\n",data);
sleep(1);
i++;
i = i%SIZE;//实现环形
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
2. 基于环形队列的多生产者多消费者
模型编写。
信号量只能实现生产者与消费者之间的·互斥·同步·
现在多生产者和消费者,所以需要加入,互斥锁实现生产者与生产者之间的互斥,消费者与消费者之间的互斥;
- 1
- 2
- 3
int i = 0;
int n = 0;
int data = 0;
void* product_1(void *arg)//生产者1
{
while(1)
{
pthread_mutex_lock(&lock_2);
//信号量在生产者之间是临界资源,所以在互斥锁加解之间
sem_wait(&blankSem);//申请放数据
ring[i] = n++;
sem_post(&dataSem);//放完数据,增加数据数目
printf("product_1 doing! %d\n",ring[i++]);
i = i%SIZE;
pthread_mutex_unlock(&lock_2);//解锁
sleep(1);
}
return NULL;
}
void* product_2(void *arg)//生产者2
{
while(1)
{
pthread_mutex_lock(&lock_2);//解锁
sem_wait(&blankSem);
ring[i] = n++;
sem_post(&dataSem);
printf("product_2 doing! %d\n",ring[i++]);
i = i%SIZE;
pthread_mutex_unlock(&lock_2);
sleep(1);
}
return NULL;
}
void* consume_1(void *arg)//消费者1
{
int data = 0;
while(1)
{
pthread_mutex_lock(&lock_1);//解锁
sem_wait(&dataSem);//申请拿数据
data = ring[con];
sem_post(&blankSem);//拿数据后,增加空格数目
printf("consume_1 doing %d\n",data);
sleep(1);
con++;
con = con%SIZE;
pthread_mutex_unlock(&lock_1);//解锁
sleep(2);
}
return NULL;
}
void* consume_2(void *arg)//消费者2
{
int data = 0;
while(1)
{
pthread_mutex_lock(&lock_1);
sem_wait(&dataSem);
data = ring[con];
sem_post(&blankSem);
printf("consume_2 doing %d\n",data);
sleep(1);
con++;
con = con%SIZE;
pthread_mutex_unlock(&lock_1);
sleep(2);
}
return NULL;
}