Class MyCAS //这是一个单例类
{
private:
MyCAS() {} //私有化了构造函数
Static MyCAS *m_instance; //静态成员变量
public:
Static MyCAS *GetInstance()
{
If(m_instance == NULL)
{
m_instance = new MyCAS();
Static Cgarhuishou cl;
}
return m_instance;
}
Class CGarhuishou //类中套类,用来释放对象
{
Public:
~CGarhuishou ()
{
If(MyCAS::m_instance)
{
Delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
}
Void func()
{
Cout << "测试" << endl;
}
}
面临的问题:在自己的线程中创建单例设计模式类,即不是主线程,而且这种线程不止一个,就可能面临需要getInStance成员函数互斥。
解决:在创建对象的函数中使用锁,再使用双重检查(双重锁定)的写法提高效率。
Class MyCAS //这是一个单例类
{
private:
MyCAS() {} //私有化了构造函数
static MyCAS *m_instance; //静态成员变量
static std::mutex mtx;
public:
static MyCAS *GetInstance()
{
//双重锁定。因为只是需要在第一次new的时候加锁,为了防止每次都要加锁,加上两个判断。效率提高。
if(m_instance == NULL){
std::unique_lock uniLock(mtx);
if(m_instance == NULL)
{
m_instance = new MyCAS();
static Cgarhuishou cl;
}
}
return m_instance;
}
class CGarhuishou //类中套类,用来释放对象
{
public:
~CGarhuishou ()
{
If(MyCAS::m_instance)
{
Delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
}
void func()
{
cout << "测试" << endl;
}
}
//静态成员默认初始化
MyCAS *MyCAS::m_instance = nullptr;
Class MyCAS //这是一个单例类
{
private:
MyCAS() {} //私有化了构造函数
static MyCAS *m_instance; //静态成员变量
static std::once_flag oneFlag;
static void createInstance(){
m_instance = new MyCAS();
static CGarhuishou cl;
}
public:
static MyCAS *GetInstance()
{
std::call_once(oneFlag, createInstance);//比如两个线程执行到这,抢先的线程执行了 call_once(),另一个线程就必须等待,等到抢先的线程执行完毕,会通过标志位oneFlag反馈给别的线程,以告知他们不用再执行这个函数,通过这种机制,实现这个函数的 call_once();
return m_instance;
}
class CGarhuishou //类中套类,用来释放对象
{
public:
~CGarhuishou ()
{
If(MyCAS::m_instance)
{
Delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
}
void func()
{
cout << "测试" << endl;
}
}
//静态成员默认初始化
MyCAS *MyCAS::m_instance = nullptr;
std::condition_variable 条件变量:就是一个类,等待一个条件达成,和互斥量配和使用
1.wait() 和 notify()配合
wait(mutex, predicate); 第二个参数默认是fasle,即wait()会unlock mutex的对象,但是会堵塞在wait()这一行,直到别的线程notify()执行,wait()才会返回。
但是可以传入一个predicate,比如lambda表达式,返回的结果如果是false,效果同上。返回true,wait立即返回,不堵塞。
2.别的线程执行了notify_one()后,首先notify所在的线程肯定要先unlock互斥量。然后wait()会再次获得互斥量的锁,对互斥量进行lock。对互斥量lock()后:
(1)如果wait()的第二个predicate有参数,会再次判断predicate是否为true,
+ 如果是true,则wait()返回,不再堵塞,但是此时并没有对互斥量unlock,因为下面还需对数据进行处理。wait()的行为相当于等待别的线程产生的数据,这个线程接受、处理,所以wait返回后,依然处于lock状态,待处理完数据,可以等到编译器自己unlock,或者自己手动unlock。
+ 如果是false,则继续堵塞,等待被notify()唤醒。重复上述过程。
(2)如果wait()的第二个predicate没有参数,默认是true。效果和返回true一样。
3.注意:
(1)notify_once唤醒了wait()后,不一定wait()会成功对互斥量lock(),因为可能notify()所在的线程,在notify对互斥量unlock()后会再次进行试图对它lock。因此不一定Wait就会成功对互斥量lock成功。
+ 因此,wait()因为多次没有lock而积累太多数据,处理不过来怎么办???具体应用中应该考虑的问题----可以限流
(2)如果某个线程执行力notify_once,但是另一个线程的程序当前没有堵塞在wait(),而是在别的地方执行,那么执行了notify_once的执行毫无意义。
4.notify_all():notifies all waiting threads
5.虚假唤醒:wait()中要有第二个参数(lambda)并且这个lambda要正确判断要处理的公共程序是否存在;
wait(),notify_one(),notify_all()
1.std::async、std::future
(1)std::async:函数模板,启动一个异步任务,它返回一个std::future类模板对象。
(2)std::future
+ get() //等待,直到获取子线程返回值,解除堵塞。注意只能调用一次,因为get()是用移动语义实现的,使用future.get()后,再次使用future.get()将变成nullptr。
+ wait() //等待,直到子线程结束,不需要返回值,解除堵塞
启动一个异步任务,就是自动创建一个线程并且开始执行对应的线程入口函数,它返回一个std::future类模板对象。在这个对象里,含有线程入口函数的返回结果,即线程返回的结果,我们可以通过std::future对象的成员函数get()获取结果。
std::future,提供了一种访问异步操作结果的机制,就是说这个结果你可能没有办法马上拿到,在线程执行完毕的时候,你就能够拿到结果了。
int myThread(){
std::cout<<"subThread id: "< retVal = std::async(myThread);//线程开始执行,虽然线程函数会延迟5s,但是不会卡在这儿
for (size_t i = 0; i < 10; i++)
std::cout<<"test..."<
比如,在上面的这个程序里, std::future<int> retVal = std::async(myThread);启动一个线程后,虽然线程函数里有个5s的延时操作,主线程并不会等待这个子线程的完成才继续执行下去,只有当主线程里需要用到子线程的数据时,才会等到子线程的结束,比如retVal.get()会使得主线程必须等到子线程结束。因此这个futute,可以理解为让主线程在将来用到子线程返回值时才等到,否则不等。
这个功能就类似于 thread::join(),让主线程等到子线程的完成,但是这个异步性质,可以等价准备的控制等待的时间,即,在哪等待。
比如:
std::future retVal = std::async(myThread);
for (size_t i = 0; i < 1000; i++)
std::cout << "test_1..." << i << std::endl;
std::cout << "\nsubThread return value: " << retVal.get() << std::endl;
for (size_t i = 0; i < 1000; i++)
std::cout << "test_2..." << i << std::endl;
第一个for循环,会和子线程争夺资源,但是第二个线程必须等子线程结束才能执行,如果使用join(),第一for循环结束,第二个就会执行。使用异步性质,可以控制程序的执行顺序。
注意:如果没有调用get()或者wait(),那么子线程会在main()return前将子线程执行结束。相当于编译器自己在主线程结束前帮你写了一个wait()。
2.std::async
第一个参数:std::launch类型,是一个枚举类型,来实现特殊的目的:
/// Launch code for futures
enum class launch
{
async = 1,
deferred = 2
};
std::future retVal = std::async(std::launch::deferred,&Async::myThread, &ayc, val);
(1)std::launch::deferred:
(2)std::launch::async
(3)std::launch::deferred | std::launch::asysnc
int myThread(){
std::chrono::milliseconds time(5000); //等待时间为5s
std::this_thread::sleep_for(time);
std::cout<<"subThread id: "< ret = std::async(myThread);
std::future_status stus = ret.wait_for(std::chrono::seconds(0)); //等待子线程的时间
if(stus == std::future_status::deferred){
//系统资源紧张了,它采用了std::launch::deferred策略
std::cout<<"main|异步任务延迟执行|返回值: "<
3.std::packaged_task
std::packaged_task是个类模板,模板参数是各种可调用对象,通过std::packaged_task把各种可调用对象包装起来,以作为线程入口函数。
用法如下:
int myThread(int val){
/***代码***/
}
int main(int argc, char const *argv[])
{
std::cout << "current id: " << std::this_thread::get_id() << std::endl;
std::packaged_task pkg(myThread);
std::thread trd(std::ref(pkg), 10);
trd.join();
std::future ret = pkg.get_future();
std::cout << ret.get() << std::endl;
std::cout << "Main thread.\n";
return 0;
}
std::packaged_task 对象将线程入口函数myThread进行封装,然后传入std::thread 对象,和普通线程一样,调用join(),当再次调用ret.get()就不会再等待,前面join()已经等待子线程执行结束了。ret.get()就可以直接获取值。如果不加join(),直接ret.get(),会报错。
4.std::promise:类模板
能够在某个线程中给它赋值,然后在其他线程中,把这个值取出来。std::promise
与取值有关的函数是std::future
//这个线程计算
void mythread(std::promise& prom, int val){
val++;
//假设这个线程花了2s, 得到了运算结果
prom.set_value(val);
return;
}
//这个线程使用上面那个线程的计算结果
void myTrd(std::future& future){
int ret = future.get();
std::cout<<"myTrd val: "< prom; //int 为保存的数据类型,通过这个prom实现两个线程的数据交互
std::thread trd(mythread, std::ref(prom), 10);
std::future future = prom.get_future(); // 获取结果值
std::thread trd2(myTrd, std::ref(future)); // 传递给第二个线程
std::cout<<"main thread.\n";
trd.join();
trd2.join();
return 0;
}
5.总结
std::asysnc
std::packaged_task,
std::promise:更像是一个传递数据的变量,通过它在线程之间传递数据。
(1)他们都可以和std::future
(2)std::async
(a)async如果创建子线程,如果子线程还没结束,主线程任务已经执行结束,那么主线程会在结束前将子线程任务执行结束再返回。
(b)async更加准确的叫法是,创建一个异步任务,但并不一定创建一个子线程,
+ std::launch:deferred传入时,谁调用get就当前线程里创建异步任务,并没有创建新的子线程;
+ std::launch::async传入时,强制创建子线程执行异步任务。
(c)std::asysnc与std::thread区别
+ thread创建线程,如果系统资源紧张,创建线程失败,那么整个程序就会报异常崩溃(有脾气),而async会强制创建一个新的线程
+ thread如果想获取线程的返回值,或者一些自己需要的中间值,不容易实现。但是async返回的是std::future
(d)由于系统资源限制:
+ 如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,奔溃。
+ 如果用std::async,一般就不会报异常不会奔溃,因为,如果系统资源紧张导致无法创建新线程的时候,
std::async这种不加额外参数的调用就不会创建新线程。而是后续谁调用了result.get()来请求结果,那么这个异步任务就运行在执行这条get()语句所在的线程上。
如果强制用std::async一定要创建新线程,那么就必须使用std::launch::async。承受的代价就是系统资源紧张时,程序奔溃。
+ 经验:一个程序里,线程数量不宜超过100-200。
6.future_status
enum class future_status{
ready,
timeout,
deferred
};
使用:
std::future<int> ret = std::async(std::launch::deferred,myThread, 10);
std::future_status stus = ret.wait_for(std::chrono::seconds(6)); //等待子线程的时间
7.std::shared_future
由于future.get()只能调用一次,所以要想实现不同线程之间通过future实现数据共享,那么怎么办?使std::shared_future<T> ,它也是个类模板
用法:
std::packaged_task pkg(myThread);
std::thread trd(std::ref(pkg), 10);
trd.join();
// std::future ret_1 = pkg.get_future();
// std::shared_future ret(pkg.get_future());
//std::shared_future ret = pkg.get_future();
std::shared_future ret(std::move(ret_1));
这样ret就可以反复的回去线程的返回值,顾名思义,share_future
std::atomic
情况:
有两个线程,一个线程对一个变量进行读取操作,另一个线程对一个变量进去写操作,如果任由两个线程自由进行操作,最终读取到的值,可能就不是当前值,也不是写线程操作之后的值,或许是一个不可预料的中间值。
比如两个线程同时对一个变量进行写操作,
int commVar = 0;
std::mutex mtx;
void Write(){
for (size_t i = 0; i < 1000000; i++) {
mtx.lock();
commVar++;
mtx.unlock();
}
return;
}
int main(int argc, char const *argv[]){
std::thread t1(Write);
std::thread t2(Write);
t1.join();
t2.join();
std::cout<
需要通过互斥量mutex,来保持两个线程的有序进行,如果不加锁输出的值不可预料,不是理想的结果。现在提供一次新的技术,即原子操作技术,不需要加锁也能保证多个线程对同一块数据进行有序访问,要么访问到的是其余线程没有对这个数据进行操作前的值(即原来的值),或者是被别的线程改动后的值,而不是中间值。
原子操作:在程序执行中,不会被别的线程打断的程序片段。注意了,原子操作针对的是单个变量,而不是大段的代码段,大段的代码还是需要mutex实现。
原子操作状态:要么是完成的,要么是未完成的,不可能出现半中间状态。
std::atomic操作:其余不变
std::atomic commVar(10);
void Write(){
for (size_t i = 0; i < 1000000; i++)
commVar++;
return;
}
注意:原子操作支持的变量操作:++、--,+=,-=,&=,之类;不支持 var = var+1 之类。
拷贝构造函数,拷贝构造运算符不能用
atomic
auto atm2(atm.load())
Load()以原子方式读
Store()以原子方式写
#include
1.临界区基本用法,类似mutex的lock()、unlock()
EnterCriticalSection(&winsSec);
msg.push_back(i);
LeaveCriticalSection(&winsSec);
2.在“同一个线程”(不同线程就会卡住等待)中,windows中的“相同临界区变量”代表的临界区的进入(EnterCriticalSection)可以被多次执行,但是你调用了几次EnterCriticalSection,你就得调用几次LeaveCriticalSection;
而在c++11中,不允许同一个线程中lock同一个互斥量多次,否则报异常
EnterCriticalSection(&winsSec); //ok
EnterCriticalSection(&winsSec);
msg.push_back(i);
LeaveCriticalSection(&winsSec);
LeaveCriticalSection(&winsSec);
3.自动析构技术:
windows临界区实现mutex的自动lock()和unlock()操作(std::lock_guard(std::mutex))。
RAII(resource aquisition is initialization)类,“资源获取即初始化”,容器,智能指针这种类,都属于RAII类
在构造函数里进行初始化,在析构函数里进行释放。
class uniLockWins{
private:
CRITICAL_SECTION *_critical_sec;
public:
uniLockWins(CRITICAL_SECTION *sec){
_critical_sec = sec;
EnterCriticalSection(_critical_sec);
}
~uniLockWins(){LeaveCriticalSection(_critical_sec);}
};
1.recursive_mutex:允许同一个线程同一个互斥量多次lock()/unlock()
2.带超时的互斥量std::timed_mutex和std::recursive_timed_mutex
(1)std::timed_mutex:是带超时功能的独占互斥量
Try_lock_for() : 参数是一段时间,是等待一段时间,如果我拿到锁,或者等待超过时间没拿到锁,就走下来;
Try_lock_until() : 参数是一个未来的时间点,在这个未来的时间没到的时间内,如果拿到了锁,那么就走下来
(2)std::recursive_timed_mutex:是带超时功能的递归独占互斥量
3.浅谈线程池
(1)场景设想
服务器程序,--》客户端,每来 一个客户端,就创建 一个线程为该客户提供服务。
a)网络游戏,2万玩家不可能给每个玩家创建个新线程,此程序写法在这种场景下不通;
b)程序稳定性问题:编写的代码中,偶尔创建一个线程这种代码,这种写法,就让人感到不安;
线程池:把一堆线程弄到一起,统一管理。这种统一管理调度,循环利用线程的方式,就叫线程池;
(2)实现方式
在程序启动时,我一次性创建好一定数量的线程。10,8,100-200,更让人放心,觉得程序代码更稳定;
4.线程创建数量谈
(1)线程开的数量极限问题,2000个线程基本就是极限,再创建线程就崩溃;
(2)线程创建数量建议
a)采用某些技术开发程序;api提供商建议 创建线程的数量 = cpu数量, cpu*2, cpu*2+2, 遵照专业建议和指示来,专业意见确保程序高效率执行;
b)创建多线程完成业务;一个线程等于一条执行通路;100要堵塞充值,我们这里开110个线程,那是很合适的;
c)1800个线程,建议,线程数量尽量不要超过500个,能控制在200个之内。