MultiThread是自己在B站学习C++多线程,跟老师写的代码
b站up主mkuangxiang
https://www.bilibili.com/video/BV1Yb411L7ak
而这个文件是对代码再次阅读时的笔记,削去多余的东西,方便对知识点的检索
-
windows_critical_section
参考:windows_critical_section1.cpp
函数调用函数,函数里也有std::recursive_mutex.lock
可使用:
std::lock_guard<std::recursive_mutex> my_Recursive_Mutex_Guard(m_recursiveMutex);
testfunc1(); //会加三次锁
//而testfun1()中,连续调用两次的
void A::testfunc1()
{
std::lock_guard<std::recursive_mutex> myGuard(m_recursiveMutex);
cout << "testfunc1() is do something" << endl;
testfunc2();
}
void A::testfunc2()
{
std::lock_guard<std::recursive_mutex> myGuard(m_recursiveMutex);
cout << "testfunc2() is do something" << endl;
}
windows_critical_section2.cpp中提出请求互斥锁,按时间的去try_lock,超时走掉
std::timed_mutex:超时功能的独占互斥锁
try_lock_for()超时没拿到锁就自己走了,不会阻塞
try_lock_unti()
std::recursive_timed_mutex:超时功能的递归独占互斥量
使用如下
std::chrono::milliseconds timeout(100);
if (m_recursive_Timed_Mutex.try_lock_for(timeout))
{
m_msgRecvQueue.push_back(i);
m_recursive_Timed_Mutex.unlock();
}
有前文铺垫
std::recursive_timed_mutex m_recursive_Timed_Mutex;
设置超时的一种变种
std::chrono::steady_clock 为了表示稳定的时间间隔,后一次调用now()得到的时间总是比前一次的值大
(这句话的意思其实是,如果中途修改了系统时间,也不影响now()的结果),每次tick都保证过了稳定的时间间隔
铺垫知识:
steady_clock::time_point t1 = steady_clock::now();
设置超时时间的变种
if (m_recursive_Timed_Mutex.try_lock_until(chrono::steady_clock::now() + timeout)) //与上面一行等价,当前时间算起多加100ms
{
m_msgRecvQueue.push_back(i);
m_recursive_Timed_Mutex.unlock();
}
-
Thread_Vector
ThreadCommunicate2 演示共享数据的--以保护方式来访问
注意outMsgLULProc中unlock有两处,因为可能从两处路径返回
注意.empty()也是保护访问的方式
std::lock_guard<std::mutex>的方式会解决上面可能是多种路径返回的问题,不用担心提前return的时候没有解锁
Thread_std_lock.cpp中
std::lock(m_myMutex1, m_myMutex2);//等价于每个互斥量都.lock().这是部分的代码段
m_msgRecvQueue.push_back(i);
m_myMutex2.unlock();
m_myMutex1.unlock();
std::lock与std::lock_guard配合使用,注意std::lock_guard第二参数
std::lock(m_myMutex2, m_myMutex1); //等价于每个互斥量都.lock()
std:;lock_guard<std::mutex> stdGuard1(m_myMutex1, std::adopt_lock); //前一语句已经把.lock()调用了,这里所以std::adopt_lock
std::lock_guard<std::mutex> stdGuard2(m_myMutex2, std::adopt_lock); //注意是stdGuard2,易写成stdGuard1
Thread_unique_lock1.cpp中
能不能有种机制让outMsgLULProc()知道自己想拿的锁处在何种状态呢?占有锁的线程是在执行还是处于拿到锁没干活阻塞起的呢?
std::unique_lock<std::mutex> stdGuard2(m_myMutex1, std::try_to_lock);
if (stdGuard2.owns_lock()) //成功拿到了锁
或者这么使用
std::unique_lock<std::mutex> uniquelock(m_myMutex1, std::defer_lock);
bool getLock = uniquelock.try_lock();
if(getLock) //成功拿到了锁
-
thread_test
简单,没重复看的必要
-
Thread_async_future_packaged_task_promise
Thread_async_future_packaged_task_promise.cpp中
比较总结性的说明了下std::async,std::future的关系,以及std::future的特点
Thread_async_future_packaged_task_promise3.cpp
注意,把打包对象按std::ref()传入线程对象
像这样
std::packaged_task<int(int)> mypt([](int mypar) { //包装lambda表达式
cout << mypar << endl;
cout << "myThread is running " << "thradid is:" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura); //不用window.h的Sleep();
cout << "myThread is over " << "thradid is:" << std::this_thread::get_id() << endl;
return 5; }
);
std::thread t1(std::ref(mypt), 3);
t1.join();
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
思考下,之前的代码对打包对象是如何启动的?是否使用引用
Thread_async_future_packaged_task_promise4.cpp
mytask.push_back(std::move(mypt)); //使用移动语义,mypt的所有权才为空
如果mytask.emplace(mypt);是否可以不显式地使用std::move()语义?
注意这里有声明
vector<packaged_task<int(int)>> mytask;
以及销毁容器中的可调对象
temmypt = std::move(*iter);
mytask.erase(iter); //后续将不能再使用iter这个迭代器
//Thread_async_future_packaged_task_promise5.cpp
演示了std::future<T> std::promise<T>作为参数传入子线程的用法,比较复杂
可以理解成利用std::promise在两个线程之间通信的做法
-
Singleton pattern
Singleton pattern.cpp中
可以用模板自己实现个单例类,注意类里面嵌套了类型声明
class MyClass
{
public:
~MyClass() {};
static MyClass* GetInstance();
class Reclaim
{
public:
~Reclaim()
{
if (MyClass::m_instance) //注意嵌套类内写代码,变量前加限定域::,容易搞忘
{
delete MyClass::m_instance;
MyClass::m_instance = NULL;
}
}
};
private:
MyClass() {};
static MyClass* m_instance;
};
MyClass* MyClass::m_instance = NULL;
MyClass* MyClass::GetInstance()
{
//if(m_instance ==NULL)
if (!m_instance)
{
m_instance = new MyClass();
static Reclaim cl; //小技巧,声明一个静态成员,程序结束时它被销毁时,跟到调用Reclaim的析构函数
}
return m_instance;
}
自己实现的模板形式的singleton.h
#pragma once
template<typename T>
class SingLeton
{
public:
static T* get_instance()
{
static T* p = nullptr;
if (nullptr == p)
{
p = new T();
static ReClaim<T> reclaim(&p);
}
return p;
}
template<typename T>
class ReClaim
{
public:
ReClaim(T** _p) :p(_p) {}
~ReClaim() { delete *p; *p = nullptr; p = nullptr; }
private:
T** p;
};
private:
SingLeton();
};
Singleton pattern1.cpp对上面创建唯一的实例GetInstance()函数里做了创建的保护工作
作者做了个小技巧,提高互斥访问效率的方法,首先会先在外层判断,如果已经创建了唯一的实例(创建唯一实例只能有一遍的机会,而判断后放弃创建是比较常见的情况)
就不会继续下一步去拿锁std::unique_lock<std::mutex> myMutex(resourse_mutex);
代码段:
if (!m_instance) //对上面代码逻辑上小改进,提高效率.双重检查
{ //注意!在不加锁,即没下一行加锁的情况下,m_instance == NULL;并不能说明MyClass没被new过;因为多线程,且这部分(即这行周边的代码)的操作不是原子操作
std::unique_lock<std::mutex> myMutex(resourse_mutex);
if (!m_instance) //根据上面的表述,所以才会把判断放在互斥保护这里来
{
m_instance = new MyClass();
static Reclaim cl; //小技巧,声明一个静态成员,程序结束时它被销毁时,跟到调用Reclaim的析构函数
}
}
Singleton pattern2.cpp
引入std::call_once()
#include <mutex>
std::once_flag g_flag;
std::call_once(g_flag, CreateInstance); //两个线程同时执行到此时,自动作原子操作,上锁,其他没拿到锁的在此等待,等待CreateInstance()执行完毕,其他线程随后没法执行CreateInstance()
Singleton pattern3.cpp
注意:要在for()循环内使用std::unique_lock<std::mutex>
for (int i = 0; i < 10000; i++)
{
cout << "inMsgRecvQueue() is running,Insert " << i << endl;
std::unique_lock<std::mutex> uniquelock(m_myMutex1);
std::chrono::milliseconds dura(1000); //即将睡1s
std::this_thread::sleep_for(dura);
m_msgRecvQueue.push_back(i);
}
Singleton pattern4.cpp引入std::condition_variable
class A
{
in_message();
out_message();
private:
std::list<int> my_list;
std::mutex my_mutex;
std::condition_variable my_cond;
};
void A::in_message()
{
std::unique_lock<std::mutex> my_lock(my_mutex);
my_list.emplace_back();
my_cond.notify_one();
}
void A::out_message()
{
std::unique_lock<std::mutex> my_lock(my_mutex);
my_cond.wait(my_lock, [this](){if(my_list.empty()) return false; return true;}) //第二参数防虚假唤醒
my_list.pop_front();
my_lock.unlock(); //可以提前unlock(),非必须的操作,主要是为之后的操作考虑,之后的操作不涉及共享数据则可以提前unlock()
}
从别人博客处扩展
std::condition_variable_any与 std::condition_variable 类似,只不过 std::condition_variable_any 的 wait 函数可以接受任何 lockable 参数
而 std::condition_variable 只能接受 std::unique_lock<std::mutex> 类型的参数
除此以外,和 std::condition_variable 几乎完全一样。
condition_variable参考博客https://www.cnblogs.com/haippy/p/3252041.html
-
Shared_Ptr_Test
这里有两个shared_ptr和unique_ptr的小示例
shared_ptr里有解决返回自身共享指针造成引用计数还是为1的办法
两种办法:
- 类修饰成enable_shared_from_this<Person>
调用return shared_from_this();
- 类修饰成enable_shared_from_this<Person>
- 封装个shared_ptr<>返回
return shared_ptr<Person>(this);
- 封装个shared_ptr<>返回
注意移动语义的典型例子
Shared_Ptr.cpp中
为类型增加个成员函数,并在合适的情境下调用它
struct Person :enable_shared_from_this<Person>
//struct Person //二选一,只能开启一个 `
{
void show() {
cout << str << "\n";
}
shared_ptr<Person> getShared() {
//return shared_ptr<Person>(this); //配合到上面结构体的声明
return shared_from_this();
}
string str;
};
调用:
shared_ptr<Person> ptr1;
{
shared_ptr<Person> ptr2(new Person);
ptr1 = ptr2->getShared(); //两个智能指针的引用计数增加
ptr1 = std::move(ptr2); //移动语义将原对象也转移ptr1引用计数为1,ptr2的引用计数为0
}
Shared_Ptr_Test.cpp中
注意:
之前一直记得unique_ptr是没有make_shared<>()方法的,但它有配套的make_unique<>()
//确实可以
std::unique_ptr<int> np_int;
np_int = std::make_unique<int>(10);
这个回顾下CPP_Primer中创建unique_ptr
auto node = make_unique<Node<T>>();
注意,这里有std::move()更典型的用法,它根本就不能用operator=(),引用计数只能0或1
而且,cpp文件实现了一个头插法的链表,简洁易懂
前插缺乏:
1.声明都不插的情况下
Link成员Node<T> head; //初始化缺乏
2.前插需要检查Node<T> head成员是否为nullptr(也即有上面一行的铺垫),若head为空,则新插进的节点不必指向下一节点
因为除了头节点(它不保存数据,只保存指向第一个实节点的指针),和才插入的实节点,没有其他节点了(实节点后面没有节点了)
-
shared_future_atomic
async的第一参数决定是否以多线程,异步的方式启动新线程
enum class launch { // names for launch options passed to async
async = 0x1,
deferred = 0x2
};
上面是future原文
记到,std::async(std::launch::async, )是为了创建std::future<T>对象
std::future<int> result = std::async(std::launch::deferred,myThread); //这里不会阻塞,异步线程即使不调用join(),在主函数返回前也会等待它执行完std::future<>的wait_for()方法
get()方法启动线程,并等待线程执行完
//shared_future_atomic.cpp中使用
std::future_status status = result.wait_for(std::chrono::seconds(6)); //会等待线程1秒钟,并检查线程状态
if (std::future_status::timeout == status) //它有三种状态:ready;timeout;deferred;
{
cout << "经过result.wait_for()状态检查得出myThread()超时" << endl;
}
else if(std::future_status::ready == status)
{
cout << "经过result.wait_for()状态检查得出myThread()执行完毕" << endl;
}
else if (std::future_status::deferred == status) //async的第一个参数被置为std::launch::deferred,则本条件成立
{ //注意:如果延迟执行,async中创建的线程myThread不会主动执行,还必须result.get(),注意它不开启子线程,而在主线程中执行
cout << "线程被延迟了,但现在还未执行,下面调用result.get()" << endl;
result.get();
}
注意,这个方法很智能,能检查异步运行的子线程的状态,比单单的std::thread()启动一个线程要更高级
稍微复杂点,对上面的辨析,std::thread启动的线程也能检测运行状态,即获取std::future<T>,但前提条件是启动的是包装对象
将函数封装成包装对象
//shared_future_atomic1.cpp中
注意,线程对象std::thread能用join(),能100%使线程运行并阻塞(线程若未执行完,就block,直到运行完)
而std::future<T>对象的get()方法,如果,线程是由std::async()启动,则get()实际会起到启动线程,并等待子线程结束并接受子线程返回值的作用
std::future<T>对象的方法
扩展下,摘录别人的博客
一个有效(valid)的 std::future 对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,其实我们前面都已经提到了,他们分别是:
- std::async 函数,本文后面会介绍 std::async() 函数。
- std::promise::get_future,get_future 为 promise 类的成员函数,详见 C++11 并发指南四(<future> 详解一 std::promise 介绍)。
- std::packaged_task::get_future,此时 get_future为 packaged_task 的成员函数,详见C++11 并发指南四(<future> 详解二 std::packaged_task 介绍)。
一个 std::future 对象只有在有效(valid)的情况下才有用(useful),由 std::future 默认构造函数创建的 future 对象不是有效的(除非当前非有效的 future 对象被 move 赋值另一个有效的 future 对象)。
在一个有效的 future对象上调用get()会阻塞当前的调用者,直到Provider设置了共享状态的值或异常(此时共享状态的标志变为 ready),std::future::get将返回异步任务的值或异常(如果发生了异常)。
铺垫知识:
std::future::valid()
检查当前的 std::future 对象是否有效,即释放与某个共享状态相关联。一个有效的 std::future 对象只能通过 std::async(), std::future::get_future 或者 std::packaged_task::get_future 来初始化。
继续扩展,关键:
std::future::share()
返回一个 std::shared_future 对象(本文后续内容将介绍 std::shared_future ),调用该函数之后,该 std::future 对象本身已经不和任何共享状态相关联,因此该 std::future 的状态不再是 valid 的了。
所以新接收的std::shared_future<T>可以多次get(),像下面这样
std::shared_future<int> result_s(result.share()); //因为这里将std::future<int>转译成std::shared_future<int>类型所以给下面的std::thread t2(myThread1,)第二参数应是result_s,不要忘了函数定义中参数类型也需要改造
auto threadResult = result_s.get();
auto threadResult1 = result_s.get();
下面跟到了解std::shared_future<T>对象
std::shared_future 与 std::future 类似,但是 std::shared_future 可以拷贝、多个 std::shared_future 可以共享某个共享状态的最终结果(即共享状态的某个值或者异常)。
shared_future 可以通过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者通过 std::future::share() 显式转换,无论哪种转换,被转换的那个 std::future 对象都会变为 not-valid.
现在回过头来讨论,std::thread对象可以用join()来控制子线程"必须"执行
std::future::get()方法是针对std::future<T>对象的
小结:
std::thread()在系统资源紧张时,创建线程可能会失败,这行语句可能会导致程序崩溃
std::async()异步任务与std::thread()最大不同是std::async()有时不创建线程
为std::async()传入第一参数:std::launch::deferred延时执行,那么该async其返回值std::future<>类型对象必须调用.wait()或.get()方法
传std::launch::deferred参数可能就是主线程执行那种(不会创建新线程)
纠正:如果不向其传参,则其默认值是std::launch::async | std::launch::deferred即可能创建新线程以异步方式运行,也可能以主线程中以函数调用的方式运行
std::thread创建的线程如果有返回值,拿到这个返回值不容易;但std::async相比这点有优势
系统资源紧张的话不加额外参数的std::async不会创建新线程
一个程序线程数量不宜超过100-200
这部分的知识平时用的比较少,这篇博客介绍的比较好
https://www.cnblogs.com/haippy/p/3280643.html
-
phonywake_atomic
防虚假唤醒,简单
-
packaged_task
packaged_task.cpp
std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象
打包对象,实际生成可调对象,核心操作:
packaged_task<int()> task( [] {return 123; } ); //<>内是类型,task()小括号内是匿名函数
future<int> result = task.get_future(); //打包任务对象中取得std::future<int>对象
task(); //直接运行可调对象
cout << result.get() << '\n'; //从std::future<int>对象获取返回值
这里用到了比较偏的知识点,同时detach()和std::future<T>::get()方法:
packaged_task<int(array<int, 5>::const_iterator, array<int, 5>::const_iterator)> task(acc); //把lambda这个可调对象再生成一个可调对象
auto result = task.get_future();
thread t(move(task), numbers.cbegin(), numbers.cend());
t.detach();
cout << result.get() << '\n';
从shared_future_atomic了解了够多的std::future<T>用法
std::future<T>对象基本都可够用了,能从异步子线程中通信,获得子线程执行后的返回值
现在进一步了解std::promise<T>
#include <future>
和std::packaged_task一样使用get_future()来返回一个std::future<T>
两者不同的是std::packaged_task<T>是可调对象,而std::promise<T>似乎并不是,它更像一种比较特殊的参数,特殊在共享(不同的线程可访问),以及异步(可以在其它时刻去获取结果)
promise.cpp中
注意std::future<T>::wait()方法阻塞在std::promise<T>来set_value()
注意向lambda函数传入的std::promise<T>使用std::move语义
array<int, 5> numbers{ 5,6,7,8,9 };
promise<int> accResult; //先声明一种数据类型,下面备用,存储一个值,异步获取
auto acc = [](const array<int, 5>& numbers, promise<int> result) //匿名函数,小括号内是参数类型
{
int sum = accumulate(numbers.cbegin(), numbers.cend(), 100);
result.set_value(sum);
};
auto result = accResult.get_future(); //返回future对象
//引用传递时第一个参数不宜直接numbers,宜用ref();
thread(acc, ref(numbers), move(accResult)).detach(); //一来就detach();小括号内(匿名函数,参数引用,第二个参数)
result.wait(); //等待阻塞结果可用
cout << "result = " << result.get() << '\n'; //拿到结果值
这个用法比较复杂
-
C++_Async
Async_Array.cpp中
#include <future>
auto res = async(&accumulate<Itetator, int>, numbers.cbegin(), numbers.cend(), 0);
//函数作用:求和.参数(,迭代器开始,迭代器结束,求和基数).偏通化模板函数
//async()做了三件事:
//1.创建子线程
//2.子线程向主线程传参 ?
//3.协调子线程与主线程同步关系
//而get()才是将子线程启动起来
cout << res.get() << '\n';
//亦可使用类型推断
auto res = async(&accumulate<decltype(std::cbegin(a)), int>, std::cbegin(a), std::cend(a), 20);
cout << res.get() << '\n';
//async生成的对象原型
future<void> ret = async( [] { cout << "This Thread Run In:" << this_thread::get_id() <<"Use Windows Api:"<< ::GetCurrentThreadId() <<" "<< endl; });
上一部分future应该是future<int> res
-
学习线程间同步,老余的四件套
#include <thread>
#include <mutex>
#include <condition_variable>
class A
{
private:
void fun();
void fun1();
std::thread t;
std::thread t1;
std::mutex mutex;
std::atomic_bool flag_ready;
std::condition_variable cnd_var;
};
A::fun()
{
{
std::unique_lock<std::mutex> unlock(mutex);
condition_variable .wait(unlock, [this](){ return flag;});
flag_ready = false;
}
}
A::fun1()
{
{
std::lock_guard<std::mutex> lock(mutex);
flag_ready = true;
}
condition_variable .notify_one()
}