UNP卷二 chapter7 互斥锁和条件变量

为允许在线程或进程间共享数据,同步通常是必需的。互斥锁和条件变量是同步的基本组成部分。

1、互斥锁:上锁与解锁

此处小标题内容已在UNPv1第26章线程讲解中有所记录。

互斥锁指代相互排斥,是最基本的同步形式。互斥锁用于保护临界区,以保证任何时刻只有一个线程在执行其中的代码,或只有一个进程在执行其中的代码。

Posix互斥锁被声明为具有pthread_mutex_t数据类型的变量。有两种分配方式:

i、互斥锁变量是静态分配的,可以将其初始化为常值PTHREAD_MUTEX_INITIALIZER,如下所示,

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//采用静态分配初始化为常值

ii、互斥锁变量是动态分配的,或者分配在共享内存区,则必须在运行时通过调用pthread_mutex_init函数(具体内容见下面小标题)初始化。

三个函数给一互斥锁上锁和解锁:

#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr);
int pthread_mutex_tyelock(pthread_mutex_t *mptr);
int pthread_mutex_unlock(pthread_mutex_t *mptr);
				//均返回:若成功则为0,若出错则为正的Exxx值

如果尝试给一个已由另外某个线程锁住的互斥锁上锁,那么pthread_mutex_lock将阻塞到该互斥锁解锁为止pthread_mutex_trylock是对应的非阻塞函数,如果该互斥锁已锁住,它就返回一个EBUSY错误

互斥锁通常用于保护由多个线程或多个进程分享的共享数据。

2、生产者-消费者问题

生产者-消费者问题也称为有界缓冲区问题:一个或多个生产者(线程或进程)创建一个个的数据条目,然后这些条目由一个或多个消费者(线程或进程)处理。数据条目在生产者和消费者之间是使用某种类型的IPC传递的。

针对管道或消息队列而言,主要是由内核隐式处理同步问题(生产者超前消费者,供过于求,消费者超前生产者,供不应求)。

针对共享内存区用作生产者消费者之间的IPC形式时,生产者或消费者必须执行某种类型的显式同步,利用互斥锁+条件变量

i、生产者先完成生产,消费者再获取条目使用

#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

int		nitems;			/* read-only by producer and consumer */
struct {
  pthread_mutex_t	mutex;
  int	buff[MAXNITEMS];
  int	nput;//buff数组中下一次存放的元素下标
  int	nval;//下一次存放的值
} shared = { PTHREAD_MUTEX_INITIALIZER };

void	*produce(void *), *consume(void *);

int
main(int argc, char **argv)
{
	int			i, nthreads, count[MAXNTHREADS];
	pthread_t	tid_produce[MAXNTHREADS], tid_consume;

	if (argc != 3)
		err_quit("usage: prodcons2 <#items> <#threads>");//指定生产的条目数及待创建生产者线程的数目
	nitems = min(atoi(argv[1]), MAXNITEMS);
	nthreads = min(atoi(argv[2]), MAXNTHREADS);

	Set_concurrency(nthreads);//告诉系统我们希望并发运行多少线程(只针对salaris系统有用,Unix下的线程本身是采用竞争形式)
		/* 4start all the producer threads */
	for (i = 0; i < nthreads; i++) {
		count[i] = 0;
		Pthread_create(&tid_produce[i], NULL, produce, &count[i]);//创建生产者线程
	}

		/* 4wait for all the producer threads */
	for (i = 0; i < nthreads; i++) {
		Pthread_join(tid_produce[i], NULL);//等待所有生产者线程终止,有点类似于进程中waipid函数的作用
		printf("count[%d] = %d\n", i, count[i]);//输出每个线程的计数器值	
	}

		/* 4start, then wait for the consumer thread */
	Pthread_create(&tid_consume, NULL, consume, NULL);//创建单个消费者线程
	Pthread_join(tid_consume, NULL);//等待消费者线程终止

	exit(0);
}

/* include producer */
void *
produce(void *arg)//在生产者中利用互斥锁解决同步问题
{
	for ( ; ; ) {
		Pthread_mutex_lock(&shared.mutex);//上锁
		if (shared.nput >= nitems) {
			Pthread_mutex_unlock(&shared.mutex);
			return(NULL);		/* array is full, we're done */
		}
		shared.buff[shared.nput] = shared.nval;
		shared.nput++;
		shared.nval++;
		Pthread_mutex_unlock(&shared.mutex);//解锁
		*((int *) arg) += 1;//相应生产者计数器+1
	}
}

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

	for (i = 0; i < nitems; i++) {
		if (shared.buff[i] != i)
			printf("buff[%d] = %d\n", i, shared.buff[i]);
	}
	return(NULL);
}
/* end producer */

ii、生产者与消费者同时启动

这方式是生产者线程产生数据的同时,消费者线程就能处理它。具体代码如下:

#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

