线程同步---条件变量

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

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

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;
}

思考:解决上面问题的核心就是让一个资源同时使用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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#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广播出去假设现在生产者生产了一个产品,他就广播一下,这时候俩消费者都醒了,其中一个拿到锁然后消耗了资源,再解锁,这时候另一个直接拿到锁,然而此时已经没产品,溢出导致段错误(如下图)。这就是条件被别人消耗了的导致的情况。


4.哲学家就餐问题:

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

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

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

#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", "<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">哲学家2</span><span style="font-size: 12px; font-family: Arial, Helvetica, sans-serif;">", "哲学家3", "哲学家4", "哲学家5"};</span>

// 就餐者状态数组
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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在C++中,可以使用互斥量(mutex)来实现全局变量的线程同步。互斥量是一种同步原语,用于保护共享资源不被多个线程同时访问。当一个线程获得了互斥量的锁时,其他线程需要等待该线程释放锁才能继续执行。这样可以确保同一时刻只有一个线程可以访问共享资源。 在引用的代码示例中,可以看到使用了互斥量来保护全局变量tickets的访问。通过在fun1Proc和fun2Proc函数中使用std::mutex类创建互斥量对象,并使用lock()和unlock()函数来锁定和释放互斥量的锁。这样可以确保在每次访问tickets时只有一个线程可以执行,从而避免了数据竞争和不确定的结果。 此外,可以使用条件变量(condition variable)来实现线程之间的通信和同步。条件变量是一种同步原语,用于在多个线程之间进行等待和唤醒操作。当一个线程需要等待某个条件满足时,可以调用wait()函数将自己阻塞,直到其他线程通过notify_one()或notify_all()函数唤醒它。这样可以有效地控制线程的执行顺序和同步。 在引用的代码示例中,没有使用条件变量,而是通过循环判断tickets的值来判断是否继续执行。这种方式并不是最优雅和高效的线程同步方法,因为它会造成不必要的CPU资源浪费。使用条件变量可以更好地实现线程之间的同步和通信,提高程序的性能和可维护性。 总结起来,C++中可以使用互斥量和条件变量来实现全局变量的线程同步。互斥量用于保护共享资源的访问,条件变量用于线程之间的等待和唤醒操作。通过合理地运用这些同步原语,可以确保多线程程序的正确性和可靠性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++多线程线程同步问题](https://blog.csdn.net/sinat_41928334/article/details/107880741)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值