单例设计模式共享数据分析、解决,call_once
什么是设计模式
设计模式是代码的特殊写法,程序灵活,维护起来可能方便,但是代码可读性低。
用设计模式理念写出的代码是很晦涩的,《head first》开始流行。是外国对于 特别大的项目,把项目开发经验、模块划分经验,总结整理成 设计模式(先有开发需求,后有理论总结和整理)。
设计模式有独特的有点,需要活学活用,不能生搬硬套。
单例设计模式
单列设计模式,使用的频率比较高;
单例:整个项目中,有某个或者某些特殊的类,属于该类的对象,我只能创建1个。
#include <iostream>
#include <windows.h>
using namespace std;
class MyCAS // 这是一个单例类
{
private:
MyCAS() {}//私有化的构造函数 不能直接生成类对象
private:
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 != NULL)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void func()
{
cout << "测试" << endl;
}
};
//类静态变量初始化
MyCAS* MyCAS::m_instance = NULL;
int main()
{
MyCAS* p_a = MyCAS::GetInstance(); //创建一个对象,返回该类对象的指针。
//MyCAS* p_b = MyCAS::GetInstance(); //都指向同一个对象。
p_a->func();
MyCAS::GetInstance()->func();//与上一行相同
}
主要思想是将构造函数设为私有,然后通过静态成员函数给静态成员变量赋值为对象,这样可以通过判断静态成员变量来达到只能够有一个对象的目的。因为程序中new了一个对象,就用类中套类的方式,在另一个类析构的时候释放new的堆区。
整个类的写法就是一种设计模式。
单例设计模式共享数据问题分析、解决
问题:需要在自己创建的线程(而不是主线程中)创建MyCAS这个单列类对象,这种线程可能不止一个(最少两个)。这时,Get Instance()需要互斥。
直接加锁会导致其他线程需要调用构造函数时都等待,减少了效率。
#include <windows.h>
#include <iostream>
#include<mutex>
using namespace std;
std::mutex resource_mutex;
class MyCAS // 这是一个单例类
{
private:
MyCAS() {}//私有化的构造函数 不能直接生成类对象
private:
static MyCAS* m_instance;//静态成员变量
public:
static MyCAS* GetInstance()
{
//提高效率
//如果m_instance != NULL条件成立。则表示m_instance已经被new过了。
//如果m_instance == NULL,不代表m_instance一定没有被new过。
if (m_instance == NULL)//双重锁定
{
std::unique_lock<std::mutex> mymutex(resource_mutex);//自动加锁 效率太低
if (m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl;
}
return m_instance;
}
}
class CGarhuishou //类中套一个类,用来释放对象
{
public:
~CGarhuishou()
{
if (MyCAS::m_instance != NULL)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void func()
{
cout << "测试" << endl;
}
};
//类静态变量初始化
MyCAS* MyCAS::m_instance = NULL;
void mythread()
{
cout << "我的线程开始执行" << endl;
MyCAS* p_a = MyCAS::GetInstance();
cout << "我的线程执行结束" << endl;
return;
}
int main()
{
//如下两个线程,虽然两个线程是同一个入口函数,但是两个线程,所以这里会有两个通路同时开始执行mythread这个函数
std::thread mytobj1(mythread);
std::thread mytobj2(mythread);
mytobj1.join();
mytobj2.join();
}
双重锁的存在能够很好的解决问题。
std::call_once()
call_once()是一个函数模板,C++11引入的函数,该函数的第二个参数是一个函数名a();
功能是能够保证函数a()只被调用一次。
call_once()具备互斥量的能力、而且效率上比互斥量消耗资源更少。
call_once()需要与标记结合使用,这个标记是std::onec_flag; once_flag是一个结构。 call_once()就是通过这个标记决定对应的函数a()是否执行调用成功后就把标记设为已调用状态,再次调用call_once()的时候,只要once_flag被设置为“已调用”状态函数a()就不会执行了。
修改类代码:
std::once_flag g_flag;//这时一个系统第一的标记class MyCAS // 这是一个单例类{ static void CreateInstance () //只被调用一次 { m_instance = new MyCAS(); static CGarhuishou cl; }private: MyCAS() {}//私有化的构造函数 不能直接生成类对象private: static MyCAS* m_instance;//静态成员变量public: static MyCAS* GetInstance() { std::call_once(g_flag, CreateInstance); return m_instance; } class CGarhuishou //类中套一个类,用来释放对象 { public: ~CGarhuishou() { if (MyCAS::m_instance != NULL) { delete MyCAS::m_instance; MyCAS::m_instance = NULL; } } }; void func() { cout << "测试" << endl; }};
condition_variable、wait、notify_one、notify_all
条件变量std::condition_variable、wait()、notify_one()
线程A:等待一个条件满足
线程B:专门往消息队列中传入数据
condition_variable:条件变量,实际上是一个和条件相关的类,说白了就是等待一个条件达成。这个类需要和互斥量来配合工作,用的时候我们要生成这个类的对象。
wait()是condition_variable的成员函数,用来等待一个东西,如果二个参数lambda表达式返回值是true,直接返回;如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量并阻塞到本行,阻塞到其他某个线程调用notify_one() 成员函数为止。
如果wait()没有第二个参数,那么就跟第二个参数lambda表达式返回false效果一样。
notify_one()将阻塞状态的wait() 唤醒,wait恢复工作:a)wait() 不断尝试重新获取互斥量锁,如果获取不到,那么流程就开在wait等着获取。如果获取到了,上锁、继续往下面走,执行b。
b.1)如果wait有第二个参数(lambda),就判断这个lambda表达式,如果表达式为false,那么重复wait阻塞解锁的过程。
b.2)如果lambda表达式为true,则wait返回。代码继续往下走(此时互斥锁被锁住)。
b.3)如果wait没有第二个参数,则wait返回,代码继续往下走。
#include<thread>
#include <iostream>
#include <windows.h>
#include<mutex>
#include<list>
#include<map>
#include<vector>
#include<mutex>
using namespace std;
class A
{
public:
//把收到的消息放入队列的线程
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;
std::unique_lock<std::mutex> sbguard(my_mutex1);
msgRecvQueue.push_back(i);//假设数字i就是收到的命令,直接加入消息队列
my_cond.notify_one();//尝试把wait线程唤醒。
}
}
void outMsgRecvQueue()
{
int command = 0;
while (true)
{
std::unique_lock<std::mutex> sbguard1(my_mutex1);
my_cond.wait(sbguard1,[this] {
if (!msgRecvQueue.empty())
return true;
return false;
});
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
sbguard1.unlock();
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
}
private:
list<int> msgRecvQueue;//容器,专门用于代表玩家发过来的命令
std::mutex my_mutex1;//一个互斥量
std::mutex my_mutex2;//另一个互斥量
std::condition_variable my_cond;//生成一个条件变量对象
};
int main()
{
A myobja;
thread myOutnMsgobj(&A::outMsgRecvQueue, &myobja);//第二个参数是引用,才能保证线程使用同一个对象。
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutnMsgobj.join();
myInMsgobj.join();
return 0;
}
上面代码有一些问题:1.wait拿锁的同时,输入线程也在不间断的加锁,可能输入十几个最后只输出了一个。
2.假如唤醒的线程正在处理一个事务,需要一段时间,而不是正卡在wait()那里等待,那么此时这次的notify_one()就没有效果。
上述代码深入思考
稳定才是最重要的,不一定非要用这种方法,因为上述问题目前也还没有解决。
notify_all()
notify_one()只能唤醒一个线程;
notify_all()可以通知唤醒所有线程;
async、future、packaged_task、promise
std::async、std::future创建后台任务并返回值
如果希望线程执行完毕后返回结果,std::async、std::future可以很好的得到效果。
std::async 是函数模板,用来启动一个异步任务,启动一个异步任务之后,它返回一个std::future,std::future是一个类模板
异步任务:就是自动创建一个线程,并开始执行对应的线程入口函数,它返回一个std::future对象,对象里面就含有线程入口函数所返回的结果(线程返回的结果)。可以通过调用fuiture对象的成员函数 get() 成员函数来得到结果。
”future“ 提供一种访问异步操作结果的机制,结果可能不能马上拿到,但是在线程执行完毕的时候就能够得到结果。future对象会保存一个值,线程执行完毕后能够拿到。
#include<thread>
#include <iostream>
#include <windows.h>
#include<mutex>
#include<list>
#include<map>
#include<vector>
#include<mutex>
#include<future>
using namespace std;
int mythread()//线程入口函数
{
cout << "mythread() start" << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::seconds dura(5);//休息5s
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "thread id = " << std::this_thread::get_id() << endl;
return 5;
}
int main()
{
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(mythread);//创建一个线程开始执行,绑定关系
cout << "continue.....!" << endl;
int def;
def = 0;
cout << result.get() << endl;//阻塞在这里等待线程执行完毕拿到结果。
cout << "主线程执行完毕!" << endl;
return 0;
}
运行结果:
main thread id = 10180
continue.....!
mythread() startthread id = 6716
mythread() endthread id = 6716
5
主线程执行完毕!
总结:以上程序是通过get()函数等待线程结束并且返回结果。get() 函数在没有拿到线程返回值的时候就会一直阻塞等待,所以在使用future的时候必须保证线程有返回值。且get() 只能调用一次。
为什么get()只能调用一次?
get()函数设计是一个移动语义,调用get时在移动函数中完成运算,并且里面对象会移动变为空。
result.wait
();函数可以等待线程结束,但是不获取返回值。
ps:和之前一样,可以用类的成员函数来做线程入口函数。
改写之后:
#include<thread>
#include <iostream>
#include <windows.h>
#include<mutex>
#include<list>
#include<map>
#include<vector>
#include<mutex>
#include<future>
using namespace std;
class A {
public:
int mythread(int mypar)//线程入口函数
{
cout << mypar << endl;
cout << "mythread() start" << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "thread id = " << std::this_thread::get_id() << endl;
return 5;
}
};
int main()
{
A a;
int tmpper = 12;
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(&A::mythread,&a,tmpper);//第二个参数是一个对象引用,第三个参数是线程需要的传参。
cout << "continue.....!" << endl;
int def;
def = 0;
cout << result.get() << endl;//阻塞在这里等待线程执行完毕拿到结果。
cout << "主线程执行完毕!" << endl;
return 0;
}
可以通过向std::async() 传递一个参数,该参数是std::lunch类型(枚举)类型,来达到特殊的目的;
a)std:launch::diferred : 表示线程入口函数调用被延迟到std::future的wait() 或者get() 函数调用时才执行; 如果wait()或者get() 没有被调用,那么线程会执行吗?
std::future<int> result = std::async(std::launch::deferred,&A::mythread,&a,tmpper);
注释掉get()后,可以发现线程没有执行。实际上,线程没有被创建。这时,只有在 get() 或者 wait() 的时候才执行。
main thread id = 13292
continue.....!
12
mythread() startthread id = 13292
mythread() endthread id = 13292
5
主线程执行完毕!
但是这时是在主线程中执行的。所以std::launch::deferred不仅是延迟调用,并且没有创建新线程,是在主线程中调用的。
b)std::launch::async:表示在调用async函数的时候就开始创建线程;
修改std::future<int> result = std::async(std::launch::async,&A::mythread,&a,tmpper);
后
main thread id = 10724
continue.....!
12
mythread() startthread id = 14468
mythread() endthread id = 14468
5
主线程执行完毕!
可以看见,子线程被创建,调试时可以发现,在std::future<int> result = std::async(std::launch::async,&A::mythread,&a,tmpper);
执行的时候就会创建线程。
async()函数默认用的就是std::launch::async。
std::packaged_task
packaged_task是一个类模板,模板参数是各种可调用对象;通过packaged_task 可以将各种可调用对象包装起来,方便将来作为线程入口函数将来调用。
#include<thread>
#include <iostream>
#include <windows.h>
#include<mutex>
#include<list>
#include<map>
#include<vector>
#include<mutex>
#include<future>
using namespace std;
int mythread(int mypar)//线程入口函数
{
cout << mypar << endl;
cout << "mythread() start" << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "thread id = " << std::this_thread::get_id() << endl;
return 5;
}
int main()
{
//A a;
//int tmpper = 12;
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);//我们把函数mythread通过packaged_task包装起来
std::thread t1(std::ref(mypt), 1);//线程直接开始执行,第二个参数作为线程入口函数的参数
t1.join();
std::future<int> result = mypt.get_future();//std::future对象里包含有线程入口函数的返回结果
cout << result.get() << endl;
cout << "主线程执行完毕!" << endl;
return 0;
}
主要是为了 std::future<int> result = mypt.get_future();
这一步。
由于packaged_task 可以包装各种可调用对象,那么也可以调用lambda表达式:
int main()
{
//A a;
//int tmpper = 12;
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "thread id = " << std::this_thread::get_id() << endl;
return 5;
});//我们把函数mythread通过packaged_task包装起来
std::thread t1(std::ref(mypt), 1);//线程直接开始执行,第二个参数作为线程入口函数的参数
t1.join();
std::future<int> result = mypt.get_future();//std::future对象里包含有线程入口函数的返回结果
cout << result.get() << endl;
cout << "主线程执行完毕!" << endl;
return 0;
}
运行结果和之前一样。
打包好的对象也是一个可调用对象,可以直接调用:
int main()
{
//A a;
//int tmpper = 12;
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "thread id = " << std::this_thread::get_id() << endl;
return 5;
});//我们把函数mythread通过packaged_task包装起来
mypt(105);//直接调用
std::future<int> result = mypt.get_future();//std::future对象里包含有线程入口函数的返回结果
cout << result.get() << endl;
cout << "主线程执行完毕!" << endl;
return 0;
}
执行结果:
main thread id = 3232
105
mythread() startthread id = 3232
mythread() endthread id = 3232
5
主线程执行完毕!
此时就相当于一个函数调用,在主线程中进行。
还可以用迭代器:
vector <std::packaged_task<int(int)>> mytasks;
int main()
{
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "thread id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "thread id = " << std::this_thread::get_id() << endl;
return 5;
});//我们把函数mythread通过packaged_task包装起来
mytasks.push_back(std::move(mypt));//这里用的是move放入容器,防止拷贝进容器
std::packaged_task<int(int)> mypt2;
auto iter = mytasks.begin();
mypt2 = std::move(*iter);
mytasks.erase(iter);
mypt2(105);
std::future<int> result = mypt2.get_future();//std::future对象里包含有线程入口函数的返回结果
cout << result.get() << endl;
cout << "主线程执行完毕!" << endl;
return 0;
}
效果一样。
std::promise
std::promise也是一个类模板,能够在某个线程中给它赋值,然后在其他线程中把值取出来用。
void mythread(std::promise<int>& tmpp, int calc)//注意第一个参数
{
//做一系列的复杂操作
calc++;
calc *= 10;
//....
std::chrono::milliseconds dura(5000);//休息5s
std::this_thread::sleep_for(dura);
int result = calc;
tmpp.set_value(result);//保存结果到tmpp这个对象中。
}
void mythread2(std::future<int>& tmpf)
{
auto result = tmpf.get();
cout << "mythread2 result = " << result << endl;
return;
}
int main()
{
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
std::promise<int> myprom;//申明一个promise对象,保存的值为int
std::thread t1(mythread, std::ref(myprom), 180);
t1.join();
//获取结果值
std::future<int> ful = myprom.get_future();
std::thread t2(mythread2, std::ref(ful));
t2.join();
cout << "主线程执行完毕!" << endl;
return 0;
}
运行结果:
main thread id = 6472
mythread2 result = 1810
主线程执行完毕!
可以看出实现了线程之间参数的传递。
小结
这么多线程函数,到底怎么用,什么时候用?
学习这些东西的目的并不是都要用在实际开发中,如果能够用最少的东西写出一个稳定、高效的多线程程序,更好。
学习过程中,必须学习别人写的好的代码,实现自己的积累,技术就会大幅度提升。学习这么多能够很好的读高手的代码。