使用互斥锁和条件变量实现windows的Event

本文围绕Linux多线程展开,介绍了互斥锁和条件变量。详细阐述了互斥锁的4个函数,包括初始化、加锁、解锁和销毁,还说明了静态初始化及不同场景下的初始化方式。同时介绍了条件变量的4个函数,以及其静态初始化方法。
#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;

class DEvent
{
	public:
		DEvent(bool bSet):bSet(bSet)
		{
			pthread_mutex_init(&mutex, NULL);	
			pthread_cond_init(&cond, NULL);
		}

		~DEvent()
		{
			pthread_mutex_destroy(&mutex);	
			pthread_cond_destroy(&cond);
		}

		void Wait()
		{
			pthread_mutex_lock(&mutex);
			while (!bSet)
			{
				pthread_cond_wait(&cond, &mutex);
			}
			bSet = false;//自动置位
			pthread_mutex_unlock(&mutex);
		}

		void SetEvent()
		{
			pthread_mutex_lock(&mutex);
			bSet = true;
			pthread_cond_signal(&cond);
			pthread_mutex_unlock(&mutex);
		}

	public:
		bool bSet = false;
		pthread_mutex_t mutex;
		pthread_cond_t cond;
};

DEvent eventA(true);
DEvent eventB(false);

void* PrintA(void* arg)
{
    while (true)
	{
	eventA.Wait();
	cout << "A" << endl;
	sleep(1);
	eventB.SetEvent();
	}
	return 0;
}

void* PrintB(void* arg)
{
	while (true)
	{
	eventB.Wait();
	cout << "B" << endl;
	eventA.SetEvent();
	}
	return 0;
}

//使用DEvent使得两个线程交替输出AB
int main(int argc, char** argv)
{
	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, PrintA, NULL);
	pthread_create(&tid2, NULL, PrintB, NULL);

	pthread_join(tid2, NULL);
	pthread_join(tid1, NULL);
	return 0;
}

(1)互斥锁

pthread_mutex_t mutex;

互斥锁用到的4个函数

初始化互斥锁

pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);

加锁:

pthread_mutex_lock(pthread_mutex_t *mutex);

解锁:

pthread_mutex_unlock(pthread_mutex_t *mutex);

销毁互斥锁:

pthread_mutex_destroy(pthread_mutex_t *mutex);

关于初始化:

如果是静态初始化,则不用调用pthead_mutex_init和pthread_mutex_destroy这两个函数,静态初始化如下:

pthread_mutex_t cond = PTHREAD_MUTEX_INITIALIZER;

当互斥锁存放在共享内存中或者动态申请(malloc)的空间中时,必须使用pthread_mutex_init来初始化。

关于pthread_mutex_init函数的第二个参数,互斥锁的属性,一般可以是NULL,但是当互斥锁在共享内存中并且需要在多进程使用时,必须给互斥锁初始化PTHREAD_PROCESS_SHARED属性,才能正确在多进程间共享,代码如下:

pthread_mutexattr_t attr; //这个属性结构可以是在本地的临时变量,执行完pthread_mutex_init函数之后就可以销毁

pthread_mutexattr_init(&attr);//初始化属性

pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);

pthread_mutex_init(pMutex, &attr);//pthread_mutex_t结构体必须在共享内存中

pthread_mutexattr_destroy(&attr);//初始化执行完就可以销毁了

条件变量在多进程间共享也是一样的道理,把以上代码的mutex改成cond就可以了

(2)条件变量

pthead_cond_t cond;

条件变量用到的4个函数

初始化信号量

pthead_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

等待信号量:(等待信号量函数必须关联一个互斥锁,信号量的操作必须是在锁住某个资源然后查看资源的状态后才觉得是否要等待)

pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

唤醒等待的线程

pthread_cond_signal(pthread_cond_t *cond);

销毁条件变量

pthread_cond_destroy(pthread_cond_t *cond);

 

如果是静态初始化,则不用调用pthead_cond_init和pthread_cond_destroy这两个函数,静态初始化如下:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

