目录
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 反复执行直到返回true
g_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;//118seconds
cout << 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
}