【IPC-UNIX网络编程】第7章互斥锁和条件变量

1 互斥锁:上锁与解锁

  • 互斥锁用于保护临界区(的共享数据),以保证任何时刻只有一个线程在执行其中的代码。

    lock_the_mutex(...);
    临界区
    unlock_the_mutex(...);
    
  • 下列3个函数给一个互斥锁上锁和解锁:

    #include <pthread.h>
    // 成功则返回0,否则返回证得Exxx值
    int pthread_mutex_lock(pthread_mutext_t *mptr);
    int pthread_mutex_trylock(pthread_mutex_t *mptr);
    int pthread_mutex_unlock(pthread_mutex_t *mptr);
    

    若尝试给一个已由另外某个线程锁住的互斥锁上锁,则pthread_mutex_lock将阻塞到该互斥锁解锁为止。

2 生产者-消费者问题

  • 生产者-消费者问题,即有界缓冲区问题。一个或多个生产者(进程或线程)创建一个个数据条目,然后由一个或多个消费者(线程或进程)处理。数据条目在生产者和消费者之间使用某种类型的IPC传递
  • 若生产者超前消费者(即管道被填满),内核就在生产者调用write时把它置于休眠状态,直到管道中有空余空间;反之,亦然。
  • 隐式同步:【上述即为隐式同步】生产者与消费者不知道内核在执行同步。【Posix消息队列、System V消息队列】
  • 显式同步【共享内存区】
生产者-消费者例子:多个生产者线程、单个消费者线程
  • Set_concurrency(nthreads):告诉线程系统我们希望并发运行多少线程
#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;   // nput是buff数组中下一次存放的元素下标
	int nval;   // nval是下一次存放的值(0、1、2等)
} shared = {
    PTHREAD_MUTEX_INITIALIZER
};

// 在main函数前先声明
void *produce(void *), *consume(void *);

int main(int argc, char const *argv[])
{
	int i, nthreads, count[MAXNTHREADS];
	// 在tid_produce保存每个线程的线程ID
	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);
	// start all the producer threads
	for (i = 0; i < nthreads; ++i)
	{
		count[i] = 0;   // 计数器初始化为0,若线程存放数据条目,则加1
		Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
	}

	// wait for all the producer threads
	for (i = 0; i < nthreads; ++i)
	{
		// 等待所有生产者线程终止,同时输出每个线程的计数器值
		Pthread_join(tid_produce[i], NULL);
		printf("count[%d] = %d\n", i, count[i]);
	}

	// start, then wait for the consumer thread
	Pthread_create(&tid_consume, NULL, consume, NULL);
	Pthread_join(tid_consume, NULL);
	exit(0);
}

void *produce(void *arg) 
{
	while (true) {
		pthread_mutex_lock(&shared.mutex);
		if (shared.nput >= nitems) {
			Pthread_mutex_unlock(&shared.mutex);
			return(NULL);
		}
		shared.buff[shared.nput] = shared.nval;
		shared.nput++;
		shared.nval++;
		Pthread_mutex_unlock(&shared.mutex);
		// count 元素的增加不属于临界区,因为每个线程有各自的计数器(main函数的count数组)
		*((int *) arg) += 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);
}

