信号量用于不同进程之间的同步,有POSIX信号量和Sysetm V信号量。

二值信号量是值为0或1的信号量。

进程通信(6):POSIX信号量_初始化

POSIX信号量分为有名信号量和无名信号量。

有名信号量使用POSIX ipc名字标识,无名信号量把信号量使用共享内存实现。

信号量的三种操作:

创建:初始化信号量初值。

P:信号量值减1,减到0后阻塞等待。

V:如果信号量的阻塞队列有进程等待,就唤醒进程,否则信号量值加1。

互斥锁和条件变量区别:

(1)互斥锁必须由给它上锁的线程解锁,信号量可以在不同线程之间等待和释放。

(2)互斥锁只有两种状态(类似二值信号量)

POSIX信号量相关函数调用

进程通信(6):POSIX信号量_linux_02

POSIX信号量在linux中最小值为0。当信号量值为0时,进程会被添加到等待队列中。

进程通信(6):POSIX信号量_信号量_03

生产者消费者问题:

(1)一个生产者和一个消费者存取循环缓冲区.

定义一个shared结构体,里面有一个缓冲区,和3个POSIX信号量。

struct {	/* data shared by producer and consumer */
  int	buff[NBUFF];
  sem_t	*mutex, *nempty, *nstored;
} shared;
  • 1.
  • 2.
  • 3.
  • 4.

mutex是二值信号量,用于互斥访问缓冲区。

nempty表示初始资源的个数,nstored表示初始存取的个数。

定义两个线程,一个是生产者,一个是消费者,初始化信号量(这里使用的是有名信号量,根据一个路径字符串初始化信号量的值。也可以使用sem_init和sem_destory创建基于内存的信号量),mutex初始化为1,nempty初始化为NBUFF,nstored为0。然后创建两个线程,最后使用pthread_join等待线程结束,然后释放信号量。