int		nitems;			/* read-only by producer and consumer */
struct {
  pthread_mutex_t	mutex;
  int	buff[MAXNITEMS];
  int	nput;
  int	nval;
} shared = { PTHREAD_MUTEX_INITIALIZER };

void	*produce(void *), *consume(void *);

/* include main */
int
main(int argc, char **argv)
{
	int			i, nthreads, count[MAXNTHREADS];
	pthread_t	tid_produce[MAXNTHREADS], tid_consume;

	if (argc != 3)
		err_quit("usage: prodcons3 <#items> <#threads>");
	nitems = min(atoi(argv[1]), MAXNITEMS);
	nthreads = min(atoi(argv[2]), MAXNTHREADS);

		/* 4create all producers and one consumer */
	Set_concurrency(nthreads + 1);//只处+1是增加了一个consumer线程
	for (i = 0; i < nthreads; i++) {
		count[i] = 0;
		Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
	}
	Pthread_create(&tid_consume, NULL, consume, NULL);

		/* 4wait for all producers and the consumer */
	for (i = 0; i < nthreads; i++) {
		Pthread_join(tid_produce[i], NULL);
		printf("count[%d] = %d\n", i, count[i]);	
	}
	Pthread_join(tid_consume, NULL);

	exit(0);
}
/* end main */

void *
produce(void *arg)
{
	for ( ; ; ) {
		Pthread_mutex_lock(&shared.mutex);
		if (shared.nput >= nitems) {
			Pthread_mutex_unlock(&shared.mutex);
			return(NULL);		/* array is full, we're done */
		}
		shared.buff[shared.nput] = shared.nval;
		shared.nput++;
		shared.nval++;
		Pthread_mutex_unlock(&shared.mutex);
		*((int *) arg) += 1;
	}
}

/* include consume */
void
consume_wait(int i)
{
	for ( ; ; ) {
		Pthread_mutex_lock(&shared.mutex);
		if (i < shared.nput) {//处理生产者已生成的条目
			Pthread_mutex_unlock(&shared.mutex);
			return;			/* an item is ready */
		}
		Pthread_mutex_unlock(&shared.mutex);
	}
}

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

	for (i = 0; i < nitems; i++) {
		consume_wait(i);
		if (shared.buff[i] != i)
			printf("buff[%d] = %d\n", i, shared.buff[i]);
	}
	return(NULL);
}
/* end consume */

自己对上述的程序consume_wait函数的理解(很通俗的话):当生产条目只到3000,而消费到3500时,此时消费者获取到cpu资源时,上锁,但由于供不应求,导致解锁释放cpu资源,同时进入下一次循环,由于互斥锁的存在,阻塞在等待获取cpu资源。不断轮询直至可以消费相应生产条目时才return。

3、条件变量:等待与信号发送

互斥锁用于上锁,条件变量则用等待,两者协同合作,为同步提供优质服务哈。

#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
//此函数特点:将调用线程投入睡眠并释放调用线程持有的互斥锁,返回时,线程再次持有互斥锁

int pthread_cond_signal(pthread_cond_t *cptr);
//通过给予条件信号,唤醒正在睡眠中的线程
                               //均返回:若成功则为0,若出错则为正的Exxx值

利用互斥锁+条件变量改写生产者-消费者问题(避免了轮询而获取cpu资源):

/* include globals */
#include	"unpipc.h"

#define	MAXNITEMS 		1000000
#define	MAXNTHREADS			100

		/* globals shared by threads */
int		nitems;				/* read-only by producer and consumer */
int		buff[MAXNITEMS];
struct {
  pthread_mutex_t	mutex;
  int				nput;	/* next index to store */
  int				nval;	/* next value to store */
} put = { PTHREAD_MUTEX_INITIALIZER };

struct {
  pthread_mutex_t	mutex;
  pthread_cond_t	cond;
  int				nready;	/* number ready for consumer */
} nready = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER };
/* end globals */

void	*produce(void *), *consume(void *);

/* include main */
int
main(int argc, char **argv)
{
	int			i, nthreads, count[MAXNTHREADS];
	pthread_t	tid_produce[MAXNTHREADS], tid_consume;

	if (argc != 3)
		err_quit("usage: prodcons6 <#items> <#threads>");
	nitems = min(atoi(argv[1]), MAXNITEMS);
	nthreads = min(atoi(argv[2]), MAXNTHREADS);

	Set_concurrency(nthreads + 1);
		/* 4create all producers and one consumer */
	for (i = 0; i < nthreads; i++) {
		count[i] = 0;
		Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
	}
	Pthread_create(&tid_consume, NULL, consume, NULL);

		/* wait for all producers and the consumer */
	for (i = 0; i < nthreads; i++) {
		Pthread_join(tid_produce[i], NULL);
		printf("count[%d] = %d\n", i, count[i]);	
	}
	Pthread_join(tid_consume, NULL);

	exit(0);
}
/* end main */

