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的实现,非常简单但是极大简化了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-12-06 17:28:04

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

Linux 多线程同步机制:互斥量、信号量、条件变量

互斥量:互斥量提供对共享资源的保护访问,它的两种状态:lock和unlock,用来保证某段时间内只有一个线程使用共享资源,互斥量的数据类型是pthread_mutex_t主要涉及函数:pthread_mutex_lock() pthread_mutex_trylock() pthread_mutex_unlock()Pthreaf_mutex_init() pthread_mutex_destroy()lock与unlock之间所锁定的区域为临界区域(如果只加锁不解锁程序会阻塞等待)信号量:信号

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

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

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

线程同步的目的简单来讲就是保证数据的一致性.在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>

生产者-消费者问题:介绍POSIX线程的互斥量和条件变量的使用

全局初始化互斥量和条件变量(不全局也行,但至少要对线程启动函数可见,这样才能使用.) static pthread_cont_t cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; 使用互斥量锁住一块代码方法如下(默认忽略pthread开头的函数的错误检查,即类似 int s = pthread_xxx(...); if (s != 0) { printErrorMsg(

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

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

使用互斥量和条件变量实现线程同步控制

管程(monitor)说明 在并发编程中,管程(monitor)是一个同步构件,管程实现了同一时间点,最多只有一个线程可以执行管程的某个子程序.与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程的实现很大程度上简化了程序设计. 管程可以确保一次只有一个进程执行管程中的程序,因此程序员不需要显式地编写同步代码,但是如果需要就某些特定条件上的同步,则需要定义一些条件结构(condition variable)来实现,并且对条件变量的操作仅有wait()和signal(),如下: condit

互斥量和条件变量的区别

互斥量与条件变量的区别 转载自:http://www.360doc.com/content/12/0129/10/1317564_182456205.shtml 前面谈过了线程锁,下面我们在继续研究一下线程锁: 互斥量从本质上来说是一个锁,对互斥量加锁后任何其他试图给它加锁的线程都会被阻塞直至当前线程释放互斥量. 同样在设计时需要规定所有的线程必须遵守相同的数据访问规则,只有这样互斥机制才能正常工作(只要是锁都这样,这是锁工作的本质) 互斥量用pthread_mutex_t 数据类型表示,在使用

Linux下多线程编程之互斥锁、条件变量、信号量

1.进程创建 int pthread_create (pthread_t * thread_id, __const pthread_attr_t * __attr, void *(*__start_routine) (void *), void *__restrict __arg); 第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数. 一个实例: void *producer(void *args); pthread_