Linux多线程同步之相互排斥量和条件变量

1. 什么是相互排斥量

相互排斥量从本质上说是一把锁,在訪问共享资源前对相互排斥量进行加锁,在訪问完毕后释放相互排斥量上的锁。

对相互排斥量进行加锁以后,不论什么其它试图再次对相互排斥量加锁的线程将会被堵塞直到当前线程释放该相互排斥锁。假设释放相互排斥锁时有多个线程堵塞,所以在该相互排斥锁上的堵塞线程都会变成可进行状态。第一个变成执行状态的线程能够对相互排斥量加锁。其它线程在次被堵塞,等待下次执行状态。

pthread_mutex_t 就是POSIX对于mutex的实现。

函数名 參数 说明
pthread_mutex_init
pthread_mutex_t * mutex,

constpthread_mutex_t *attr

初始化一个相互排斥量,静态方式能够直接使用PTHREAD_MUTEX_INITIALIZER进行赋值初始化
pthread_mutex_destroy pthread_mutex_t *mutex 释放对相互排斥变量分配的资源。注意pthread_mutex_init有可能malloc了资源
pthread_mutex_lock pthread_mutex_t *mutex 假设相互排斥量已经上锁,调用线程堵塞直至相互排斥量解锁
pthread_mutex_trylock pthread_mutex_t *mutex 加锁。假设失败不堵塞
pthread_mutex_unlock pthread_mutex_t *mutex 解锁

使用init函数进行初始化:

[cpp] view
plain
 copy

  1. #include <pthread.h>
  2. pthread_mutex_t foo_mutex;
  3. void foo()
  4. {
  5. pthread_mutex_init(&foo_mutex, NULL);
  6. pthread_mutex_lock(&foo_mutex);
  7. /* Do work. */
  8. pthread_mutex_unlock(&foo_mutex);
  9. pthread_mutex_destroy(&foo_mutex);
  10. }

当然该初始化

[cpp] view
plain
 copy

  1. pthread_mutex_init(&foo_mutex, NULL);

仅仅能foo_mutex使用前初始化一次。最后destroy。

初始化已经初始化的mutex将导致undefined behavior。

第二种使用方法:

[cpp] view
plain
 copy

  1. pthread_mutex_t foo_mutex = PTHREAD_MUTEX_INITIALIZER;
  2. void foo()
  3. {
  4. pthread_mutex_lock(&foo_mutex);
  5. /* Do work. */
  6. pthread_mutex_unlock(&foo_mutex);
  7. }

当然了,这两种使用方法都有问题:假设在lock住后unlock之前出现exception,那么这个锁永远也不能unlock。

这样的情况下须要guard这个资源。详细可參照boost::mutex::scoped_lock的实现,很easy可是极大简化了mutex的安全使用。

2. 什么是条件变量

与相互排斥锁不同,条件变量是用来等待而不是用来上锁的。

条件变量用来自己主动堵塞一个线程,直到某特殊情况发生为止。通常条件变量和相互排斥锁同一时候使用。

条件变量使我们能够睡眠等待某种条件出现。

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包含两个动作:一个线程等待"条件变量的条件成立"而挂起;还有一个线程使"条件成立"(给出条件成立信号)。

条件的检測是在相互排斥锁的保护下进行的。

假设一个条件为假,一个线程自己主动堵塞,并释放等待状态改变的相互排斥锁。假设还有一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程。又一次获得相互排斥锁,又一次评价条件。假设两进程共享可读写的内存,条件变量能够被用来实现这两进程间的线程同步。

条件变量的初始化和mutex的初始化差点儿相同,也是有两种方式:

pthread_cond_tmy_condition=PTHREAD_COND_INITIALIZER;

也能够利用函数pthread_cond_init动态初始化。

以下中各个函数的简单介绍。

函数名 參数 说明
pthread_cond_init pthread_cond_t *cond,

const pthread_condattr_t *attr

初始化
pthread_cond_destroy pthread_cond_t *cond 回收
pthread_cond_wait pthread_cond_t *cond,

pthread_mutex_t *mutex

等待。无超时
pthread_cond_timedwait pthread_cond_t *cond,pthread_mutex_t *mutex,

