C++多线程交替打印详解:从互斥锁到条件变量的实现

叶初
叶初
发布时间:2026-10-15 | 5人浏览过
原创

在C++多线程编程中,交替打印是考察线程同步机制的经典场景。本文将演示如何通过互斥锁和条件变量,让两个线程按顺序分别打印奇数和偶数,避免数据竞争并实现精确的并发控制。

目标:实现两个线程交替打印

我们的最终目标是创建两个线程,让它们依次打印数字序列,例如一个打印奇数(1, 3, 5...),另一个打印偶数(2, 4, 6...),直到达到指定数量。为了实现这一目标,我们需要处理线程间的资源竞争和调度顺序。

第一步:基础框架与问题分析

首先,我们尝试用最基础的 <thread>库创建两个线程来打印计数器。由于两个线程同时访问共享变量 i,且没有同步机制,会导致数据二义性和输出混乱。

第1步:创建基础并发框架

  • 入口:#include <iostream>, #include <thread>
  • 动作:在主函数中创建两个 std::thread 对象,并使用 Lambda 表达式捕获外部变量 n(上限)和 i(当前值)。
  • 关键点:两个线程使用 t.join()等待结束。
// 基础框架:两个线程争夺打印 i
#include <iostream>
#include <thread>
using namespace std;

int main(void)
{
	int n = 100;
	int i = 0;
	// 创建两个线程,共同操作 i
	thread t1([&n, &i](){
		while (i < n)
		{
			cout << i << " ";
			i++;
		}
	});
	thread t2([&n, &i]() {
		while (i < n)
		{
			cout << i << " ";
			i++;
		}
	});
	if (t1.joinable()) t1.join();
	if (t2.joinable()) t2.join();
	return 0;
}

此时运行结果往往不是有序的 0, 1, 2...,而是出现跳跃或重复,因为 i 是临界资源,多个线程争抢访问造成数据不一致。为了看清是哪个线程在打印,我们可以添加 this_thread::get_id() 来标识线程身份:

// 增加线程ID以观察执行主体
#include <iostream>
#include <thread>
using namespace std;

int main(void)
{
	int n = 100;
	int i = 0;
	thread t1([&n, &i](){
		while (i < n)
		{
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	thread t2([&n, &i]() {
		while (i < n)
		{
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
		}
	});
	if (t1.joinable()) t1.join();
	if (t2.joinable()) t2.join();
	return 0;
}

第二步:使用互斥量保护临界资源

为了解决数据安全问题,必须确保同一时刻只有一个线程能访问 i。我们可以引入 <mutex> 互斥量进行加锁。为了保证两个线程使用同一把锁,需要在 main 函数中定义 mutex mtx,并通过引用传递给 Lambda。

第2步:加入 Mutex 进行同步

  • 操作:在每次访问 i 前调用 mtx.lock(),访问后调用 mtx.unlock()。
  • 参数:mutex mtx 作为全局唯一的锁对象。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	thread t1([&n, &i, &mtx](){
		while (i < n)
		{
			mtx.lock();
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			mtx.unlock();
		}
	});
	thread t2([&n, &i, &mtx]() {
		while (i < n)
		{
			mtx.lock();
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			mtx.unlock();
		}
	});
	if (t1.joinable()) t1.join();
	if (t2.joinable()) t2.join();
	return 0;
}

虽然此时线程安全了,但如果某个线程竞争力较强,可能会连续抢到锁,导致另一个线程饥饿,依然无法实现“交替”打印。此外,手动 lock/unlock 容易因异常导致死锁,C++推荐由 RAII 类管理锁。

第三步:引入 unique_lock 与管理类

C++提供了 lock_guard 和 unique_lock 两种管理类。lock_guard 仅用于简单的构造/析构加锁,而 unique_lock 更灵活,支持条件变量的 wait 操作。我们将使用 unique_lock 来管理锁。

// 模拟 lock_guard 原理
template <class Lock>
class LockGuard
{
public:
	LockGuard(Lock &lck) : _lock(lck) { _lock.lock(); }
	~LockGuard() { _lock.unlock(); }
private:
	Lock &_lock;
};

// 实际使用中直接使用 std::unique_lock

第四步:使用条件变量实现交替逻辑

要实现严格的交替打印,需要引入 condition_variable(条件变量)和一个状态标志 flag。基本逻辑是:当前线程执行完后修改 flag,唤醒对方线程;对方线程在等待时检查 flag,条件满足则继续,否则阻塞。

第4步:配置 Condition Variable 和 Flag

  • 准备条件:引入 <condition_variable>,定义 bool flag = false 初始状态。
  • 动作:在线程中使用 cv.wait(lock, predicate) 阻塞等待。
int main(void)
{
	int n = 100;
	int i = 0;
	mutex mtx;
	condition_variable cv;
	bool flag = false; // 初始为false,让t2先等还是t1先等取决于逻辑设计

	// 线程t1:打印奇数或根据flag逻辑执行
	thread t1([&n, &i, &mtx, &cv, &flag](){
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			// 等待 !flag 为真才继续
			cv.wait(LockManage, [&flag]() {return !flag; });
			
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			
			flag = true; // 修改状态
			cv.notify_one(); // 唤醒等待者
		}
	});

	// 线程t2:打印偶数
	thread t2([&n, &i, &mtx, &cv, &flag]() {
		while (i < n)
		{
			unique_lock<mutex> LockManage(mtx);
			// 等待 flag 为真才继续
			cv.wait(LockManage, [&flag]() {return flag; });
			
			cout << this_thread::get_id() << ": " << i << endl;
			i++;
			
			flag = false; // 还原状态
			cv.notify_one(); // 唤醒等待者
		}
	});

	if (t1.joinable()) t1.join();
	if (t2.joinable()) t2.join();
	return 0;
}

在上述代码中,flag 的初始值为 false。 - t1 在 cv.wait 中判断 !flag(即 true),所以 t1 不阻塞,先获得锁执行打印。 - t2 在 cv.wait 中判断 flag(即 false),条件不满足,进入阻塞队列。

当 t1 执行完一次打印后,设置 flag = true 并调用 cv.notify_one()。此时 t2 被唤醒,重新获取锁,发现 flag 为真,于是跳出等待执行打印。

第五步:验证与结果

通过这种“互斥锁+条件变量+状态标志”的组合,我们实现了严格的交替执行。每个线程只在自己该执行时运行,打印完毕后立即交出控制权。

最终输出将呈现有序的递增序列,且每个数字只由一个线程输出一次,达到了两个线程交替打印的目的。这种方式比单纯的信号量或睡眠轮询更加高效和稳定。

以上就是C++详解如何实现两个线程交替打印的详细内容,更多关于C++线程并发控制的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表