3 对比上锁与等待

  • 使用互斥锁,使得生产者产生数据的同时,消费者线程就能处理它。

    #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;   // nput是buff数组中下一次存放的元素下标
    	int nval;   // nval是下一次存放的值(0、1、2等)
    } shared = {
        PTHREAD_MUTEX_INITIALIZER
    };
    
    // 在main函数前先声明
    void *produce(void *), *consume(void *);
    
    int main(int argc, char const *argv[])
    {
    	int i, nthreads, count[MAXNTHREADS];
    	// 在tid_produce保存每个线程的线程ID
    	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);  // 指定待创建生产者的数目
    
    	// Create all producers and one consumer	 
    	Set_concurrency(nthreads + 1);
    	// start all the producer threads
    	for (i = 0; i < nthreads; ++i)
    	{
    		count[i] = 0;   // 计数器初始化为0,若线程存放数据条目,则加1
    		Pthread_create(&tid_produce[i], NULL, produce, &count[i]);
    	}
    	Pthread_create(&tid_consume, NULL, consume, NULL);
    	// wait for all the producer threads
    	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);
    }
    
    void *produce(void *arg) 
    {
    	while (true) {
    		pthread_mutex_lock(&shared.mutex);
    		if (shared.nput >= nitems) {
    			Pthread_mutex_unlock(&shared.mutex);
    			return(NULL);
    		}
    		shared.buff[shared.nput] = shared.nval;
    		shared.nput++;
    		shared.nval++;
    		Pthread_mutex_unlock(&shared.mutex);
    		*((int *) arg) += 1;
    	}
    }
    
  • 必须等到生产者生产了第i个条目,否则一直while循环,即轮询。

    当生产者未生产第i个条目时,可以使用另外一种类型的同步,允许一个线程(或进程)休眠 到某个事件发生为止。

    void consume_wait(int i)
    {
    	while (true) {
    		Pthread_mutex_lock(&shared.mutex);
    		if (i < shared.nput) {
    			Pthread_mutex_unlock(&shared.mutex);
    			return;
    		}
    		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);
    }
    

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

  1. 互斥锁用于上锁,条件变量则用于等待。这两种不同类型的同步都是需要的。

  2. 条件变量是类型为pthread_cond_t的变量,以下两个函数使用了这些变量:

    // 成功则返回0,否则返回正的Exxx值
    #include <pthread.h>
    int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
    int pthread_cond_signal(pthread_cond_t *cptr);
    

    这两个函数所等待或由之得以通知的“条件”,其定义由我们选择。每个条件变量总是有一个互斥锁与之关联。我们使用pthread_cond_wait等待某个条件为真时,还会指定其条件变量的地址和所关联的互斥锁的地址。

  • put结构包含:互斥锁变量mutex以及与之相关的两个变量nput和nval;互斥锁初始化为PTHREAD_MUTEX_INITIALIZER

    struct 
    {
    	pthread_mutex_t mutex;
    	int nput;   // nput是buff数组中下一次存放的元素下标
    	int nval;   // nval是下一次存放的值(0、1、2等)
    } put = {
        PTHREAD_MUTEX_INITIALIZER
    };
    
    • nready结构包含:一个计数器、一个条件变量、一个互斥锁。互斥锁初始化为PTHREAD_MUTEX_INITIALIZER, 条件变量初始化为PTHREAD_COND_INITIALIZER
    
    struct 
    {
    	pthread_mutex_t mutex;
    	pthread_cond_t cond;
    	int nready;   // number ready for consumer
    } nready = {
        PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER
    };
    

5 互斥锁和条件变量的属性

  • 以非默认属性初始化互斥锁和条件变量。

  • 初始化或摧毁互斥锁条件变量

    // 成功则返回0,否则返回正的Exxx值
    #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);
    
  • 初始化或摧毁互斥锁属性的数据类型pthread_mutexattr_t

    // 成功则返回0,否则返回正的Exxx值
    #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_t *attr);
    int pthread_condattr_destroy(pthread_condattr_t *cptr);
    

    一旦某个互斥锁属性对象或某个条件变量属性对象已被初始化,就通过调用不同函数启用或禁止特定的属性。这个属性是用一下函数获得或存入的:

    // 成功则返回0,否则返回正的Exxx值
    #include <pthread.h>
    int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *valptr);
    int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int value);
    	int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *valptr);
    int pthread_condattr_setpshared(pthread_condattr_t *attr, int value);
    

6 小结

  • 有时候一个线程获得某个互斥锁后,发现自己需要等待某个条件为真。则该线程就可以等待在某个条件变量上。条件变量总是有一个互斥锁与之关联。
  • 调用线程置于休眠状态的pthread_cond_wait函数在这么做之前先给所关联的互斥锁解锁,以后某个时刻唤醒该线程前再给该互斥锁上锁。
  • 该条件变量由另外某个线程向它发送信号,而这个发送信号的线程既可以只唤醒一个线程(pthread_cond_signal),也可以唤醒等待相应条件变为真的所有线程(pthread_cond_broadcast)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值