1.threadControl.cpp
#include <iostream>
#include <mutex>
#include <vector>
#include <thread>
#include <chrono>
#include <condition_variable>
enum THREAD_CONTROL
{
THREAD_RUN,
THREAD_PAUSE,
THREAD_STOP,
THREAD_INVALID
};
THREAD_CONTROL g_threadStatus = THREAD_INVALID;
std::mutex g_threadMutex;
std::condition_variable g_threadcv;
void ThreadRun()
{
if (g_threadStatus == THREAD_PAUSE)
{
std::unique_lock<std::mutex> lck(g_threadMutex);
g_threadStatus = THREAD_RUN;
g_threadcv.notify_all();
}
}
void ThreadPause()
{
if (g_threadStatus == THREAD_RUN)
{
std::unique_lock<std::mutex> lck(g_threadMutex);
g_threadStatus = THREAD_PAUSE;
}
}
void ThreadStop()
{
// 在暂停状态点击了停止,需要先让线程解除阻塞,然后才停止
if (g_threadStatus == THREAD_PAUSE)
{
std::unique_lock<std::mutex> lck(g_threadMutex);
g_threadStatus = THREAD_STOP;
g_threadcv.notify_all();
}
std::unique_lock<std::mutex> lck(g_threadMutex);
g_threadStatus = THREAD_STOP;
}
void printThreadID(int num)
{
while(1)
{
std::cout << "the thread NO is:" << num << ", ID is:" << std::this_thread::get_id() << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
// while 判断条件, 防止虚假唤醒
while (g_threadStatus == THREAD_PAUSE)
{
std::cout << "thread " << num << " pause!\n";
std::unique_lock<std::mutex> tmp(g_threadMutex);
g_threadcv.wait(tmp);
}
if (g_threadStatus == THREAD_STOP)
{
std::cout << "thread " << num << " stop!\n";
break;
}
}
}
int main(int argc, char *argv[])
{
g_threadStatus = THREAD_RUN;
std::cout << "按任意键开始:\n";
std::cout << "开始之后输入数字控制线程: 1-暂停, 2-继续, 3-停止:\n";
std::string tmp;
std::cin >> tmp;
// 使用容器管理线程
std::vector<std::thread> vecThread;
for (int i = 0; i < 4; ++i)
{
vecThread.push_back(std::thread(printThreadID, i));
}
// 开始手动控制线程
int cmd = -1;
while (std::cin >> cmd)
{
switch (cmd)
{
case 1:
{
ThreadPause();
break;
}
case 2:
{
ThreadRun();
break;
}
case 3:
{
ThreadStop();
break;
}
default:
{
break;
}
}
if (3 == cmd)
{
break;
}
}
// 回收线程
for (int i = 0; i < vecThread.size(); ++i)
{
if (vecThread[i].joinable())
{
vecThread[i].join();
}
}
return 0;
}
2.需求说明
对运行中的线程进行暂停、继续、停止的操作十分必要,比如我们视屏或音乐播放软件的暂停、停止、继续播放,360检查的暂停检查、继续、取消检查等等。
3.代码效果
4.原理及其代码细节
1、首先条件变量是能够阻塞线程的最经典的方法,条件变量一定要有互斥量保护,当条件发生收到一定的信号(标志位改变)可以阻塞线程,当标志位再次改变时又将重新从阻塞的地方继续执行(notify_all()或者notify_one()),notify_all()唤醒所有线程,notify_one()唤醒一个阻塞的线程,很多人认为notify_one()没有用,但是大名鼎鼎的线程池就是用这个来唤醒一个线程的,所以场景不同,使用的东西就不同,大家都很有用武之地。这样我们就能很轻松地根据条件来控制线程。
2、注意判断条件时要使用while判断避免虚假唤醒,虚假唤醒是指当线程被唤醒之后发现标志位或者资源被其他线程竞争走了导致了很尴尬的局面,极有可能导致程序的崩溃,逻辑的错误,这是操作系统的锅(为了给操作系统提供处理错误情况和竞争实现灵活性,条件变量即使没有被触发,它也可能被允许返回),但是我们要谨慎行事,当被唤醒时再判断一次。
3、注意停止的情形很容易犯错,因为可能会有最简单的情形运行时停止,但是也很有可能发生暂停之后我就要停止了,线程中停止的判断一定在暂停之后(之前的话,都要停止了你又要人家再循环一次?),所以停止信号发出,就要将线程唤醒再置位标志位,否则根本进入不到下面的判断。