<think>我们要求实现循环唤醒一个线程:即一个线程被唤醒后执行任务,然后再次进入等待,等待下一次唤醒。 我们可以使用一个循环,在循环中等待互斥锁(或其他同步对象)来实现。但注意,互斥锁通常用于互斥访问,而要实现循环唤醒,我们可能需要结合条件变量(Condition Variable)或事件(Event)等机制。不过,题目要求使用互斥锁,我们可以通过一个循环多个互斥锁(或配合其他机制)来实现,但更常见的是使用条件变量。然而,在Windows平台上,我们可以使用事件(Event)对象,但题目要求互斥锁。 实际上,单独使用互斥锁无法实现等待/唤醒的循环,因为互斥锁只有两种状态:拥有或释放。当线程释放互斥锁后,其他线程可以获取,但无法让一个线程在释放后立即再次等待(除非有另一个线程再次释放)。因此,我们需要结合条件变量或者使用事件(Event)对象。但是,题目要求使用互斥锁,所以我们可以考虑使用两个互斥锁实现一个类似乒乓的机制,但这可能不够直接。 另一种思路:使用一个互斥锁一个条件变量(Condition Variable)是标准做法。但是,如果必须只使用互斥锁,我们可以尝试用两个互斥锁来模拟,但这样会复杂且不高效。 考虑到题目要求使用互斥锁,我们可以这样设计: - 使用两个互斥锁:mutex1mutex2。 - 初始状态:mutex1被主线程锁定,mutex2被工作线程锁定。 - 工作线程循环: 1. 等待mutex1(等待主线程释放mutex1,表示可以开始工作) 2. 执行任务 3. 释放mutex2(通知主线程任务完成) - 主线程循环: 1. 释放mutex1(让工作线程开始) 2. 等待mutex2(等待工作线程完成任务) 3. 再次获取mutex1(为下一次唤醒准备) 但是,这种方法每次循环都需要主线程工作线程交替释放锁,只能实现一次唤醒一次,然后再次等待,符合循环唤醒的要求。但注意,这种设计只能用于两个线程(一个主线程一个工作线程)之间的同步。 然而,题目中只要求循环唤醒一个线程,所以我们可以这样实现。 但是,更常见且高效的做法是使用条件变量(Condition Variable)互斥锁。在Windows中,条件变量是Vista之后引入的,我们可以使用`SleepConditionVariableCS``WakeConditionVariable`。如果考虑跨平台或更早的Windows版本,可以使用事件(Event)对象。但题目要求互斥锁,我们这里使用条件变量(因为它需要互斥锁配合使用)是符合题意的,因为题目要求使用互斥锁,但并没有禁止使用其他同步对象。然而,题目要求“用互斥锁实现”,可能希望只使用互斥锁?这有些矛盾。 重新审视题目:要求使用互斥锁实现循环唤醒一个线程。我们可能只能使用互斥锁,那么我们可以尝试用两个互斥锁实现。 下面我们使用两个互斥锁(mutexAmutexB)实现循环唤醒: 主线程(唤醒线程): 1. 初始化:mutexA创建并锁定(主线程拥有),mutexB创建并锁定(工作线程将会拥有,但初始时主线程创建后立即等待工作线程来获取?这样不行) 我们改变思路,使用一个互斥锁一个循环标志,但等待机制如何实现互斥锁本身不支持等待特定条件。 因此,我们决定使用条件变量(Condition Variable)互斥锁,因为条件变量必须互斥锁一起使用,所以这符合“使用互斥锁”的要求(因为确实用了互斥锁)。而且,条件变量实现这种等待-唤醒模式的正确工具。 方案: - 使用一个互斥锁(CRITICAL_SECTION)一个条件变量(CONDITION_VARIABLE)。 - 一个布尔标志`workToDo`,用于指示是否有工作需要做。 - 工作线程循环: 1. 锁定互斥锁 2. 如果`workToDo`为假,则等待条件变量(自动释放互斥锁,等待被唤醒) 3. 当被唤醒后,`workToDo`为真,则执行工作 4. 将`workToDo`设为假(表示工作完成,等待下一次) 5. 解锁互斥锁 - 主线程(或其他唤醒线程): 1. 锁定互斥锁 2. 设置`workToDo`为真 3. 唤醒条件变量(唤醒工作线程) 4. 解锁互斥锁 这样,工作线程就会执行一次工作,然后继续等待。但是,这个方案中,工作线程被唤醒后执行一次工作,然后再次进入等待,就实现了循环唤醒。 但是,注意:工作线程的循环中,当`workToDo`为假时,它会一直等待条件变量。当主线程设置`workToDo`并唤醒工作线程后,工作线程从条件变量等待中返回,然后检查`workToDo`为真,执行工作,然后设置`workToDo`为假,并解锁。这样,下一次循环它又会等待。 因此,我们按照这个方案实现。 下面我们使用Windows API实现条件变量(需要Windows Vista及以上版本)。 如果必须在更早的Windows版本上运行,可以使用事件对象模拟条件变量,但这里我们假设使用较新的Windows版本。 代码实现: 工作线程函数: - 循环:等待条件变量,当被唤醒且`workToDo`为真时,执行工作,然后重置`workToDo`为假。 主线程: - 在需要唤醒工作线程时,设置`workToDo`为真,并唤醒条件变量。 注意:条件变量需要互斥锁(这里使用CRITICAL_SECTION)配合使用。 具体步骤: 1. 初始化: - 条件变量:`CONDITION_VARIABLE condVar; InitializeConditionVariable(&condVar);` - 互斥锁:`CRITICAL_SECTION cs; InitializeCriticalSection(&cs);` - 全局变量:`BOOL workToDo = FALSE;` 2. 工作线程: ```c while (running) { EnterCriticalSection(&cs); while (!workToDo) { // 等待条件变量,释放互斥锁,直到被唤醒 SleepConditionVariableCS(&condVar, &cs, INFINITE); } // 执行工作 DoWork(); // 重置workToDo为假,表示工作已完成 workToDo = FALSE; LeaveCriticalSection(&cs); } ``` 3. 主线程唤醒工作线程: ```c EnterCriticalSection(&cs); workToDo = TRUE; WakeConditionVariable(&condVar); LeaveCriticalSection(&cs); ``` 但是,注意:工作线程中,当被唤醒后,我们使用了一个`while`循环检查`workToDo`,这是因为条件变量可能存在虚假唤醒(spurious wakeup),所以即使被唤醒,我们也要检查条件是否满足。 然而,题目要求循环唤醒一个线程,所以工作线程会一直运行,等待被唤醒,执行工作,然后继续等待。 下面给出完整代码示例: 注意:由于条件变量需要Windows Vista及以上,编译时需要指定目标平台或确保运行环境支持。 完整代码:</think># 使用互斥锁实现线程循环唤醒机制 下面我将实现一个线程循环唤醒机制,使用互斥锁控制线程在唤醒-执行-等待的循环中运行。这种模式常用于工作线程、任务处理器等场景。 ## 完整解决方案 ### 使用互斥锁实现循环唤醒 ```cpp #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <atomic> #include <chrono> class CyclicWorker { private: std::mutex mtx; std::condition_variable cv; std::atomic<bool> running{false}; std::atomic<bool> wakeup_requested{false}; int wakeup_count = 0; std::thread worker_thread; void worker_function() { while (running) { // 等待唤醒信号 { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [this] { return wakeup_requested || !running; }); if (!running) break; // 重置唤醒标志 wakeup_requested = false; } // 执行工作 perform_task(); // 短暂休眠模拟工作完成 std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } void perform_task() { int current_count = ++wakeup_count; std::cout << "线程 " << std::this_thread::get_id() << " 执行任务 #" << current_count << " (" << current_count * 10 << "ms 工作)" << std::endl; } public: CyclicWorker() : running(true) { worker_thread = std::thread(&CyclicWorker::worker_function, this); } ~CyclicWorker() { stop(); } void wakeup() { { std::lock_guard<std::mutex> lock(mtx); wakeup_requested = true; } cv.notify_one(); } void stop() { if (running) { running = false; { std::lock_guard<std::mutex> lock(mtx); wakeup_requested = true; // 确保线程能退出 } cv.notify_one(); if (worker_thread.joinable()) { worker_thread.join(); } } } int get_wakeup_count() const { return wakeup_count; } }; int main() { CyclicWorker worker; std::cout << "按回车键唤醒工作线程 (输入 'q' 退出)..." << std::endl; char input = 0; while (input != 'q') { std::cin.get(input); if (input == '\n') { worker.wakeup(); std::cout << "唤醒信号已发送" << std::endl; } } worker.stop(); std::cout << "工作线程已停止,总共执行了 " << worker.get_wakeup_count() << " 次任务" << std::endl; return 0; } ``` ## 实现原理详解 ### 1. 循环唤醒机制设计 ```mermaid stateDiagram-v2 [*] --> 等待状态 等待状态 --> 唤醒状态: 收到唤醒信号 唤醒状态 --> 工作状态: 获取互斥锁 工作状态 --> 等待状态: 完成任务 等待状态 --> [*]: 停止信号 ``` ### 2. 关键组件说明 1. **互斥锁 (std::mutex)** - 保护共享状态变量 - 确保条件变量的正确使用 2. **条件变量 (std::condition_variable)** - 实现高效的线程等待/唤醒机制 - 避免忙等待(busy-waiting)消耗CPU资源 3. **原子变量 (std::atomic)** - `running`: 控制线程生命周期 - `wakeup_requested`: 指示唤醒请求状态 4. **工作循环流程** ```cpp while (running) { 1. 等待条件变量 (cv.wait) 2. 检查唤醒请求 3. 执行任务 (perform_task) 4. 返回等待状态 } ``` ### 3. 线程状态转换 ```mermaid sequenceDiagram participant Main as 主线程 participant Worker as 工作线程 Main->>Worker: 创建并启动线程 Worker->>Worker: 进入等待状态 Main->>Worker: wakeup() 发送唤醒信号 Worker->>Worker: 唤醒并执行任务 Worker->>Worker: 返回等待状态 Main->>Worker: 再次 wakeup() Worker->>Worker: 再次执行任务 Main->>Worker: stop() 发送停止信号 Worker->>Main: 线程退出 ``` ## 高级实现:带任务队列的循环唤醒 下面扩展为带任务队列的实现,可以处理多种任务: ```cpp #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> #include <functional> #include <atomic> class TaskWorker { private: std::mutex mtx; std::condition_variable cv; std::atomic<bool> running{false}; std::queue<std::function<void()>> task_queue; std::thread worker_thread; void worker_function() { while (running) { std::function<void()> task; // 等待任务或停止信号 { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [this] { return !task_queue.empty() || !running; }); if (!running && task_queue.empty()) break; // 获取任务 task = std::move(task_queue.front()); task_queue.pop(); } // 执行任务 if (task) { try { task(); } catch (const std::exception& e) { std::cerr << "任务执行异常: " << e.what() << std::endl; } } } } public: TaskWorker() : running(true) { worker_thread = std::thread(&TaskWorker::worker_function, this); } ~TaskWorker() { stop(); } template <typename F> void enqueue_task(F&& task) { { std::lock_guard<std::mutex> lock(mtx); task_queue.push(std::forward<F>(task)); } cv.notify_one(); } void stop() { if (running) { running = false; cv.notify_one(); if (worker_thread.joinable()) { worker_thread.join(); } } } size_t pending_tasks() const { std::lock_guard<std::mutex> lock(mtx); return task_queue.size(); } }; int main() { TaskWorker worker; // 添加不同类型的任务 worker.enqueue_task([] { std::cout << "任务A: 计算开始..." << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(200)); std::cout << "任务A: 计算完成" << std::endl; }); worker.enqueue_task([] { std::cout << "任务B: 数据处理..." << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(150)); std::cout << "任务B: 处理完成" << std::endl; }); worker.enqueue_task([] { std::cout << "任务C: 网络请求..." << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(300)); std::cout << "任务C: 请求成功" << std::endl; }); // 等待所有任务完成 while (worker.pending_tasks() > 0) { std::cout << "等待任务完成,剩余: " << worker.pending_tasks() << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } worker.stop(); return 0; } ``` ## 性能优化技巧 1. **批量唤醒处理** ```cpp void wakeup_multiple(int count) { { std::lock_guard<std::mutex> lock(mtx); wakeup_requested = true; wakeup_count += count; // 设置需要唤醒的次数 } cv.notify_one(); } // 在工作线程中 while (wakeup_count > 0) { perform_task(); wakeup_count--; } ``` 2. **线程池实现** ```cpp class ThreadPool { private: std::vector<std::thread> workers; std::queue<std::function<void()>> tasks; // ... 其他成员 public: ThreadPool(size_t threads) { for (size_t i = 0; i < threads; ++i) { workers.emplace_back([this] { while (running) { std::function<void()> task; { std::unique_lock<std::mutex> lock(queue_mutex); condition.wait(lock, [this] { return !tasks.empty() || !running; }); // ... 获取任务 } task(); } }); } } // ... 其他方法 }; ``` 3. **优先级任务队列** ```cpp struct Task { int priority; std::function<void()> func; bool operator<(const Task& other) const { return priority < other.priority; // 更高优先级 } }; std::priority_queue<Task> task_queue; ``` ## 应用场景 1. **事件驱动系统** - GUI应用程序中的事件处理 - 游戏引擎中的消息处理 2. **后台任务处理** - 文件下载队列 - 数据处理流水线 3. **服务端编程** - HTTP请求处理 - 数据库操作队列 4. **实时系统** - 传感器数据处理 - 实时控制任务 ## 编译与运行 ### 编译命令 ```bash # 使用g++编译 g++ -std=c++17 -pthread cyclic_worker.cpp -o worker # 使用MSVC编译 cl /EHsc /std:c++17 cyclic_worker.cpp ``` ### 运行示例 ``` 按回车键唤醒工作线程 (输入 'q' 退出)... [回车] 唤醒信号已发送 线程 140735226582784 执行任务 #1 (10ms 工作) [回车] 唤醒信号已发送 线程 140735226582784 执行任务 #2 (20ms 工作) q 工作线程已停止,总共执行了 2 次任务 ``` ## 安全注意事项 1. **死锁预防** - 避免在任务中长时间持有锁 - 使用RAII管理锁的生命周期 2. **异常安全** - 在任务周围使用try-catch块 - 确保异常不会导致线程终止 3. **资源清理** - 在析构函数中正确停止线程 - 确保所有任务完成后退出 4. **线程安全设计** - 所有共享数据必须受互斥锁保护 - 使用原子操作进行简单状态标记
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值