线程同步---条件变量

1. 问题引入:互斥锁问题,假设现在有两个资源A和B,一个线程先拿A再拿B,另一个则相反,这样导致的问题就是死锁,即两个线程无休止的互相等待

#include 
#include 
#include 
#include 

pthread_mutex_t g_mtxa = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t g_mtxb = PTHREAD_MUTEX_INITIALIZER;

void* thread_proc1 (void* arg) {
	pthread_t tid = pthread_self ();

	printf ("%lu线程:等待A...\n", tid);
	pthread_mutex_lock (&g_mtxa);
	printf ("%lu线程:获得A!\n", tid);

	sleep (1);

	printf ("%lu线程:等待B...\n", tid);
	pthread_mutex_lock (&g_mtxb);
	printf ("%lu线程:获得B!\n", tid);

	pthread_mutex_unlock (&g_mtxb);
	printf ("%lu线程:释放B。\n", tid);

	pthread_mutex_unlock (&g_mtxa);
	printf ("%lu线程:释放A。\n", tid);

	return NULL;
}

void* thread_proc2 (void* arg) {
	pthread_t tid = pthread_self ();

	printf ("%lu线程:等待B...\n", tid);
	pthread_mutex_lock (&g_mtxb);
	printf ("%lu线程:获得B!\n", tid);

	sleep (1);

	printf ("%lu线程:等待A...\n", tid);
	pthread_mutex_lock (&g_mtxa);
	printf ("%lu线程:获得A!\n", tid);

	pthread_mutex_unlock (&g_mtxa);
	printf ("%lu线程:释放A。\n", tid);

	pthread_mutex_unlock (&g_mtxb);
	printf ("%lu线程:释放B。\n", tid);

	return NULL;
}

int main (void) {
	pthread_t tid1;
	int error = pthread_create (&tid1, NULL, thread_proc1, NULL);
	if (error) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	pthread_t tid2;
	if ((error = pthread_create (&tid2, NULL, thread_proc2,
		NULL)) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_join (tid1, NULL)) != 0) {
		fprintf (stderr, "pthread_join: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_join (tid2, NULL)) != 0) {
		fprintf (stderr, "pthread_join: %s\n", strerror (error));
		return -1;
	}
	printf ("大功告成!\n");
	return 0;
}
线程同步---条件变量_第1张图片

思考:解决上面问题的核心就是让一个资源同时使用A和B,使用完在释放,这样就不会出现互相等待导致死锁的问题。


2.  条件变量:条件变量是一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足。条件变量上的基本操作有:触发条件(当条件变为 true 时);等待条件,挂起线程直到其他线程触发条件。条件变量必须和互斥锁一起使用

int pthread_cond_init (pthread_cond_t* cond,const pthread_condattr_t* attr);// 初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 作用和上个函数等价
int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex);// 使调用线程睡入条件变量cond,同时释放互斥锁mutex,
int pthread_cond_timedwait (pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime);// abstime 指定的时间内 cond 未触发,互斥量 mutex 被重新加锁
struct timespec {
    time_t tv_sec;  // Seconds
    long   tv_nsec; // Nanoseconds [0 - 999999999]
};
int pthread_cond_signal (pthread_cond_t* cond);// 从条件变量cond中唤出一个线程, 令其重新获得原先的互斥锁
注意:被唤出的线程此刻将从pthread_cond_wait函数中返回,但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
int pthread_cond_broadcast (pthread_cond_t* cond);// 从条件变量cond中唤出所有线程
int pthread_cond_destroy (pthread_cond_t* cond);//销毁条件变量释放资源


3. 生产者消费者模型:两个生产 者生产A-Z的字符,两个消费者线程

#include 
#include 
#include 
#include 
#include 

#define MAX_STOCK 20 // 仓库容量

char g_storage[MAX_STOCK]; // 仓库
size_t g_stock = 0; // 当前库存

pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t g_full = PTHREAD_COND_INITIALIZER;  // 满仓
pthread_cond_t g_empty = PTHREAD_COND_INITIALIZER; // 空仓

// 显示库存
void show (const char* who, const char* op, char prod) {
	printf ("%s:", who);

	size_t i;
	for (i = 0; i < g_stock; i++)
		printf ("%c", g_storage[i]);

	printf ("%s%c\n", op, prod);
}

// 生产者线程
void* producer (void* arg) {
	const char* who = (const char*)arg;

	for (;;) {
		pthread_mutex_lock (&g_mtx);

//		if (g_stock >= MAX_STOCK) {    //此时应该对条件进行再判断
		while (g_stock >= MAX_STOCK) {
			printf ("%s:满仓!\n", who);
			pthread_cond_wait (&g_full, &g_mtx);
		}

		char prod = 'A' + rand () % 26;
		show (who, "<-", prod);
		g_storage[g_stock++] = prod;
		pthread_cond_broadcast (&g_empty);

		pthread_mutex_unlock (&g_mtx);

		usleep ((rand () % 100) * 1000);
	}

	return NULL;
}

// 消费者线程
void* customer (void* arg) {
	const char* who = (const char*)arg;

	for (;;) {
		pthread_mutex_lock (&g_mtx);

//		if (! g_stock) {
		while (! g_stock) {
			printf ("%s:空仓!\n", who);
			pthread_cond_wait (&g_empty, &g_mtx);
		}

		char prod = g_storage[--g_stock];
		show (who, "->", prod);
		pthread_cond_broadcast (&g_full);

		pthread_mutex_unlock (&g_mtx);

		usleep ((rand () % 100) * 1000);
	}

	return NULL;
}