void	*produce(void *), *consume(void *);
int main(int argc, char **argv)
{
	pthread_t tid_produce, tid_consume;
	shared.mutex = Sem_open(SEM_MUTEX, O_CREAT | O_EXCL,
							FILE_MODE, 1);
	shared.nempty = Sem_open(SEM_NEMPTY, O_CREAT | O_EXCL,
							 FILE_MODE, NBUFF);
	shared.nstored = Sem_open(SEM_NSTORED, O_CREAT | O_EXCL,
							  FILE_MODE, 0);
							  
	Pthread_create(&tid_produce, NULL, produce, NULL);
	Pthread_create(&tid_consume, NULL, consume, NULL);
	Pthread_join(tid_produce, NULL);
	Pthread_join(tid_consume, NULL);
	Sem_unlink(SEM_MUTEX);
	Sem_unlink(SEM_NEMPTY);
	Sem_unlink(SEM_NSTORED);
	exit(0);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

生产者代码:

void *produce(void *arg)
{
	int	i;
	for (i = 0; i < nitems; i++) {
		Sem_wait(shared.nempty);	/* wait for at least 1 empty slot */
		Sem_wait(shared.mutex);
		shared.buff[i % NBUFF] = i;	/* store i into circular buffer */
		Sem_post(shared.mutex);
		Sem_post(shared.nstored);	/* 1 more stored item */
	}
	return(NULL);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

(nitems不一定比NBUFF小,但是使用循环缓冲区,就不会溢出) 

消费者代码:

void *consume(void *arg){
	int	i;
	for (i = 0; i < nitems; i++) {
		Sem_wait(shared.nstored);		/* wait for at least 1 stored item */
		Sem_wait(shared.mutex);
		if (shared.buff[i % NBUFF] != i)
			printf("buff[%d] = %d\n", i, shared.buff[i % NBUFF]);
		Sem_post(shared.mutex);
		Sem_post(shared.nempty);		/* 1 more empty slot */
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

上面代码只有一个消费者和一个生产者,并且消费者的下标通过信号量机制不会超过生产者的下标。当消费者的下标没有数据时,就会在信号量上等待。

(2)多个生产者,一个消费者存取一个缓冲区

int		nitems, nproducers;		/* read-only by producer and consumer */
struct {	/* data shared by producers and consumer */
  int	buff[NBUFF];
  int	nput;
  int	nputval;
  sem_t	mutex, nempty, nstored;		/* semaphores, not pointers */
} shared;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

nitems是要存取的个数,buff表示缓冲区,nput  和 nputval 表示已经存放的个数和下一个存放的值,三个信号量作用和(1)相同。

void	*produce(void *), *consume(void *);
int main(int argc, char **argv)
{
	int		i, count[MAXNTHREADS];
	pthread_t	tid_produce[MAXNTHREADS], tid_consume;
	Sem_init(&shared.mutex, 0, 1);
	Sem_init(&shared.nempty, 0, NBUFF);
	Sem_init(&shared.nstored, 0, 0);
	for (i = 0; i < nproducers; i++) {
		count[i] = 0;
		Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
	}
	Pthread_create(&tid_consume, NULL, consume, NULL);
	for (i = 0; i < nproducers; i++) {
		Pthread_join(tid_produce[i], NULL);
		printf("count[%d] = %d\n", i, count[i]);	
	}
	Pthread_join(tid_consume, NULL);
	Sem_destroy(&shared.mutex);
	Sem_destroy(&shared.nempty);
	Sem_destroy(&shared.nstored);
	exit(0);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

初始化多个生产者线程和一个消费者线程,然后初始化3个信号量(这里用到的是基于内存的信号量),同(1)。然后创建多个生产者,和一个消费者,执行join等线程结束时,最后销毁信号量。

void *
produce(void *arg)
{
	for ( ; ; ) {
		Sem_wait(&shared.nempty);	/* wait for at least 1 empty slot */
		Sem_wait(&shared.mutex);
		if (shared.nput >= nitems) {
			Sem_post(&shared.nempty);
			Sem_post(&shared.mutex);
			return(NULL);			/* all done */
		}
		shared.buff[shared.nput % NBUFF] = shared.nputval;
		shared.nput++; shared.nputval++;

		Sem_post(&shared.mutex);
		Sem_post(&shared.nstored);	/* 1 more stored item */
		*((int *) arg) += 1;
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

生产者线程:资源信号量减1,然后互斥锁上锁,如果已经完成生产,就要释放已经获取的信号量;否则生产一个数据到缓冲区,然后更新nput,释放信号量。

有多个生产者,但是因为有mutex二值信号量上锁,所以不会出现多个生产者同时访问nput和nval的情况。

void *
consume(void *arg)
{
	int		i;

	for (i = 0; i < nitems; i++) {
		Sem_wait(&shared.nstored);		/* wait for at least 1 stored item */
		Sem_wait(&shared.mutex);

		if (shared.buff[i % NBUFF] != i)
			printf("error: buff[%d] = %d\n", i, shared.buff[i % NBUFF]);

		Sem_post(&shared.mutex);
		Sem_post(&shared.nempty);		/* 1 more empty slot */
	}
	return(NULL);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

消费者和(1)相同。

(3)多个消费者和多个生产者存取一个缓冲区

在(2)的基础上加上nget和ngetval

struct {	/* data shared by producers and consumers */
  int	buff[NBUFF];
  int	nput;			/* item number: 0, 1, 2, ... */
  int	nputval;		/* value to store in buff[] */
  int	nget;			/* item number: 0, 1, 2, ... */
  int	ngetval;		/* value fetched from buff[] */
  sem_t	mutex, nempty, nstored;		/* semaphores, not pointers */
} shared;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
void	*produce(void *), *consume(void *);
int main(int argc, char **argv)
{
	int		i, prodcount[MAXNTHREADS], conscount[MAXNTHREADS];
	pthread_t	tid_produce[MAXNTHREADS], tid_consume[MAXNTHREADS];
	Sem_init(&shared.mutex, 0, 1);
	Sem_init(&shared.nempty, 0, NBUFF);
	Sem_init(&shared.nstored, 0, 0);
	for (i = 0; i < nproducers; i++) {
		prodcount[i] = 0;
		Pthread_create(&tid_produce[i], NULL, produce, &prodcount[i]);
	}
	for (i = 0; i < nconsumers; i++) {
		conscount[i] = 0;
		Pthread_create(&tid_consume[i], NULL, consume, &conscount[i]);
	}

	for (i = 0; i < nproducers; i++) {
		Pthread_join(tid_produce[i], NULL);
		printf("producer count[%d] = %d\n", i, prodcount[i]);	
	}
	for (i = 0; i < nconsumers; i++) {
		Pthread_join(tid_consume[i], NULL);
		printf("consumer count[%d] = %d\n", i, conscount[i]);	
	}

	Sem_destroy(&shared.mutex);
	Sem_destroy(&shared.nempty);
	Sem_destroy(&shared.nstored);
	exit(0);
}
  • 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.
  • 30.
  • 31.

创建过程和(3)基本相同。