目录
thread
thread 类总览
构造函数
= join joinable
编辑
detach swap yield swap
成员函数的调用
namespace::this_thread
线程同步--锁
互斥锁mutex
递归锁recursive_mutex
定时锁
Lock 锁辅助类
lock_guard编辑
unique_lock
std::lock 解决死锁问题
消息队列
condition_variable
condition_variable
生产者消费者模型
atomic
call_once
chrono 时间库
thread
thread 类总览
构造函数
= join joinable
-
谁调用了这个函数?调用了这个函数的线程对象,一定要等这个线程对象的方法(在构造时传入的方法)执行完毕后(或者理解为这个线程的活干完了!),这个join()函数才能得到返回。
-
在什么线程环境下调用了这个函数?上面说了必须要等线程方法执行完毕后才能返回,那必然是阻塞调用线程的,也就是说如果一个线程对象在一个线程环境调用了这个函数,那么这个线程环境就会被阻塞,直到这个线程对象在构造时传入的方法执行完毕后,才能继续往下走,另外如果线程对象在调用join()函数之前,就已经做完了自己的事情(在构造时传入的方法执行完毕),那么这个函数不会阻塞线程环境,线程环境正常执行
detach swap yield swap
成员函数的调用
必须传入成员函数地址和调用的对象
namespace::this_thread
线程同步--锁
互斥锁mutex
递归锁recursive_mutex
递归锁出现的意义:假设存在这样一个场景,一个函数使用mutex 同时调用另外的一个函数里面有用到同一个mutex,则此时同一个互斥量被上了两次锁,导致死锁;而递归锁可以对互斥量拥有多层所有权,可以避免死锁;
同一个线程多次占用(递归占用次数有限,不能太多),可配合lock_guard使用,不通线程和互斥锁一致。
定时锁
Lock 锁辅助类
lock_guard
unique_lock
比lock_guard更灵活,主要用与条件变量一同使用
std::lock 解决死锁问题
消息队列
#include <list>
#include <thread>
#include <mutex>
#include<iostream>
class A
{
public://把收到的消息(玩家命令)入到一个队列的线程void inMsgRecvQueue(){for (int i = 0;i < 100;++i){cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;my_mutex.lock();msgRecvQueue.push_back(i);my_mutex.unlock();}}
bool outMsgLULProc(int& command){// my_mutex.lock();std::lock_guard<std::mutex> sguard(my_mutex);if (!msgRecvQueue.empty()){command = msgRecvQueue.front();msgRecvQueue.pop_front();// my_mutex.unlock();return true;}// my_mutex.unlock();return false; }
//把数据从消息队列中取出的线程void outMsgRecvQueue(){int command = 0;for (int i = 0;i < 100;i++){bool result = outMsgLULProc(command);if (result == true){cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;//可以考虑对命令(数据)进行处理}else{cout << "outMsgRecvQueue()执行,但是目前消息队列中为空" << i << endl;}}cout << "end" << endl;}
private:std::list<int> msgRecvQueue; //容器,专门用于代表玩家发送过来的命令std::mutex my_mutex; //创建一个互斥量
};
int main()
{A myobja;std::thread myOutnMsgObj(&A::outMsgRecvQueue,&myobja);std::thread myInMsgObj(&A::inMsgRecvQueue,&myobja);myInMsgObj.join();myOutnMsgObj.join();return 0;
}
condition_variable
condition_variable
生产者消费者模型
//condition_variable.h头文件#include <mutex>
#include <thread>
#include <chrono>
#include <deque>
#include <condition_variable>
namespace TestConditional_variable
{extern std::mutex g_cvMutex;extern std::condition_variable g_cv;extern std::deque<int>g_data_deque;extern const int MAX_NUM;extern int g_next_index;
const int PRODUCER_THREAD_NUM = 3;const int CONSUMER_THREAD_NUM = 3;
void producer_thread(int thread_id);void consumer_thread(int thread_id);
}//condition_variable.cpp头文件#include"Condition_variable.h"
#include<iostream>
namespace TestConditional_variable {
std::mutex g_cvMutex;std::condition_variable g_cv;std::deque<int>g_data_deque;const int MAX_NUM = 30;
int g_next_index = 0;void producer_thread(int thread_id){for (int i = 0; i < 4;i++){std::this_thread::sleep_for(std::chrono::milliseconds(500));std::unique_lock<std::mutex>lk(g_cvMutex);g_cv.wait(lk, [](){return g_data_deque.size() <= MAX_NUM; });//wait的第二个参数为可执行的OBJ 反复执行直到返回trueg_next_index++;g_data_deque.push_back(g_next_index);std::cout << "producer_thread" << thread_id << " producer data" << g_next_index;std::cout << " queue size:" << g_data_deque.size() << std::endl;g_cv.notify_all();}}
void consumer_thread(int thread_id){for (int i = 0; i < 4; i++){std::this_thread::sleep_for(std::chrono::milliseconds(500));std::unique_lock<std::mutex>lk(g_cvMutex);g_cv.wait(lk, [](){return !g_data_deque.empty(); });g_next_index++;int data = g_data_deque.front();g_data_deque.pop_front();std::cout << "consumer_thread" << thread_id << " consumer data" << g_next_index;std::cout << " queue size:" << g_data_deque.size() << std::endl;g_cv.notify_all();}}
}#include"Condition_variable.h"
#include<iostream>
int main(int argc, char *argv[])
{std::thread arrProducerThread[TestConditional_variable::PRODUCER_THREAD_NUM];std::thread arrConsumerThread[TestConditional_variable::CONSUMER_THREAD_NUM];
for (int i = 0; i < TestConditional_variable::PRODUCER_THREAD_NUM; i++){arrProducerThread[i] = std::thread(TestConditional_variable::producer_thread, i);}for (int i = 0; i < TestConditional_variable::CONSUMER_THREAD_NUM; i++){arrConsumerThread[i] = std::thread(TestConditional_variable::consumer_thread, i);}for (int i = 0; i < TestConditional_variable::PRODUCER_THREAD_NUM; i++){arrProducerThread[i].join();}for (int i = 0; i < TestConditional_variable::CONSUMER_THREAD_NUM; i++){arrConsumerThread[i].join();}return 0;
}
atomic
原子变量
call_once
在某些特定情况下,某些函数只能在多线程环境下调用一次,比如:要初始化某个对象,而这个对象只能被初始化一次,就可以使用 std::call_once() 来保证函数在多线程环境下只能被调用一次。使用 call_once() 的时候,需要一个 once_flag 作为 call_once() 的传入参数。
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
flag:once_flag 类型的对象,要保证这个对象能够被多个线程同时访问到。
f:回调函数,可以传递一个有名函数地址,也可以指定一个匿名函数
args:作为实参传递给回调函数。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;once_flag g_flag;
void do_once(int a, string b)
{cout << "name: " << b << ", age: " << a << endl;
}void do_something(int age, string name)
{static int num = 1;call_once(g_flag, do_once, 19, "luffy");cout << "do_something() function num = " << num++ << endl;
}void test_call_once()
{thread t1(do_something, 20, "ace");thread t2(do_something, 20, "sabo");thread t3(do_something, 19, "luffy");t1.join();t2.join();t3.join();
}
chrono 时间库
duration
定义于头文件 <chrono>
template<
class Rep,
class Period = std::ratio<1>
> class duration;
这是一个数值类型,表示时钟数(周期)的类型(默认为整形)。若 Rep 是浮点数,则 duration 能使用小数描述时钟周期的数目
Period:表示时钟的周期,它的原型如下
// 定义于头文件 <ratio>
template<
std::intmax_t Num,//周期的分子
std::intmax_t Denom = 1//周期的分母 默认为1
> class ratio;
ratio 类表示每个时钟周期的秒数,其中第一个模板参数 Num代表分子,Denom代表分母,该分母值默认为 1,因此,ratio代表的是一个分子除以分母的数值,比如:ratio<2> 代表一个时钟周期是 2 秒,ratio<60 > 代表一分钟,ratio<60*60 > 代表一个小时,ratio<60*60*24 > 代表一天。而 ratio<1,1000 > 代表的是 1/1000 秒,也就是 1 毫秒,ratio<1,1000000 > 代表一微秒,ratio<1,1000000000 > 代表一纳秒。
void test_chrono()
{using namespace std;chrono::hours h(1); // 一小时chrono::milliseconds ms{ 3 }; // 3 毫秒 初始化操作 ms{3} 表示一共有 3 个时间周期,每个周期为 1 毫秒std::chrono::microseconds us = 2 * ms; // 6000 微秒chrono::duration<int, ratio<1000>> ks(3); // 3000 秒// chrono::duration<int, ratio<1000>> d3(3.5); // error//dd(6.6) 时钟周期为默认的 1 秒,共有 6.6 个时钟周期,所以 dd 表示的时间间隔为 6.6 秒chrono::duration<double> dd(6.6); // 6.6 秒 周期类型为小数// 使用小数表示时钟周期的次数//hz(3.5) 时钟周期为 1 / 30 秒,共有 3.5 个时钟周期,所以 hz 表示的时间间隔为 1 / 30 * 3.5 秒chrono::duration<double, std::ratio<1, 30>> hz(3.5);//count统计周期数std::cout << "3 ms duration has " << ms.count() << " ticks\n" //3<< "6000 us duration has " << us.count() << " ticks\n" //6000<< "3.5 hz duration has " << hz.count() << " ticks\n"; //3.5//重载了++ -- + - = 等操作chrono::minutes t1(2);chrono::seconds t2(2);chrono::seconds t3 = t1 - t2;chrono::seconds t4 = t1 + t2;t4++;cout << t3.count() <<"seconds" << endl;//118secondscout << t4.count() << "seconds" << endl;//123seconds/*注意事项:duration 的加减运算有一定的规则,当两个 duration 时钟周期不相同的时候,会先统一成一种时钟,然后再进行算术运算,统一的规则如下:假设有 ratio<x1,y1> 和 ratio<x2,y2 > 两个时钟周期,首先需要求出 x1,x2 的最大公约数 X,然后求出 y1,y2 的最小公倍数 Y,统一之后的时钟周期 ratio 为 ratio<X,Y>*/chrono::duration<double, ratio<9, 7> >t5(3);//3* 9/7秒chrono::duration<double, ratio<4, 3>> t6(3); // 3 * 4/3秒chrono::duration<double, ratio<1, 21>> t7 = t6 - t5;cout << t7.count() << "ticks" << endl;//3ticks}