int main (void) {
	srand (time (NULL));

	pthread_attr_t attr;
	int error = pthread_attr_init (&attr);
	if (error) {
		fprintf (stderr, "pthread_attr_init: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_attr_setdetachstate (&attr,//修改线程分离状态属性

		PTHREAD_CREATE_DETACHED)) != 0) {
		fprintf (stderr, "pthread_attr_setdetachstate: %s\n",
			strerror (error));
		return -1;
	}

	pthread_t tid;
	if ((error = pthread_create (&tid, &attr, producer,
		"生产者1")) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_create (&tid, &attr, producer,
		"生产者2")) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_create (&tid, &attr, customer,
		"消费者1")) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_create (&tid, &attr, customer,
		"消费者2")) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}
	getchar ();
	return 0;
}
注意:

1.处于分离状态的线程终止后自动释放线程资源,且不能被pthread_join函数等待

2. 进行条件再判断的原因:两个生产者,两个消费者,signal一次只能叫醒一个,所以现在使用broadst广播出去假设现在生产者生产了一个产品,他就广播一下,这时候俩消费者都醒了,其中一个拿到锁然后消耗了资源,再解锁,这时候另一个直接拿到锁,然而此时已经没产品,溢出导致段错误(如下图)。这就是条件被别人消耗了的导致的情况。

线程同步---条件变量_第2张图片

4.哲学家就餐问题:

五个哲学家围坐在一张圆桌周围,每个哲学家面前都有一盘通心粉。由于通心粉很滑,所以需要两把叉子才能夹住。相邻两个盘子之间放有一把叉子。哲学家的生活中有两种交替活动时段:即吃饭和思考。当一个哲学家觉得饿了时,他就试图分两次去取其左边和右边的叉子,每次拿一把,但不分次序。如果成功地得到了两把叉子,就开始吃饭,吃完后放下叉子继续思考。那么我们要做的就是为每一个哲学家写一段描述其行为的程序,且决不会死锁吗?

解决方案:让哲学家不看筷子看人,如果两边的人都没吃,就自己吃,吃完后就通知两边的人。

#include 
#include 
#include 
#include 
#include 

#define DINERS 5 // 就餐人数

// 就餐者状态
typedef enum tag_State {
	THINKING, // 正在思考
	HUNGRY,   // 正在挨饿
	EATING    // 正在吃饭
}	ESTAT;

pthread_mutex_t g_mtx = PTHREAD_MUTEX_INITIALIZER;

// 就餐者条件变量数组
pthread_cond_t g_conds[DINERS] = {
	PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER,
	PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER,
	PTHREAD_COND_INITIALIZER};

// 就餐者姓名数组
const char* g_names[DINERS] = {
	"哲学家1", "哲学家2", "哲学家3", "哲学家4", "哲学家5"};

// 就餐者状态数组
ESTAT g_stats[DINERS] = {
	THINKING, THINKING, THINKING, THINKING, THINKING};

// 吃饭
void eat (int i) {
	int l = (i + 1) % DINERS; // 左邻
	int r = (i + DINERS - 1) % DINERS; // 右邻

	pthread_mutex_lock (&g_mtx);

	while (g_stats[l] == EATING || g_stats[r] == EATING) {  //只有左右两个人都不吃的时候自己才能吃
		g_stats[i] = HUNGRY;
		printf ("%s:快点吧,饿死我了~~~\n", g_names[i]);
		pthread_cond_wait (&g_conds[i], &g_mtx);      //等待的时候把锁放开
	}

	g_stats[i] = EATING;
	printf ("%s:终于可以吃一口了!\n", g_names[i]);

	pthread_mutex_unlock (&g_mtx);

	usleep ((rand () % 100) * 10000);  //吃的时间随机给出
}

// 思考
void think (int i) {
	int l = (i + 1) % DINERS; // 左邻
	int r = (i + DINERS - 1) % DINERS; // 右舍

	pthread_mutex_lock (&g_mtx);

	g_stats[i] = THINKING;
	printf ("%s:吃饱了,开始思考...\n", g_names[i]);

	pthread_cond_signal (&g_conds[l]); // 令其重新获得原先的互斥锁
	pthread_cond_signal (&g_conds[r]);

	pthread_mutex_unlock (&g_mtx);

	usleep ((rand () % 100) * 10000);
}

// 就餐者线程
void* diner (void* arg) {
	int i = (int)arg; //拿到哲学家的索引号

	for (;;) {
		eat (i);
		think (i);
	}

	return NULL;
}

int main (void) {
	srand (time (NULL));

	pthread_attr_t attr;
	int error = pthread_attr_init (&attr);
	if (error) {
		fprintf (stderr, "pthread_attr_init: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_attr_setdetachstate (&attr,
		PTHREAD_CREATE_DETACHED)) != 0) {
		fprintf (stderr, "pthread_attr_setdetachstate: %s\n",
			strerror (error));
		return -1;
	}

	size_t i;
	pthread_t tid;
	for (i = 0; i < DINERS; ++i)
		if ((error = pthread_create (&tid, &attr, diner,
			(void*)i)) != 0) {
			fprintf (stderr, "pthread_create: %s\n", strerror (error));
			return -1;
		}
	getchar ();
	return 0;
}
线程同步---条件变量_第3张图片

你可能感兴趣的:(线程)