const struct timespec *abstime

等待。有超时
pthread_cond_signal pthread_cond_t *cond 一个在同样条件变量上堵塞的线程将被解锁。假设同一时候有多个线程堵塞,则由调度策略确定接收通知的线程
pthread_cond_broadcast pthread_cond_t *cond 将通知堵塞在这个条件变量上的全部线程。一旦被唤醒,线程仍然会要求相互排斥锁。

一个简单使用条件变量进行线程同步的小样例:

/*
 * pthread_mutex_cond.c
 *
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond =  PTHREAD_COND_INITIALIZER;
int i = 0;

void *thread1(void *);
void *thread2(void *);

int main(void)
{
	pthread_t tid1, tid2;
	if(pthread_create(&tid1, NULL, thread1, NULL))
		exit(1);
	if(pthread_create(&tid2, NULL, thread2, NULL))
		exit(1);
	if(pthread_join(tid1, NULL))
		exit(1);
	printf("thread1 exit\n");
	if(pthread_join(tid2, NULL))
		exit(1);
	printf("thread2 exit\n");

	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	exit(0);
}

void *thread1(void *arg)
{
	printf("thread1 start\n");
	while(i <= 6)
	{
		pthread_mutex_lock(&mutex);
		printf("thread1: lock %d\n", __LINE__);
		printf("thread1 i = %d\n", i);
		if(i%3 == 0)
		{
			printf("thread1:signal 1  %d\n", __LINE__);
			pthread_cond_signal(&cond);
			printf("thread1:signal 2  %d\n", __LINE__);
		}
		pthread_mutex_unlock(&mutex);
		printf("thread1: unlock %d\n", __LINE__);
		sleep(1);	//sleep 1s,让线程2得以运行
		i++;
	}
	pthread_exit((void *)0);
}

void *thread2(void *arg)
{
	//sleep(1);
	printf("thread2 start\n");
	while(i <= 6)
	{
		pthread_mutex_lock(&mutex);
		printf("thread2: lock %d\n", __LINE__);
		printf("thread2 i = %d\n", i);
		if(i%3 != 0)
		{
			 printf("thread2: wait 1  %d\n", __LINE__);
			 pthread_cond_wait(&cond, &mutex);
			 printf("thread2: wait 2  %d\n", __LINE__);
		}

		pthread_mutex_unlock(&mutex);
		printf("thread2: unlock %d\n", __LINE__);
		sleep(1);	//sleep 1s。让线程1得以运行
	}
	pthread_exit((void *)0);
}

运行结果:

thread2 start

thread2: lock 65

thread2 i = 0

thread2: unlock 75

thread1 start

thread1: lock 42

thread1 i = 0

thread1:signal 1  46

thread1:signal 2  48

thread1: unlock 51

thread2: lock 65

thread2 i = 0

thread2: unlock 75

thread1: lock 42

thread1 i = 1

thread1: unlock 51

thread2: lock 65

thread2 i = 1

thread2: wait 1  69

thread1: lock 42

thread1 i = 2

thread1: unlock 51

thread1: lock 42

thread1 i = 3

thread1:signal 1  46

thread1:signal 2  48

thread1: unlock 51

thread2: wait 2  71

thread2: unlock 75

thread2: lock 65

thread2 i = 3

thread2: unlock 75

thread1: lock 42

thread1 i = 4

thread1: unlock 51

thread2: lock 65

thread2 i = 4

thread2: wait 1  69

thread1: lock 42

thread1 i = 5

thread1: unlock 51

thread1: lock 42

thread1 i = 6

thread1:signal 1  46

thread1:signal 2  48

thread1: unlock 51

thread2: wait 2  71

thread2: unlock 75

thread2: lock 65

thread2 i = 6

thread2: unlock 75

thread1 exit

thread2 exit

生产者-消费者的实现

该问题描写叙述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际执行时会发生的问题。

要求:当工作队列为空时,消费者不能从工作队列取走数据,直到工作队列有数据时才干够。

本例中用一个单链表来模拟工作队列。生产者生产一个结构体串在链表的表头上,消费者从表头取走结构体。在两个线程里分别计数。生产者生产6次,消费者消费6次。

/*
 * producer-consumer.c
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

struct msg{
	struct msg *next;
	int num;
};

struct msg *head;	//共享资源,全局指针初始化为NULL
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *);
void *producer(void *);

int main(void)
{
	pthread_t tid1,tid2;
	int res;

	srand(time(NULL));
	res = pthread_create(&tid1,NULL,producer,NULL);
	if(res != 0)
	{
		perror("thread producer create failed\n");
		exit(1);
	}
	res = pthread_create(&tid2,NULL,consumer,NULL);
	if(res != 0)
	{
		perror("thread consumer create failed\n");
		exit(1);
	}
	pthread_join(tid1,NULL);
	if(res != 0)
	{
		perror("join thread producer failed\n");
		exit(1);
	}
	printf("thread producer exit\n");
	pthread_join(tid2,NULL);
	if(res != 0)
	{
		perror("join thread consumer failed\n");
		exit(1);
	}
	printf("thread consumer exit\n");

	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&has_product);
	exit(0);
}

void *producer(void *arg)
{
	struct msg *mp;
	int i;
	printf("producer thread start\n");
	for(i=0;i<6;i++)
	{
		printf("producer i = %d\n", i);
		mp = (struct msg *)malloc(sizeof(struct msg));
		mp->num = rand()%100 + 1;
		printf("Produce %d\n", mp->num);
		pthread_mutex_lock(&mutex);
		mp->next = head;
		head = mp;	//生产者生产一个结构体串在链表的表头上
		pthread_mutex_unlock(&mutex);
		pthread_cond_signal(&has_product);
		sleep(1);	//让还有一个线程有机会运行
	}
	pthread_exit(NULL);
}

void *consumer(void *arg)
{
	struct msg *con;
	int i;
	printf("consumer thread start\n");
	for(i=0;i<6;i++)
	{
		printf("consumer i = %d\n", i);
		pthread_mutex_lock(&mutex);
		while(head == NULL)
		{
			printf("struct msg is null\n");
			pthread_cond_wait(&has_product,&mutex);
		}
		con = head;	//消费者从表头取走结构体
		head = con->next;
		pthread_mutex_unlock(&mutex);
		printf("Consume %d\n", con->num);
		free(con);
		sleep(1);	//让还有一个线程有机会运行
	}
	pthread_exit(NULL);
}

运行结果:

consumer thread start

consumer i = 0

struct msg is null

producer thread start

producer i = 0

Produce 52

Consume 52

consumer i = 1

struct msg is null

producer i = 1

Produce 33

Consume 33

consumer i = 2

struct msg is null

producer i = 2

Produce 77

Consume 77

consumer i = 3

struct msg is null

producer i = 3

Produce 86

Consume 86

consumer i = 4

struct msg is null

producer i = 4

Produce 84

Consume 84

consumer i = 5

struct msg is null

producer i = 5

Produce 46

Consume 46

thread producer exit

thread consumer exit

时间: 2024-08-15 00:35:43

Linux多线程同步之相互排斥量和条件变量的相关文章

Linux同步与相互排斥应用(零):基础概念

[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途] 当操作系统进入多道批处理系统时代以后.一个系统中就存在多个任务,每一个任务都依照一定的算法进行调度来使用内存.cpu等共享资源. 当当中一个任务等待其它资源时,该任务能够临时睡眠,操作系统调度另外任务继续运行额,这样能够使系统资源得到最大化利用.而无需像曾经单道批处理系统那样仅仅有当一个任务完毕之后才运行下一个任务. 可是由此也引入了多任务并发

Linux线程相互排斥量--进程共享属性

多线程中.在相互排斥量和 读写锁的 属性中.都有一个叫 进程共享属性 . 对于相互排斥量,查询和设置这个属性的方法为: pthread_mutexattr_getpshared pthread_mutexattr_setpshared 我一開始不理解什么是 进程共享属性. 看了man中的说明例如以下 The pthread_mutexattr_getpshared() function shall obtain the value of the process-shared attribute

[转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程

一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http://blog.csdn.net/locape/article/details/6040383 http://www.cnblogs.com/liuweijian/archive/2009/12/30/1635888.html 一.什么是多线程? 当我自己提出这个问题的时候,我还是很老实的拿着操作系

线程同步与相互排斥:相互排斥锁

为什么须要相互排斥锁? 在多任务操作系统中,同一时候执行的多个任务可能都须要使用同一种资源.这个过程有点类似于,公司部门里.我在使用着打印机打印东西的同一时候(还没有打印完).别人刚好也在此刻使用打印机打印东西,假设不做不论什么处理的话,打印出来的东西肯定是错乱的. 以下我们用程序模拟一下这个过程.线程一须要打印" hello ",线程二须要打印" world ",不加不论什么处理的话.打印出来的内容会错乱: [cpp] view plaincopy #includ

一起talk C栗子吧(第一百回:C语言实例--使用信号量进行进程间同步与相互排斥一)

各位看官们.大家好,上一回中咱们说的是进程间同步与相互排斥的样例,这一回咱们说的样例是:使用信号量进行进程间同步与相互排斥. 闲话休提,言归正转.让我们一起talk C栗子吧! 看官们,信号量是由著名计算机科学家迪杰斯特拉(Dijkstra)提出的一种概念,专门用来解决进程间同步与相互排斥.在他提出的概念中信号量是一个非负整数值. 信号量的操作仅仅能有两种原子操作: 等待信号; 发送信号. "什么是原子操作呢?"台下有看官在提问.原子操作就是指某个动作在运行时不能被其他动作中断,它会一

多任务的同步与相互排斥

现代操作系统基本都是多任务操作系统,即同一时候有大量可调度实体在执行.在多任务操作系统中,同一时候执行的多个任务可能: 都须要訪问/使用同一种资源 多个任务之间有依赖关系.某个任务的执行依赖于还有一个任务 这两种情形是多任务编程中遇到的最主要的问题,也是多任务编程中的核心问题,同步和相互排斥就是用于解决这两个问题的. 相互排斥:是指散步在不同任务之间的若干程序片断,当某个任务执行当中一个程序片段时,其他任务就不能执行它们之中的任一程序片段,仅仅能等到该任务执行完这个程序片段后才干够执行.最主要的

浅析线程间通信一:互斥量和条件变量

线程同步的目的简单来讲就是保证数据的一致性.在Linux中,常用的线程同步方法有互斥量( mutex ).读写锁和条件变量,合理使用这三种方法可以保证数据的一致性,但值得的注意的是,在设计应用程序时,所有的线程都必须遵守相同的数据访问规则为前提,才能保证这些同步方法有效,如果允许某个线程在没有得到访问权限(比如锁)的情况下访问共享资源,那么其他线程在使用共享资源前都获得了锁,也会出现数据不一致的问题.另外还有自旋锁.barrier和信号量线程同步方法.本文将讨论互斥量和条件变量的使用,并给出了相

互斥量和条件变量

1.如何利用2个条件变量实现线程同步? 思路:就是来回的利用pthread_cond_signal()函数,当一方被阻塞时,唤醒函数可以唤醒pthread_cond_wait()函数,只不过pthread_cond_wait()这个方法要执行其后的语句,必须遇到下一个阻塞(也就是pthread_cond_wait()方法时),才执行唤醒后的其后语句. 代码如下: #include<stdio.h> #include<unistd.h> #include<stdlib.h>

读写锁————用互斥量和条件变量模拟

一. 读写锁 在多线程环境下为了防止对临界资源访问的冲突我们往往会在线程函数中加入互斥锁来完成线程间的互斥:但是,在有些情况下,互斥锁mutex并不是那么高效,比如当要对一块缓冲区进行读写操作的时候,因为读的需要比写入修改的需要要多,读取数据并不会修改缓冲区的数据个数或者内容,如果要使用互斥锁就会耗费额外的时间,每一次读取都要争夺锁资源挂起等待,因此就可以使用另外一种锁机制----读写锁. 有读写锁的存在当然就会有读者和写者,也就是多个线程,但是它们之间的相互关系和mutex锁中有所不同: 当读