POSIX信号量与互斥锁(生产者,消费者)

时间:2022-09-22 15:14:13

POSIX信号量相关函数

  POSIX信号量的打开操作跟POSIX消息队列,共享内存的打开方式是一样的

  

  sem_open(打开),sem_close(关闭),sem_unlink(删除一个信号量)

  sem_open - initialize and open a named semaphore

  sem_close - close a named semaphore

  sem_unlink - remove a named semaphore
  其实上面都是对一个有名的信号量进行处理


  那么无名的信号量呢???

  sem_init, sem_destroy

       sem_init - initialize an unnamed semaphore

       sem_destroy - destroy an unnamed semaphore
       如上,是对无名信号量进行处理(初始化,撤销)


       那么要对信号量进行一个PV操作:

  sem_wait(),sem_post()

       既可以用于有名的信号量的操作,也可以用于无名的信号量的操作


  那么无名的信号量是不是意味着它不能用于不同进程的多个线程间进行通信呢???

  实际上,不是这个样子的,无名的信号量能不能用于不同进程的多个线程间进行通信,这取决于第二个参数

  

    #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);

       //如果pshared是nonzero,那么意味着无名信号量可以用于不同进程的多个线程间进行通信
    //当然还是有一个前提的:这个信号量的对象sem要存放在共享内存区当中才可以

   //如果是0,是可以在同一个进程中的多个线程中进行通信的话,那么同样也是不需要存放在共享内存区中的 



POSIX互斥锁相关函数


  pthread_mutex_init(初始化一个互斥锁)

  pthread_mutex_lock(加锁操作)

  pthread_mutex_unlock(解锁操作)

  pthread_mutex_destroy(销毁一个互斥锁)

  同样的,这样的互斥锁也是无名的,也可以用于不同进程的多个线程间进行通信


生产者,消费者问题

我们这里就用信号量和互斥锁来看看生产者,消费者的问题。。。。

                          POSIX信号量与互斥锁(生产者,消费者)



有一个缓冲区(有界的缓冲区,也就是说:一定的容量),对于生产者而言:要生产产品的时候要来判定,当前这个

仓库,这个缓冲区是不是满了,

如果满了,那么生产者将阻塞  p(sem_full),每当我们生产一个产品的时候,这个信号量的计数值就减1

如果仓库还没有满的话,,就意味着我们可以生产产品,一旦生产一个产品,也就说明仓库不是空的状态了

v(sem_empty),此刻就是告诉消费者,现在可以消费了

另外在生产产品的时候,生产者可以有多个,那么一定会对缓冲区进行操作。那么缓冲区也就需要互斥来访问。

所以这里,我们还用到了p(mutex),v(mutex)

如上:就是生产者要做的事情了


生产者可以有多个,那么消费者也可以有多个了

那么消费者要完成的任务就是:

判定一下当前缓冲区(仓库)是不是为空的(p(sem_empty))

如果是空的话,那么它将不能消费产品,它需要等待生产者给消费者一个信号(也就是刚才的v(sem_empty),使得信

号量加1,意味着这边有产品了)

那么有产品的话,我就可以消费产品了

一旦消费了一个产品的话,那么就会使得仓库的容量加1(v(sem_full))

同样的,我们在消费产品的时候,也要互斥的访问 p(mutex),v(mutex)


那么刚开始的时候,我们是可以生产产品的,但是不能消费产品,所以说:sem_full的初始值为仓库的容量(假设为

10)

sem_full(10)

sem_empty(0)

 

#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


#define ERR_EXIT(m)\
	do\
	{\
		perror(m);\
		exit(EXIT_FAILURE);\
	}while(0)

#define CONSUMERS_COUNT 1
#define PRODUCERS_COUNT 5
//两个生产者一个消费者
#define BUFFSIZE 10
//缓冲区的容量10
int g_buffer[BUFFSIZE];

unsigned short in=0;
unsigned short out = 0;
//生产的时候从一个位置放,取的时候从一个位置取
unsigned short produce_id = 0; //当前正在生产产品的id
unsigned short consume_id = 0; //当前正在消费的id也从0开始
//另外这个缓冲区也实现为环形缓冲区

//另外我们还需要定义几个同步对象,两个信号量,一个互斥锁
sem_t g_sem_full;
sem_t g_sem_empty;
pthread_mutex_t g_mutex;

//另外我们还要注意到建立线程(消费者+生产者的个数)
pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT];