/* include prodcons */
void *
produce(void *arg)
{
	for ( ; ; ) {
		Pthread_mutex_lock(&put.mutex);
		if (put.nput >= nitems) {
			Pthread_mutex_unlock(&put.mutex);
			return(NULL);		/* array is full, we're done */
		}
		buff[put.nput] = put.nval;
		put.nput++;
		put.nval++;
		Pthread_mutex_unlock(&put.mutex);

		Pthread_mutex_lock(&nready.mutex);
		if (nready.nready == 0)
			Pthread_cond_signal(&nready.cond);//此处给予条件信号,但代码仍处临界区,接收到条件信号的线程可以被唤醒,但还需要等待获取互斥锁资源
		nready.nready++;
		Pthread_mutex_unlock(&nready.mutex);

		*((int *) arg) += 1;
	}
}

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

	for (i = 0; i < nitems; i++) {
		Pthread_mutex_lock(&nready.mutex);
		while (nready.nready == 0)//表时缓存池中可消费条目为0,消费者需要投入睡眠
			Pthread_cond_wait(&nready.cond, &nready.mutex);//投入睡眠,释放互斥锁,,记住,被唤醒后还要获取互斥锁资源才能继续下面代码
		nready.nready--;
		Pthread_mutex_unlock(&nready.mutex);

		if (buff[i] != i)
			printf("buff[%d] = %d\n", i, buff[i]);
	}
	return(NULL);
}
/* end prodcons */

以下给出条件变量发送信号代码的大体套路:

struct {
	pthread_mutex_t mutex;
	pthread_cond_t	cond;
	/*维护本件的各个变量*/
}var = {PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER,...};

pthread_mutex_lock(&var.mutex);//上锁
/*设置条件为真*/
pthread_cond_signal(&var.cond);//给予条件信号
pthread_mutex_unlock(&var.mutex);//解锁

以下给测试条件并进入睡眠以等待该条件变为真的代码大体套路:

pthread_mutex_lock(&var.mutex);
while (/*条件为假*/)
pthread_cond_wait(&var.cond, &var.mutex);
/*修改条件*/
pthread_mutex_unlock(&var.mutex);

避免上锁冲突

Posix明确允许:调用pthread_cond_signal的线程不必是与之关联的互斥锁的当前属主。但如果需要可预见的调度行为,那么调用pthread_cond_signal的线程必须锁住该互斥锁。

4、互斥锁和条件变量的属性

两个常值PTHREAD_MUTEX_INITIALIZER和PTHREAD_COND_INITIALIZER来初始化互斥锁和条件变量,此方式具备默认属性,但还可以以非默认属性初始化它们。

i、互斥锁和条件变量用以下函数初始化或摧毁:

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *mptr, const pthread_mutexattr_t *attr);//初始化互斥锁(属性)
int pthread_mutex_destroy(pthread_mutex_t *mptr);//摧毁
int pthread_cond_init(pthread_cond_t *cptr, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cptr);
							//均返回:若成功则为0,若出错则为正的Exxx值

参数attr指向的pthread_mutex_t或pthread_condattr_t值指定其属性。如果该参数是个空指针,那就使用默认属性。

ii、互斥锁或条件变量属性由以下函数初始化或摧毁:

#include<pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_condattr_init(pthread_condattr *attr);
int pthread_ocndattr_destroy(pthread_condattr *attr);
							//均返回:若成功则为0,若出错则为正的Exxx值

此处需要对互斥锁或条件变量的属性进行初始化或摧毁可理解成,初始化即获取动态内存,摧毁即释放动态内存。(事实也是这样)

iii、既然初始化了互斥锁或条件变量的属性,就要用函数结合相应参数去设置或获取相应属性。以下只介绍一个需要使用的属性值设置:指定互斥锁或条件变量在不同进程间共享,而不是只在单个进程内的不同线程间共享。

#include<pthread.h>
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *valptr);
int pthread_mutexattr_setpshared(const pthread_mutexattr_t *attr, int value);
int pthread_condattr_getshared(const pthread_condattr_t *attr, int *valptr);
int pthread_condattr_setshared(const pthread_condattr_t *attr, int value);
							//均返回:若成功则为0,若出错则为正的Exxx值

上述函数中参数value可以是PTHREAD_PROCESS_PRIVATE或PTHREAD_PROCESS_SHARED,后者也称为进程间共享属性。

运用样例:

pthread_mutex_t	*mptr;//pointer to the mutex in shared memory
pthread_mutexattr_t	mattr;//mutex attribute datatype

mptr =/* some value that points to shared memory */;
pthread_mutexattr_init(&mattr);//锁属性初始化,即动态分配内存
#ifdef _POSIX_THREAD_PROCESS_SHARED
	pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);//对互斥锁设置进程间共享属性
#else
# error this implementation does not support _POSIX_THREAD_PROCESS_SHARED
#endif
pthread_mutex_init(mptr, &mattr);//对锁进行初始化

此外还有一个问题:持有锁期间进程终止(参见书上P138)

以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值