设计模式
设计模式:代码的一些写法,这些写法与常规写法不同:程序灵活,维护起来可能方便,但是别人接管和阅读很痛苦。用设计模式写出的代码很晦涩
单例设计模式
整个项目中,有某个或者某个特殊的类,属于该类的对象只能有一个,多了创建不了
#include<iostream>
using namespace std;
class A
{
private:
A(){} //私有化构造函数
static A *m_instance; // 静态成员函数
public:
static A *getInstance()
{
if(m_instance == nullptr)
{
m_instance = new A();
static CGarhuishou garhuishou;
}
return m_instance;
}
class CGarhuishou // 用来释放对象
{
public:
~CGarhuishou()
{
if(A::m_instance){
delete A::m_instance;
A::m_instance = NULL;
}
}
};
void func(){
cout << "test" << endl;
}
};
A *A::m_instance = NULL;
int main(int argc, char const *argv[])
{
/* code */
A *p_a = A::getInstance(); //创建类对象,返回该类的指针
A *p_b = A::getInstance(); // p_a与p_b相同
//A a1; //不能这样创建
return 0;
}
单例设计模式共享数据问题
面临的问题,需要在我们自己创建的线程(不是主线程)中来创建单例类的对象, 这种线程可能有多个。这种情况我们就面临getInstance()这种成员函数要互斥
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mut;
class A
{
private:
A() {} // 私有化构造函数
static A *m_instance; // 静态成员函数
public:
static A *getInstance()
{
if (m_instance == nullptr) //双重锁定
{
unique_lock<mutex> mymutex(mut); // 直接加锁,但是效率低;双重锁定改善
if (m_instance == nullptr)
{
m_instance = new A();
static CGarhuishou garhuishou;
}
}
return m_instance;
}
class CGarhuishou // 用来释放对象
{
public:
~CGarhuishou()
{
if (A::m_instance)
{
delete A::m_instance;
A::m_instance = NULL;
}
}
};
void func()
{
cout << "test" << endl;
}
};
void mythread()
{
cout << "mythread_start" << endl;
A *p_a = A::getInstance();
p_a->func();
cout << "mythread_end" << endl;
}
A *A::m_instance = NULL;
int main(int argc, char const *argv[])
{
thread myobj1(mythread);
thread myobj2(mythread);
myobj1.join();
myobj2.join();
return 0;
}
std::call_once():c++11引入的函数,该函数的第二个参数为一个函数名a(),作用是保证函数a()只能被调用一次;具备互斥量的能力,比互斥量更加高效;需要与一个标记结合使用,这个标记是std::once_flag
std::once_flag g_flag;
class A
{
private:
A() {} // 私有化构造函数
static A *m_instance; // 静态成员函数
static void createinstance(){
m_instance = new A();
static CGarhuishou garhuishou;
}
public:
static A *getInstance()
{
std::call_once(g_flag,createinstance);
return m_instance;
}
class CGarhuishou // 用来释放对象
{
public:
~CGarhuishou()
{
if (A::m_instance)
{
delete A::m_instance;
A::m_instance = NULL;
}
}
};
void func()
{
cout << "test" << endl;
}
};
条件变量
std::condition_variable my_cond
实际上是一个类,是一个和条件相关的类,说白了就是等待一个条件的达成,这个类需要与互斥量配合使用,使用前需要#include <condition_variable>
class A
{
public:
//收到的数据加入到消息队列的线程
void inmsgRecvQueue()
{
for(int i = 0; i<100000;i++)
{
std::unique_lock<mutex> sbguard(my_mutex, std::try_to_lock);
if(sbguard.owns_lock())
{
cout<<"inmsgRecvQueue()执行,插入一个数据:"<<i<<endl;
msgRecvQueue.push_back(i);
//如果outmsgRecvQueue()正在处理一个事物,需要一段时间,而不是卡在wait()
//等你唤醒那么此时这个notify_one()不能唤醒wait()线程
my_cond.notify_one(); // 尝试把wait()线程唤醒
}else{
cout<<"inmsgRecvQueue()执行,没有拿到锁"<<i<<endl;
}
}
}
//把数据从消息队列中取出的线程
void outmsgRecvQueue()
{
int command = 0;
while(true){
unique_lock<mutex> sbguard(my_mutex);
//wait()用来等一个东西,如果第二个参数lambda表达式返回值是false,那么wait将解锁互斥量,并阻塞在本行
//阻塞到其他某个线程调用notify_one()函数为止;如果返回值为true,那wait()直接返回
//如果wait()无第二个参数,那么wait将一直阻塞到其他某个线程调用notify_one()函数为止
my_cond.wait(sbguard,[this]{
if(!msgRecvQueue.empty())
{
return true;
}else{
return false;
}
});
// 只要能走到这里,这个互斥锁一定是加锁的状态
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
sbguard.unlock(); //unique_lock的灵活性,可以随时解锁,后面可以处理取到的Command
cout<<"outmsgRecvQueue()执行,取出一个数据:"<<command<<endl;
}
}
private:
list<int> msgRecvQueue;
mutex my_mutex; //创建一个互斥量
std::condition_variable my_cond;
};
notify_all()
:notify_one()只能通知一个线程,通知所用线程唤醒
std::async(),std::future
:创建后台任务并返回值。std::async()是一个函数模板,用来启动一个异步任务,启动完成异步任务之后,返回一个std::future对象,std::future是一个类模板,这个对象中含有线程入口函数所返回的结果,可以调用future对象的get()方法来获得。启动一个异步任务,就是自动创建一个线程并开始执行对应的线程入口函数。要使用必须#include <future>
#include <thread>
#include <iostream>
#include <future>
using namespace std;
int mythread(){
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout<<"mythread threadid = "<<this_thread::get_id()<<endl;
return 5;
}
int main(int argc, char const *argv[])
{
cout<<"main threadid = "<<this_thread::get_id()<<endl;
std::future<int> fut = std::async(mythread);
cout << "continue..."<<endl;
cout<<fut.get()<<endl; //卡在这里等待mythread()执行完成,只能调用一次,get()是一个移动语义
//fut.wait(); //等待线程返回,本身并不返回结果
cout<<"main end..."<<endl;
//不调用get(),或者wait(),依旧会在return 0;之前阻塞
return 0;
}
可以额外向std::async()传递参数,该类型是std::launch类型(枚举),来达到一些特殊的目的
a)std::launch::deferred
:延迟调用,没有创建新线程,是在主线程中调用线程入口函数,表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行;如果没有调用wait()或者get()函数,则线程不会创建并调用
b)std::launch::async
:再调用async函数的时候就开始创建线程
std::packaged_task
:打包任务,把任务包装起来。是个类模板,它的模板参数是各种可调用对象,通过std::packaged_task来吧各种可调用对象包装起来,方便将来作为线程入口函数调用;packaged_task包装起来的可调用对象还可以直接调用,所以,packaged_task对象也是一个可调用对象
int mythread(int tmp){
cout<<tmp<<endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout<<"mythread threadid = "<<this_thread::get_id()<<endl;
return 5;
}
cout << "main threadid = " << 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> fut = mypt.get_future(); //std::future对象中包含有线程入口函数的返回结果
cout << fut.get()<<endl;
cout <<"end"<<endl;
//也可以使用lambda表达式
std::packaged_task<int(int)> mypt([](int tmp){
cout<<tmp<<endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout<<"mythread threadid = "<<this_thread::get_id()<<endl;
return 5;
});
std::promise
:类模板,能够在某个线程中进行赋值,然后可以在其他线程中把这个值取出来使用
void mythread(std::promise<int> &prm,int calc){
// 做一系列复杂的操作
calc++;
calc*=10;
//做其他运算,花费了5s
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
int result = calc;
prm.set_value(result);
}
std::promise<int> myprm;
cout << "main threadid = " << this_thread::get_id() << endl;
std::thread t1(mythread, std::ref(myprm), 150);
t1.join(); //一定要使用join()
//获取结果
int result = myprm.get_future().get(); //promise和future绑定,用于获取线程返回值
cout << result << endl;
cout <<"end"<<endl;
std::future的其他成员函数
std::future_status
:枚举类型
int mythread(){
cout<<"mythread start threadid = "<<this_thread::get_id()<<endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout<<"mythread end threadid = "<<this_thread::get_id()<<endl;
return 5;
}
int main(int argc, char const *argv[])
{
cout<<"main threadid = "<<this_thread::get_id()<<endl;
std::future<int> fut = std::async(mythread);
cout << "continue..."<<endl;
std::future_status status = result.wait_for(std::chrono::seconds(1));
if(status == std::future_status::timeout){
// 超时:表示线程还没有执行完,我想等待你一秒,希望你返回;如果没有返回那么就是timeout
cout<<"status timeout"<<endl;
}
else if(status ==std::future_status::ready){
//表示线程成功返回,设置result.wait_for(std::chrono::seconds(6))
cout<<"status ready"<<endl;
cout<<result.get()<<endl;
}
else if(status ==std::future_status::deferred){
//设置std::future<int> fut = std::async(std::launch::deferred,mythread);成立
cout<<"status deferred"<<endl;
cout<<result.get()<<endl; //设置std::launch::deferred调用get()线程才会执行
}
cout<<"main end..."<<endl;
return 0;
}
std::shared_future
与future对象一样,也是一个类模板,future的get()函数是转移数据只能调用一次;shared_future的get()函数是复制数据,可以多次调用
std::future<int> fut = std::async(mythread);
std::shared_future<int> fut_s(std::move(fut));
std::shared_future<int> fut_s(fut.share()); //执行完毕后,fut_s有值,而fut空了
原子操作
不需要用到互斥量加锁的多线程并发编程方式,在多线程中不会被打断的程序执行片段,比互斥量效率更高;互斥量的加锁一般针对的是一个代码段,而原子操作一般针对的是一个变量;std::atomic
来代表原子操作,是一个类模板
std::atomic<int> g_mint = 0;
g_mint++; //正确
g_mint += 1; //正确
g_mint = g_mint +1; //不正确
std::async
async用来创建一个异步任务,std::launch::deferred
延迟调用,std::launch::async
强制创建一个线程。std::thread()
创建线程,如果系统资源紧张,那么可能创建线程就会失败,程序会报异常;std::async()
一般不叫创建线程(虽然它可以创建线程),我们一般叫他创建一个异步任务。两个最明显的不同是async有时候并不创建新线程并且async很容易拿到线程的返回值。std::async()
如果系统资源紧张导致无法创建新线程,那么就不会创建新线程,后续如果调用了get()方法,那么这个异步任务就运行在这个线程上。
一般而言,一个程序中的线程数不宜超过100-200
std::recursive_mutex
递归的独占式互斥量,允许同一个线程,同一个互斥量多次被lock(),比mutex效率差
std::timed_mutex和std::recursive_timed_mutex
带超时的独占互斥量和带超时功能的递归独占互斥量,try_lock_for()和try_lock_until()
两个接口,try_lock_for()是等待一段时间,如果拿到了锁或者等待超过时间没拿到锁,就继续往下走;try_lock_until()参数是一个未来的时间点,在这个未来的时间点未到,如果拿到了锁,那么就走下来,如果时间到了,没拿到锁,也走下来
线程池
把一堆线程弄到一起,统一管理。这种统一管理调度,循环利用线程的方式就叫线程池。在程序启动时,一次性创建好一定数量的线程。
评论区