void *consume(void *arg)
{
	int i;
	int num = (int)arg;
	while(1)
	{
		printf("%d wait buffer not empty\n", num);
		sem_wait(&g_sem_empty);
		pthread_mutex_lock(&g_mutex);
		for(i = 0; i<BUFFSIZE; i++)
		{
			printf("%02d ", i);
			if(g_buffer[i] == -1)
				printf("%s", "null");
			else 
				printf("%d", g_buffer[i]);
			if(i == out)
				printf("\t<---consume");
			printf("\n");
		}
		consume_id = g_buffer[out];
		printf("%d  begin consume product %d\n",num, consume_id);
		g_buffer[out] = -1;
		out = (out+1)%BUFFSIZE;
		printf("%d  end consume product %d\n",num, consume_id);
		pthread_mutex_unlock(&g_mutex);
		sem_post(&g_sem_full);
		sleep(1);
	}
	return NULL;
}

void *produce(void *arg)
{
	int num = (int)arg;
	int i;
	//不管是生产者还是消费者,都应该在不停的生产消费
	while(1)
	{
		printf("%d wait buffer not full\n", num);
		sem_wait(&g_sem_full);
		//等待一个满的信号量,如果不满我们就可以生产了

		pthread_mutex_lock(&g_mutex);
		//加一个锁的机制
                //接下来就是生产产品
		//先打印仓库当前的状态
		for(i = 0; i<BUFFSIZE; i++)
		{
			printf("%02d ", i);
			if(g_buffer[i] == -1)
				printf("%s", "null");
			else 
			        printf("%d", g_buffer[i]);
			if(i == in)  //那就是我们生产产品的地方
			{
				printf("\t<---produce");
			}
			printf("\n");
		}
		//生产产品
		printf("%d begin produce product %d\n",num, produce_id);
		g_buffer[in] = produce_id;
		in = (in + 1)%BUFFSIZE;
		printf("%d end produce product %d\n",num, produce_id++);

		pthread_mutex_unlock(&g_mutex);		


		//生产了一个产品,v一个空的信号量
		sem_post(&g_sem_empty);
		sleep(5);
	}
	return NULL;
}

int main(void) 
{

        int i;
	for(i = 0; i< BUFFSIZE; i++)
		g_buffer[i] = -1;
	sem_init(&g_sem_full, 0, BUFFSIZE);
	sem_init(&g_sem_empty, 0, 0);
        //初始化这些信号量
	pthread_mutex_init(&g_mutex, NULL);
        //也可以用于多个进程间的不同线程之间进行通信,取决于第一个对象是不算在
        //共享内存区,当前不是在共享内存区,表明只能在同一个进程的不同线程间通信

	for(i = 0; i<CONSUMERS_COUNT; i++)
	{
		pthread_create(&g_thread[i], NULL, consume, (void*)i);
	}
	//创建若干个消费者线程
	for(i = 0; i< PRODUCERS_COUNT; i++)
		pthread_create(&g_thread[CONSUMERS_COUNT+i], NULL, produce, (void*)i);
	//创建若干个生产者线程
	for(i = 0; i< CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
		pthread_join(g_thread[i], NULL);
        //等待所创建的线程执行完成

	sem_destroy(&g_sem_full);
	sem_destroy(&g_sem_empty);
	pthread_mutex_destroy(&g_mutex);
	//销毁这些信号量和锁
	return 0;
}

运行结果:

POSIX信号量与互斥锁(生产者,消费者)


自旋锁与读写锁

自旋锁类似与互斥锁,但是,它的性能比互斥锁更高,

一般来说:对于实时性要求比较高的话,才会应用自旋锁

自旋锁跟互斥锁:一个很重要的区别在于,线程在申请自旋锁的时候,线程不会被挂起,即使线程不能得到锁,

它处于忙等待状态(也就是说:cpu处于忙碌的状态)不断的申请锁

所以说:自旋锁不能用于等待时间长的应用,一般来说应用与等待时间短的应用(一些实时性要求高的应用)

如果等待时间长的话,cpu处于等待的状态,在浪费cpu


  pthread_spin_init

  pthread_spin_lock

  pthread_spin_unlock

  pthread_spin_destroy


读写锁

读写锁也是在跟互斥锁进行比较:只要没有线程持有给定的读写锁用于写,那么任意数目的线程可以持有读写锁用于

读,(也就是说:只要没有线程以写的方式锁定了该锁,那么其它线程可以以读的方式锁定该锁)

读写锁用于读的话称为共享锁,(也就是说:一个线程锁定了共享锁,那么其它线程也是可以持有共享锁的)

读写索用于写的话称为排它锁,

一个线程不管是读的锁,还是写的锁,这个时候不允许其它线程对该锁进行写的操作,只要有一个线程加了读写锁,

那么这个时候我们就不能在施加排它锁  。。。。。


  POSIX信号量与互斥锁(生产者,消费者)