C++高级编程-高级特性

《C++高级编程(第四版)》
临时总结:
<utility> std::move 获得右值引用
奇形怪状的函数

1.传统函数 void f(int a){}

2.<functional> bind
bind( F&& f, Args&&... args );
参数
f	-	可调用 (Callable) 对象(函数对象、指向函数指针、到函数引用、指向成员函数指针或指向数据成员指针)
args	-	要绑定的参数列表,未绑定参数会被命名空间 std::placeholders 的占位符 _1, _2, _3... 替换
返回值
某个未指定类型 T 的函数对象 g
void func(int  num, sring_view str);
string mySting = "haha";
auto f1 = bind(func, placehoders::_1, myString);
f1(16); //func(16, "haha");

3.<functional> std::function<R(ArgTypes..)> R--函数值返回类型,ArgTpes--参数列表 CP18.2
4.<functinal> invoke<fun,"a">  调用fun函数,lambda表达式等,赋予值a	
5.函数对象-包含operator()的类
class f
{
public:
    bool operator()(char c) const	{ return isdigit(c)!= 0;}
}
6.lambda表达式
7.函数模板,函数指针,函数指针作为参数

C++高级特性

第21章 自定义和扩展标准库–待回头

不想把玩STL,跳过此章

21.1 分配器–Allocator

大部分情况默认即可。
也可以自定义Allocator类型的对象,来自定义容器分配内存的方式
容器每次内存分配都是通过Allocator对象的allocate()方法进行的,每次释放都是通过deallocate()方法。

自定义的情形:

  1. 底层分配器的性能无法接受,但可构建替换的分配器
  2. 内存碎片问题严重。大量不同的分配和释放操作导致内存中出现很多不可用的片段。可创建某种类型的“对象池”,称为内存池。
  3. 必须给操作系统特定的功能分给空间,如共享内存段。可自定义分配器允许在共享内存段使用标准容器

C++17引入多态内存分配。不同分配器的容器便已经不同了,因此不能互相赋予。
cppreference-allocator示例代码

std::pmr名称空间的< memory_resource> 中定义了多态内存分配器。

自定义分配器-需要时深入

21.2 流适配器

21.3 迭代器适配器

21.4 扩展标准库

hash_map实现

第二十二章 高级模板-待补充

22.1 深入了解模板参数

22.1.2 template template 参数

C++17怎么判断类型T是std::vector?

#include <iostream>
#include <vector>
template <typename, template<typename...> class>
struct is_ispecialization: std:: false_type{};

template <template<typename...> class Template, typename... Args>
struct is_ispecialization<Template<Args...>, Template>: std:: true_type{};

int main()
{
    constexpr bool is_vec = is_ispecialization<std::vector<int>, std::vector>::value;
    printf("%d\n", is_vec);
    system("pause");
    return 0;
}

P523
如果想要接收模板作为模板参数,就用到template template参数。
有点像在普通函数中指定函数指针参数,函数指针的类型包括函数的返回类型和参数类型,同样,template template参数的完整规范包括该模板的参数,如:

/*
E 元素类型
*/
template <typename E, typename Allocator = std::allocator<E>>
class vector
{
	//Vector definition
};

在P523例子中作为另一个模板的参数时(这里按照需要用Container替代类型vector)

template <typename T,
	template <typename E, typename Allocator = std::allocator<E>> class Container
	= std::vector>
class Grid
{
	/* ...*/
};

template template参数更通用的语法规则是:

template<..., template<TemplateTypeParams> class ParameterName, ...>
/*
C++17中,typename也可以替代此处的class
*/

模板类部分特例化

通过重载模拟函数部分特例化

模板递归

可变参数模板

22.6 模板元编程

模板元编程目标是在编译时而不是运行时执行一些计算

borehole打洞哥-C++模板元编程详细教程(之一)

第二十三章 C++多线程编程

posix是什么都不知道,还好意思说你懂Linux?

**尽量编写平台独立的多线程程序,如pthreads库和boost::thread库。**本章讲解标准的线程库

23.1 多线程概述

多线程并行计算。本书完成时,C++标准仅针对CPU,不适用GPU。
多线程难点是将算法并行化,这个过程和算法的类型高度相关。其他困难是防止争用条件,死锁,撕裂和为共享等,使用原子或显式的同步机制解决。
避免多线程问题,设计程序,多个线程不需要读写共享的内存位置,使用原子操作,或互斥的同步方法

23.1.1 争用条件

共享内存上下文的争用条件——数据争用。
多个线程共享内容,且一个线程正在写入共享的内存,就会发生数据争用。

23.1.2 撕裂 tearing

数据争用的特例或结果:撕裂读和撕裂写。
线程一未完成写入,线程二看到的将一要写入的不一致,发生撕裂读。
同时写入,结果将和各自想要写入的不一致,发生撕裂写。

23.1.3死锁

操作系统部分的内容
如果需要得到由多个互斥对象保护的多个资源的权限,而非单独获取每个资源的权限,推荐使用std::lock()或std::try_lock()函数,通过一次调用获得或尝试获得多个资源的权限。

23.1.4 伪共享

大多数缓存都使用缓存行cache line,现代CPU通常是64字节。
多线程共享一个缓存行,其中一个使用时,会锁定整行。
可使用显式的内存对齐(memory alignment)方式优化数据结构,确保不共享任何缓存行。C++17引入了hardware_destructive_interference_size常量,定义在< new>中,为避免共享缓存行,返回两个并发访问的对象之间的建议偏移量,可将这个值与alignas关键字结合使用,以合理地数据对齐。

23.2 线程

MingGW 各版本区别
GCC for Windows带有两个线程模型:win32和posix。只有posix线程模型支持标准的多线程库。 要检查这一点,请尝试运行g++ -v。如果您没有看到–enable-thread=posix,则表示您的编译器套件不支持标准多线程库。—编译问题

cppreference-thread

类 thread 表示**单个执行线程**。线程允许多个函数同时执行。
在标头 <thread> 定义
class thread; (C++11 起)
类 thread 表示单个执行线程。线程允许多个函数同时执行。

线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数参数的顶层函数开始。顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用 std::terminate 。顶层函数可以通过 std::promise 或通过修改共享变量(可能需要同步,见 std::mutex 与 std::atomic )将其返回值或异常传递给调用方。

std::thread 对象也可能处于不表示任何线程的状态(默认构造、被移动、 detach 或 join 后),并且执行线程可能与任何 thread 对象无关( detach 后)。

没有两个 std::thread 对象会表示同一执行线程; std::thread 不是可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的,尽管它可移动构造 (MoveConstructible) 且可移动赋值 (MoveAssignable) 。

创建线程

thread() noexcept;(1)	(C++11)
thread( thread&& other ) noexcept;(2)	(C++11)
template< class Function, class... Args >explicit thread( Function&& f, Args&&... args );(3)	(C++11)

1) 构造不表示线程的新 std::thread 对象。
2) 移动构造函数。构造表示 other 曾表示的执行线程的 std::thread 对象。此调用后 other 不再表示执行线程。
3) 构造新的 std::thread 对象并将它与执行线程关联。新的执行线程开始执行:

后条件
1) get_id() 等于 std::thread::id()(即 joinable 是 false2) other.get_id() 等于 std::thread::id()get_id() 返回构造开始前 other.get_id() 的值
3) get_id() 不等于 std::thread::id()(即 joinable 是 true)
如果需要传递引用参数给线程函数,那么必须包装它(例如用 std::ref 或 std::cref)。
忽略来自函数的任何返回值。如果函数抛出异常,那么就会调用 std::terminate。需要将返回值或异常传递回调用方线程时可以使用 std::promise 或 std::async。

bool joinable() const noexcept;
若 thread 对象标识活跃的执行线程则为 true ,否则为 false 。

thread::id
在标头 <thread> 定义
class thread::id;(C++11)
类 thread::id 是轻量的可平凡复制类,它的作用是 std::thread 及 std::jthread (C++20)对象的唯一标识符。
此类的实例亦可保有不表示任何线程的特殊的有区别值。一旦线程结束,则 std::thread::id 的值可为另一线程复用。
此类为用作包括有序和无序的关联容器的关键而设计。
默认构造新的线程标识符。标识符不代表线程。```

管理当前线程的函数在命名空间 this_thread 定义
在这里插入图片描述

sleep_for()

在标头 <thread> 定义
template< class Rep, class Period >
void sleep_for( const std::chrono::duration<Rep, Period>& sleep_duration );(C++11)
阻塞当前线程执行,至少经过指定的 sleep_duration 。
此函数可能阻塞长于 sleep_duration ,因为调度或资源争议延迟。

可能的使用情况:
让线程按照一定的频率执行某些任务,如每隔一秒做些啥。
等待外部某个时间发生,如用户输入或网络资源
让线程释放一些CPU资源,让其他线程有机会记性。

get_id()

std::thread::id get_id() const noexcept;(C++11)
返回标识与 *this 关联的线程的 std::thread::id 。
参数(无)
返回值
标识与 *this 关联的线程的 std::thread::id 类型值。若无关联的线程,则返回默认构造的 std::thread::id 。
例如:
std::thread t1(foo);
std::thread::id t1_id = t1.get_id();
std::cout << "t1's id: " << t1_id << '\n';
//cppreference-thread示例代码
#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
 
void f1(int n) //值
{
    for (int i = 0; i < 5; ++i)
    {
        std::cout << "正在执行线程1\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
void f2(int& n)//引用
{
    for (int i = 0; i < 5; ++i)
    {
        std::cout << "正在执行线程2\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
class foo//类
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i)
        {
            std::cout << "正在执行线程3\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
 
class baz//函数对象
{
public:
    void operator()() //为让bz类成为函数对象,需要实现此函数。
    {
        for (int i = 0; i < 5; ++i)
        {
            std::cout << "正在执行线程4\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
 
int main()
{
    int n = 0;
    foo f;
    baz b; //创建baz类的一个实例子
    std::thread t1; // t1 不是线程
    std::thread t2(f1, n + 1); // 按值传递,通过函数指针---1
    std::thread t3(f2, std::ref(n)); // 按引用传递
    std::thread t4(std::move(t3)); // t4 现在运行 f2()。t3 不再是线程
    //创建了一个foo实例f,线程执行f中的bar成员函数,可在不同线程执行某个对象中的方法。--要确认是否线程安全
    std::thread t5(&foo::bar, &f); // t5 在对象 f 上运行 foo::bar(),通过对象的成员函数---4
    std::thread t6(b); // t6 在对象 b 的副本上运行 baz::operator(),以函数对象baz的实例b创建线程----2
    t2.join();
    t4.join();
    t5.join();
    t6.join();
    std::cout << "n 的最终值是 " << n << '\n';
    std::cout << "f.n (foo::n) 的最终值是 " << f.n << '\n';
    std::cout << "b.n (baz::n) 的最终值是 " << b.n << '\n';
}
/*
若函数对象需要参数初始化,则创建线程的方式除了baz b
还有最好是
thread t1{ Counter{1, 20} };使用了统一初始化语法。而不是全换成圆括号

线程函数的参数总是被复制到线程的某个内部存储中,cref,ref可以按引用传递参数---t3的创建方式

还可以通过lambda表达式创建----3
int id = 1;
thread t1([id] {...} );
*/

如果一个线程对象表示系统当前或过去的某个活动线程,则认为它是可结合的joinable,即使这个线程执行完毕,该线程对象也依然处于可结合状态。默认构造的线程对象是不可结合的。

销毁一个可结合的线程对象前,必须对其使用join()或detach()方法。将使线程不可结合
join()的调用是阻塞调用,会一直等到线程完成工作为止。

detach()调用会将线程对象与底层OS线程分立。此时,OS线程将独立运行。
从 thread 对象分离执行线程,允许执行独立地持续。一旦该线程退出,则释放任何分配的资源
调用 detach 后 *this 不再占有任何线程。

一个仍可结合的线程对象被销毁,析构函数会调用std::terminate(),会终止所有线程以及程序本身。

关于此段chatgpt的解释:
主线程创建了一对子线程。
std::thread创建一个线程之后,我们可以用joinable()方法检查该线程是否可以被假如到当前线程中。
如果可以,那么他就是可连接的(joinable)。一个可连接的线程意味着该线程正在运行或者已经完成但尚未被假如到当前线程中。
我们可以使用jon()方法来等待该线程完成并将其假如到当前线程中,如果一个线程尚未完成我们就试图调用join()方法的话,就会抛出一个std::system_error异常。
我们应当使用join()方法将线程假如到当前线程中,以确保线程已经完成并可以被正确的处理。如果我们不像等待一个线程完成,就可以使用detach()方法将线程分立,这样使得该线程在后台继续运行,但我们不能再对它进行任何操作。

23.2.5 线程本地存储

通过关键字thread_local可将任何变量标记为线程本地数据即每个线程都有这个变量的独立副本,线程 存储期

thread_local 关键词只能搭配在命名空间作用域声明的对象在块作用域声明的对象及静态数据成员。它指示对象具有线程存储期。如果对块作用域变量只应用了 thread_local 这一个存储类说明符,那么同时也意味着应用了 static(不论函数在线程中调用多少次,每个线程仅初始化一次这个变量)。它能与 static 或 extern 结合,以分别指定内部或外部链接(但静态数据成员始终拥有外部链接)。

23.2.6 取消线程

C++17 没有包含一个线程中取消另一个已运行线程的任何机制。可以提供两个线程都支持的某种通信机制:如,共享变量,目标线程定期检查这个变量,判断是否终止。其他线程可设置这个空想变量,间接指示线程关闭。
C++20引入 < stop_token>jthread类
以下待修改

在 C++ 中,您可以使用以下方法来取消线程:	
使用共享变量: 您可以使用一个共享变量来控制线程是否应该继续运行。例如,您可以创建一个名为 stop_thread 的布尔变量,并在主线程中将其设置为 true,以指示线程应该停止运行。线程可以在运行时检查 stop_thread 变量,并在其为 true 时退出。	
使用条件变量: 您可以使用条件变量来通知线程应该停止运行。例如,您可以创建一个名为 stop_thread 的条件变量,并在主线程中调用 notify_all() 函数来通知所有等待线程停止运行。线程可以在运行时等待 stop_thread 条件变量,并在收到通知时退出。	
使用 std::thread::interrupt() 函数:这个函数可以中断正在运行的线程并抛出一个 std::thread_interrupted 异常。您可以在主线程中调用该函数来中断正在运行的线程,然后在线程中捕获 std::thread_interrupted 异常并退出线程。	
需要注意的是,取消线程可能会导致资源泄漏和其他问题,因此建议您在设计时考虑避免需要取消线程的情况。	
是的,我知道 stop_token 和 jthread。

stop_token 是 C++20 中引入的一种机制,用于向线程发送停止信号。它允许线程在处理完当前任务后优雅地停止,并避免了使用共享变量或条件变量可能导致的竞态条件和死锁问题。stop_token 可以与 std::stop_source 配合使用,后者可以用于从外部控制线程的停止。	
jthread 是 C++20 中引入的一种线程类型,它与 std::thread 类似,但支持 stop_token,可以更容易地实现优雅地停止线程。当使用 jthread 创建线程时,将同时创建一个 std::stop_source,并将其与 jthread 关联。您可以使用 jthread::stop_token() 函数获取与 jthread 关联的 stop_token,并在线程中使用它来检查是否应该停止线程。	
总的来说,stop_token 和 jthread 提供了一种更安全、更优雅的方式来停止线程,并且避免了使用共享变量或条件变量可能导致的问题。如果您使用的是 C++20 或更高版本的编译器,建议您考虑使用这些库。

如果您使用的是 C++17,可以使用 std::condition_variable 来实现线程间的同步和协调,以实现线程的优雅停止。	
具体来说,您可以创建一个名为 stop_thread 的布尔变量,以指示线程是否应该继续运行。然后,您可以创建一个 std::condition_variable 对象,并使用它来等待外部停止信号。在主线程中设置 stop_thread 为 true,并调用 notify_all() 函数来通知所有等待线程停止运行。线程可以在运行时检查 stop_thread 变量,并在其为 true 时退出。	
下面是一个使用 std::condition_variable 实现线程优雅停止的示例代码:	
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>

std::mutex m;
std::condition_variable cv;
bool stop_thread = false;

void worker_thread() {
    std::unique_lock<std::mutex> lock(m);
    while (!stop_thread) {
        // 等待外部停止信号
        cv.wait(lock);
        // 处理任务
        std::cout << "Worker thread is running" << std::endl;
    }
    std::cout << "Worker thread is stopped" << std::endl;
}
int main() {
    std::thread t(worker_thread);

    // 等待一段时间后停止线程
    std::this_thread::sleep_for(std::chrono::seconds(3));
    {
        std::lock_guard<std::mutex> lock(m);
        stop_thread = true;
    }
    cv.notify_all();
    t.join();
    return 0;
}
在该示例中,worker_thread 函数中的 while 循环会在等待 cv 条件变量时阻塞,直到收到外部停止信号才会退出循环。在主线程中,我们等待一段时间后设置 stop_thread 为 true,并调用 notify_all() 函数来通知等待线程停止运行。最后,我们等待线程完成并退出。

需要注意的是,这种方式仍然可能存在竞态条件和死锁问题,因此建议您在设计时考虑避免需要取消线程的情况,并尽可能使用更安全、更优雅的方式来停止线程,例如使用 C++20 中的 stop_token 和 jthread。
23.2.7 从线程获得结果

可以向线程传入指向结果变量的指针或引用,线程将结果保存在其中。
可以将结果存储在函数对象的类成员变量中,线程结束后可获得结果。只有std::ref()将函数对象按引用传递给thread构造函数,才能生效

可以使用future

23.2.8 复制和重新抛出异常

单线程下,每个线程自己捕获自己的异常。不然C++运行库将调用std::terminate(),从而终止整个引用程序。
STL通过与异常相关的函数处理线程间的异常。

std::exception_ptr
是一个类可空的用于处理异常对象的共享指针类型,管理已抛出并为std::current_exception 所捕捉的异常对象。
std::exception_ptr 的实例可传递给另一函数,可以在另一线程,在那里异常可能重抛并为 catch 子句所处理。
默认构造的 std::exception_ptr 是空指针;它不指向异常对象。
二个 std::exception_ptr 实例比较相等,仅若它们均为空或都指向同一异常对象。
std::exception_ptr 不可隐式转换为任何算术、枚举或指针类型。它可以按语境转换成 bool ,且若它为空则求值为 false ,否则为 true 。
一个 std::exception_ptr 所引用的异常对象只要为至少一个 std::exception_ptr 所引用就保持合法:
std::exception_ptr 是共享所有权的智能指针(注意:这附加于异常对象生存期规则)
std::exception_ptr 满足可空指针 (NullablePointer) 的要求。

#include <iostream>
#include <string>
#include <exception>
#include <stdexcept> 
void handle_eptr(std::exception_ptr eptr) // 按值传递 ok
{
    try {
        if (eptr) {
            std::rethrow_exception(eptr);
        }
    } catch(const std::exception& e) {
        std::cout << "Caught exception \"" << e.what() << "\"\n";
    }
} 
int main()
{
    std::exception_ptr eptr;
    try {
        std::string().at(1); // 生成一个 std::out_of_range
    } catch(...) {
        eptr = std::current_exception(); // 捕获
    }
    handle_eptr(eptr);
} // std::out_of_range 的析构函数调用于此,此时析构 ept
/*
输出:Caught exception "basic_string::at"
*/

<exception>库中
异常相关–笔记CP15

exception_ptr current_exception() noexcept

catch块中调用,返回一个exception_ptr对象。这个对象引用目前这个对象引用目前正在处理的异常或其副本,
没有异常,则返回空的exception_ptr对象。
并创建一个保有该异常对象复制或到该异常对象引用的 std::exception_ptr (依赖于实现)。被引用对象保持合法,只要至少要有一个 exception_ptr 对象引用它。
exception_ptr对象类型是bullablepointer,意味着整个变量很容易通过简单的if语句检查

[[noreturn]] void rethrow_exception( std::exception_ptr p );

重新抛出由exception_ptr参数引用的异常。[[noreturn]]表面这个函数绝不会正常地返回。CP11

template< class E >
std::exception_ptr make_exception_ptr( E e ) noexcept;
创建一个保有到 e 副本的引用的 std::exception_ptr 。
这如同以下列代码执行:
try {
    throw e;
} catch(...) {
    return std::current_exception();
}

不同线程间异常处理

#include <thread>
#include <iostream>
#include <exception>
#include <stdexcept>

using namespace std;

void doSomeWork()
{
	for (int i{ 0 }; i < 5; ++i) {
		cout << i << endl;
	}
	cout << "Thread throwing a runtime_error exception..." << endl;
	throw runtime_error{ "Exception from thread" };
}

void threadFunc(exception_ptr& err)
{
	try {
		doSomeWork();
	} catch (...) {
		cout << "Thread caught exception, returning exception..." << endl;
		err = current_exception();
	}
}

void doWorkInThread()
{
	exception_ptr error;
	// Launch thread.
	thread t{ threadFunc, ref(error) };
	// Wait for thread to finish.
	t.join();
	// See if thread has thrown any exception.
	if (error) {
		cout << "Main thread received exception, rethrowing it..." << endl;
		rethrow_exception(error);
	} else {
		cout << "Main thread did not receive any exception." << endl;
	}
}

int main()
{
	try {
		doWorkInThread();//主线程创建新线程t,调用异常,并扔出
	} catch (const exception& e) {
		cout << "Main function caught: '" << e.what() << "'" << endl;
	}

    system("pause");
    return 0;
}

自定义异常类,catch(…)捕获,what()输出

在 C++ 中,可以使用 catch(…) 来捕获任何类型的异常,而不仅仅是继承自 std::exception 的异常。然而,catch(…) 并不能直接访问异常对象的 what() 方法,因为它不知道异常对象的确切类型。

如果想要在 catch(…) 中输出异常的具体信息,可以使用 std::current_exception() 函数获取当前的异常对象,并将其存储在 std::exception_ptr 类型的变量中。然后可以使用 std::exception_ptr 的 std::rethrow_exception() 方法将异常重新抛出,并在新的 try-catch 块中捕获它。在新的 catch 块中,可以通过 std::exception_ptr 的 std::exception_ptr::rethrow_exception() 方法重新抛出异常,并将其捕获为 std::exception 的引用,以便访问其 what() 方法。

下面是一个示例代码:

try {
    // some code that may throw an exception
} catch (...) {
    std::exception_ptr p = std::current_exception();
    try {
        if (p) {
            std::rethrow_exception(p);
        }
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }
}

在上面的代码中,第一个 catch 块使用 std::current_exception() 获取当前的异常对象,并将其存储在 std::exception_ptr 类型的变量 p 中。然后,在内部的 try-catch 块中,使用 std::rethrow_exception() 方法重新抛出异常,并在新的 catch 块中捕获它。在新的 catch 块中,可以通过 const std::exception& 类型的引用 e 访问异常对象的 what() 方法,以输出异常的具体信息。

#include <iostream>
#include <exception>
using namespace std;

// 自定义一个异常类,继承自std::exception
class MyException : public exception
{
  public:
    // 重写what()方法,返回异常信息
    const char *what() const throw()
    {
        return "My exception happened";
    }
};

int main()
{
    try
    {
        // 抛出一个自定义异常对象
        throw MyException();
    }
    catch (...) // 捕获所有异常类型
    {
        try
        {
            throw;//再次抛出
        }
        //尝试捕获,是否是MyException
        catch (const MyException &e) // 捕获自定义异常类型,给了个名字e
        {
            // 输出异常信息
            cout << e.what() << endl;
        }
        //标准异常类型兜底
        catch (const exception &e) // 捕获标准库异常类型
        {
            // 输出异常信息
            cout << e.what() << endl;
        }
    }
    return 0;
}

23.3原子操作库<atomic>

操作系统
不可分割,不可打断。

为了线程安全,不显示使用任何同步机制。可使用std::atomic类型。

atomic<int> counter(0);//全局变量
++counter;//多线程中执行

可以直接使用这些类型别名,而不显式使用任何同步机制。但在底层,某些类型的原子操作可能仍在使用同步机制(如互斥对象)——当你的引荐缺少以原子方式执行操作的指令时。可在原子类型上使用is_lock_free()查询它是否支持无锁操作(运行时,底层没有显式同步机制.免锁则为 true ,否则为 false)
P556

在标头 <atomic> 定义
template< class T >struct atomic;(1)	
template< class U >struct atomic<U*>; //指针类型

主 std::atomic 模板可用任何满足可复制构造 (CopyConstructible)可复制赋值 (CopyAssignable)可平凡复制 (TriviallyCopyable) 类型 T 特化。如果下列任何值是 false,那么程序非良构:
std::is_trivially_copyable<T>::value
std::is_copy_constructible<T>::value
std::is_move_constructible<T>::value
std::is_copy_assignable<T>::value
std::is_move_assignable<T>::value

因此atomic<MyType> 也可以,只要MyType平凡可复制

以下代码编译可能出现undefined reference to `__atomic_is_lock_free’,是因为没有链接libatomic库,它提供一些原子操作的实现,当硬件或操作系统不支持锁无关指令时,需要此库。—编译问题
请使用g++ 28.cpp -latomic -o 28.exe 命令

#include <atomic>
#include <iostream>
#include <type_traits>
using namespace std;
class Foo { private: int m_array[123]; };
class Bar { private: int m_int; };

int main()
{
	atomic<Foo> f;
	cout << is_trivially_copyable_v<Foo> << " " << f.is_lock_free() << endl;

	atomic<Bar> b;
	cout << is_trivially_copyable_v<Bar><< " " << f.is_lock_free()<< endl;

    system("pause");
    return 0;
}
/*
输出
1 0
1 0
*/
在标头 <type_traits> 定义
template< class T >struct is_trivially_copyable;(C++11)

T	-	要检查的类型
辅助变量模板
template< class T >
inline constexpr bool is_trivially_copyable_v = is_trivially_copyable<T>::value;

如果 T 是可平凡复制类型,那么提供等于 true 的成员常量 value。对于任何其他类型 value 是 false。
如果 std::remove_all_extents_t 是(可有 cv 限定的)void 以外的不完整类型,那么行为未定义。
添加 is_trivially_copyableis_trivially_copyable_v (C++17 起) 的特化的程序行为未定义。

平凡的:用来描述没有用户定义的构造,析构,两个复制构造函数,两个赋值运算符以及虚函数的类(也不是虚基类)或结构体。
平凡的类或结构体可以用简单的内存操作进行复制和销毁。

struct P{
	int x;
	int y;
};//平凡的结构体,没有用户定义的特殊成员函数
class P
{
public:
//以下两个函数都是用户定义的。因此这个类不是一个平凡的类
	P();
	~P();
}
23.3.1 原子类型实例——为什么使用原子类型

以下代码,10个线程并行运行,改变共享的变量—存在数据争用

#include <iostream>
#include <vector>
#include <thread>
#include <functional>
#include <chrono>
using namespace std;
void increment(int& counter)
{
	for (int i{ 0 }; i < 100; ++i) {
		++counter;
		this_thread::sleep_for(1ms);
	}
}
int main()
{
	int counter{ 0 };
	vector<thread> threads;

	for (int i{ 0 }; i < 10; ++i) {
		threads.push_back(thread{ increment, ref(counter) });
	}
	for (auto& t : threads) {
		t.join();
	}
	cout << "Result = " << counter << endl;
}

上述代码对应

#include <atomic>
void increment(atomic<int> &counter)
atomic<int> counter(0)

同时,线程安全之后,引入了性能问题,所以要最小同步次数。每个线程自己计算完再同步到主线程。

void increament(atomic<int> & counter)
{
	int result = 0;
	for(int i = 0; i < 100; ++i) {
		++result;
		this_thread::sleep_for(1ms);
	}
	counter += result;
}
23.3.2 原子操作

<atomic>
compare_exchange_weak

//atomic类方法
bool std::atomic<T>::compare_exchange_weak( T& expected, T desired,
                            std::memory_order success,
                            std::memory_order failure ) noexcept;
原子地比较 *this 和 expected 的对象表示 (C++20)值表示 (C++20),
而若它们逐位相等,则以 desired 替换前者(进行读修改写操作)。
否则,将 *this 中的实际值加载进 expected (进行加载操作)。

类似以下操作:
if(*this == expected) {
	*this = desired;
	return true;
}else{
	expected = *this;
	return false;
}

参数
expected	-	到期待在原子对象中找到的值的引用。若比较失败则被存储 *this 的实际值。
desired	-	若符合期待则存储于原子对象的值
success	-	若比较成功,则读修改写操作所用的内存同步顺序。容许所有值。
failure	-	若比较失败,则加载操作所用的内存同步顺序。不能为 std::memory_order_release 或 std::memory_order_acq_rel ,且不能指定强于 success 的顺序 (C++17)
order	-	两个操作所用的内存同步顺序
返回值
若成功更改底层原子值则为 true ,否则为 false 。

注解
比较和复制是逐位的(类似 std::memcmp 和 std::memcpy );不使用构造函数、赋值运算符或比较运算符。

比较和交换操作通常用作无锁数据结构的基本建筑块

//cppreference示例代码
#include <atomic>
template<typename T>
struct node
{
    T data;
    node* next;
    node(const T& data) : data(data), next(nullptr) {}
};
 
template<typename T>
class stack
{
    std::atomic<node<T>*> head;
 public:
    void push(const T& data)
    {
      node<T>* new_node = new node<T>(data);
 
      // 放 head 的当前值到 new_node->next 中
      new_node->next = head.load(std::memory_order_relaxed);
 
      // 现在令 new_node 为新的 head ,但若 head 不再是
      // 存储于 new_node->next 的值(某些其他线程必须在刚才插入结点)
      // 则放新的 head 到 new_node->next 中并再尝试
      while(!head.compare_exchange_weak(new_node->next, new_node,
                                        std::memory_order_release,
                                        std::memory_order_relaxed))
          ; // 循环体为空
 
// 注意:上述使用至少在这些版本不是线程安全的
// 先于 4.8.3 的 GCC (漏洞 60272 ),先于 2014-05-05 的 clang (漏洞 18899 )
// 先于 2014-03-17 的 MSVC (漏洞 819819 )。下面是变通方法:
//      node<T>* old_head = head.load(std::memory_order_relaxed);
//      do {
//          new_node->next = old_head;
//       } while(!head.compare_exchange_weak(old_head, new_node,
//                                           std::memory_order_release,
//                                           std::memory_order_relaxed));
    }
};
int main()
{
    stack<int> s;
    s.push(1);
    s.push(2);
    s.push(3);
}

fetch_add()

T std::atomic<T>::fetch_add( T arg,
             std::memory_order order = std::memory_order_seq_cst )

arg	-	算术加法的另一参数
order	-	强制的内存顺序制约
#include <iostream>
#include <thread>
#include <atomic>
 
std::atomic<long long> data;
void do_work()
{
    data.fetch_add(1, std::memory_order_relaxed);
}
 
int main()
{
    std::thread th1(do_work);
    std::thread th2(do_work);
    std::thread th3(do_work);
    std::thread th4(do_work);
    std::thread th5(do_work);
 
    th1.join();
    th2.join();
    th3.join();
    th4.join();
    th5.join();
 
    std::cout << "Result:" << data << '\n';
}

/*
Result:5
*/

std::memory_order

23.3 互斥<mutex> mutual exclusion

互斥算法避免多个线程同时访问共享资源。这会避免数据竞争,并提供线程间的同步支持。

布尔值和整数等标量经常使用院子操作来实现同步。但数据更复杂且必须在多个线程中使用这些数据时,就必须提供显式的同步机制

STL支持互斥的形式包括互斥体mutex类和锁

23.4.1 互斥体类
class mutex;(C++11)
mutex 类是能用于保护共享数据免受从多个线程同时访问的同步原语。

mutex 提供排他性非递归所有权语义:
1.调用方线程从它成功调用 lock 或 try_lock 开始,到它调用 unlock 为止占有 mutex 。
	在此期间,该线程可以随机使用共享的内存。

2.线程占有 mutex 时,所有其他线程若试图要求 mutex 的所有权,
	则将阻塞(对于 lock 的调用)或收到 false 返回值(对于 try_lock ).
3.调用方线程在调用 lock 或 try_lock 前必须不占有 mutex 。否则死锁。

若 mutex 在仍为任何线程所占有时即被销毁,或在占有 mutex 时线程终止,则行为未定义。
 mutex 类满足互斥体 (Mutex) 和标准布局类型 (StandardLayoutType) 的全部要求。

线程将锁释放,多个线程再等待锁,没有机制能保证哪个线程优先获得锁。

std::mutex 既不可复制亦不可移动。
注意
通常不直接使用 std::mutex : 
std::unique_lock 、 std::lock_guard 或 std::scoped_lock (C++17)以更加异常安全的方式管理锁定。
非定时的互斥体类

<mutex> :mutex,recursive_mutex
<shared_mutex>:shared_mutex(C++17)
均支持各自的类方法
lock()线程尝试获取锁,并阻塞直到获取锁。可能会无限期阻塞。如果希望设置阻塞最大时间,请使用定时的互斥体类。
通常不直接调用 lock() :用 std::unique_lock 与 std::lock_guard 管理排他性锁定。

try_lock:尝试获取锁,如果当前锁被其他线程持有,则调用立即返回。成功获取,返回true,否则false

unlock:释放线程持有的锁,使得其他线程可以获取。
通常不直接调用 unlock() :用 std::unique_lock 与 std::lock_guard 管理互斥锁定。

mutex

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_var = 0;

void add_to_shared(int n) {
    mtx.lock();
    shared_var += n;
    mtx.unlock();
}

int main() {
    std::thread t1(add_to_shared, 5);
    std::thread t2(add_to_shared, 10);
    t1.join();
    t2.join();
    std::cout << "The value of shared_var is: " << shared_var << std::endl;
    return 0;
}

recursive_mutex
几乎和mutex一致。区别在于:
它允许一个线程多次锁定和解锁共享资源,即使它已经持有锁。这与常规的mutex不同,后者会阻塞线程,如果它尝试锁定它已经持有的锁。
递归锁在需要单个线程以嵌套方式多次访问共享资源的情况下非常有用,例如,如果一个函数递归调用自身并需要每次访问相同的资源。如果没有递归锁,线程将遇到死锁,即它会被阻塞,等待它已经持有的锁。
在C++中使用recursive_mutex,通常需要创建std::recursive_mutex类的实例。然后可以使用lock()方法获取互斥锁,并使用unlock()方法释放它**。如果同一线程多次调用lock(),则互斥锁仍将保持,直到进行相同数量的unlock()调用。**

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex my_mutex;

void foo(int n) {
    my_mutex.lock();
    std::cout << "Thread " << n << " locked the mutex" << std::endl;
    if (n < 3) {
        foo(n + 1); // Call recursively
    }
    my_mutex.unlock();
    std::cout << "Thread " << n << " unlocked the mutex" << std::endl;
}

int main() {
    std::thread t1(foo, 1);
    std::thread t2(foo, 2);
    t1.join();
    t2.join();
    return 0;
}

shared_mutex ——readerwriters锁
支持”共享锁拥有权“概念。
线程可获取锁的独占所有权共享所有权
独占所有权:写锁。仅当没有其他线程拥有独占或共享所有权时才能获得。
没有人在写和读,你的写才不会造成不良影响。
共享所有权:读锁。如果其他线程都没有独占所有权,可获得。
没有人在写,读的内容才可能保持一致。
在一个线程内,同一时刻只能获取一个锁(共享或独占性)
https://zh.cppreference.com/w/cpp/thread/shared_mutex

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex shared_mtx;
int shared_var = 0;

void read_shared_data(int thread_id) {
    std::shared_lock<std::shared_mutex> lock(shared_mtx);
    std::cout << "Thread " << thread_id << " reading shared data: " << shared_var << std::endl;
}

void write_shared_data() {
    std::unique_lock<std::shared_mutex> lock(shared_mtx);
    shared_var += 42;
    std::cout << "Shared data is now: " << shared_var << std::endl;
}

int main() {
    std::thread t1(read_shared_data, 1);
    std::thread t2(read_shared_data, 2);
    std::thread t3(write_shared_data);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}
定时的互斥体类–有时间限制

<mutex> :timed_mutex,recursive_timed_mutex
<shared_mutex>:shared_timed_mutex(C++17)
支持非定时互斥体类的方法。同时还支持:
try_lock_for
尝试在给定的相对时间内获得锁。如果在指定时间内无法获取到锁,则会立即返回,而不会阻塞当前线程。因此,try_lock_for 可以用来实现非阻塞式的锁获取,从而避免因为长时间的等待而导致的性能问题。

template< class Rep, class Period >
bool try_lock_for( const std::chrono::duration<Rep,Period>& timeout_duration );
(C++11)
尝试锁互斥。阻塞直到经过指定的 timeout_duration 或得到锁,取决于何者先到来。
成功获得锁时返回 true , 否则返回 false 。

若 timeout_duration 小于或等于 timeout_duration.zero() ,则函数表现同 try_lock() 。

由于调度或资源争议延迟,此函数可能阻塞长于 timeout_duration 。

标准推荐用 steady_clock 度量时长。若实现用 system_clock 代替,则等待时间亦可能对时钟调整敏感。

若已占有 mutex 的线程调用 try_lock_for ,则行为未定义。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

std::timed_mutex mtx;

void try_lock_for_example() {
    if (mtx.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "Thread " << std::this_thread::get_id() << " got the lock!" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        mtx.unlock();
    } else {
        std::cout << "Thread " << std::this_thread::get_id() << " failed to get the lock." << std::endl;
    }
}

int main() {
    std::thread t1(try_lock_for_example);
    std::thread t2(try_lock_for_example);
    std::thread t3(try_lock_for_example);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}
/*
在 main() 函数中,我们创建了三个线程,分别调用 try_lock_for_example() 函数。
由于我们在 try_lock_for_example() 中使用了 if (mtx.try_lock_for(std::chrono::milliseconds(100))) 
来尝试获取锁,并且指定了 100 毫秒的超时时间,
所以如果多个线程同时尝试获取锁的话,可能会有一些线程在指定时间内无法获取到锁,从而输出失败消息。
*/

try_lock_until
尝试获得锁,直到系统时间等于或超过指定的绝对时间。

template< class Clock, class Duration >
bool try_lock_until( const std::chrono::time_point<Clock,Duration>& timeout_time );
(C++11)
尝试锁住互斥(Tries to lock the mutex)。阻塞直至抵达指定的 timeout_time 或得到锁,取决于何者先到来。
成功获得锁时返回 true ,否则返回 false 。

若已经过 timeout_time ,则此函数表现同 try_lock() 。

Clock 必须符合时钟 (Clock) 要求。若 std::chrono::is_clock_v<Clock>false 则程序为非良构。 (C++20)

标准推荐使用绑定到 timeout_time 的时钟,该情况下可以计入时钟的调整。从而在调用时刻,阻塞的时长可以,
但也可以不,小于或大于 timeout_time - Clock::now() 。这取决于调整的方向以及实现是否尊重它。函数亦可能由于调度或资源纠纷延迟,而阻塞长于到抵达 timeout_time 之后。

同 try_lock() ,允许此函数虚假地失败并返回 false ,即使在 timeout_time 前的某点任何线程都不锁定互斥。

若此操作返回 true ,则同一互斥上先前的 unlock() 调用同步于(定义于 std::memory_order )它。

若已占有 mutex 的线程调用 try_lock_until ,则行为未定义。

time_point:
类模板 std::chrono::time_point 表示时间中的一个点。它被实现成如同存储一个 Duration 类型的自 Clock 的纪元起始开始的时间间隔的值。
#include <thread>
#include <iostream>
#include <chrono>
#include <mutex> 
std::timed_mutex test_mutex; 
void f()
{
    auto now=std::chrono::steady_clock::now();
    test_mutex.try_lock_until(now + std::chrono::seconds(10));
    std::cout << "hello world\n";
}
 
int main()
{
    std::lock_guard<std::timed_mutex> l(test_mutex);
    std::thread t(f);
    t.join();
}

shared_timed_mutex还支持try_lock_shared_for,try_lock_shared_until,参数同上。

除了recursive锁,两外两种不可以在已拥有对应类型mutex所有权的情况下,再次获得这个互斥体上的锁,否则思索

23.4.2 锁
一般情况创建时自动lock,不然阻塞
RAII,全称资源获取即初始化(英语:Resource Acquisition Is Initialization)
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,
即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。
在这种要求下,只要对象能正确地析构,就不会出现资源泄漏问题。

锁类时RAII类。可用于更方便正确地获得和释放互斥体上的锁:锁类的析构函数会自动释放锁关联的互斥体。
在这里插入图片描述
在这里插入图片描述

lock_guard

创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥,等效地调用 m.lock() 不需要提前手动锁定互斥量

std::lock_guard<Mutex>::lock_guard

explicit lock_guard( mutex_type& m );(1)	(C++11)
lock_guard( mutex_type& m, std::adopt_lock_t t );(2)	(C++11)

获得给定互斥 m 的所有权。

1) 等效地调用 m.lock()2) 获得互斥 m 的所有权而不试图锁定它。若当前线程不在 m 上保有非共享锁
(即由 lock、 try_lock、 try_lock_for 或 try_lock_until 取得的锁)则行为未定义。

若 m 先于 lock_guard 对象被销毁,则行为未定义。

参数
m	-	要获得所有权的共享互斥
t	-	用于选择构造函数非锁定版本的标签参数
异常
1) 抛任何 m.lock() 所抛的异常
2) 不抛出
scoped_lock

与lock_guard类似,只是可以接收多个互斥体,获取多个锁。不需要提前手动锁定互斥量

std::scoped_lock<MutexTypes...>::scoped_lock
explicit scoped_lock( MutexTypes&... m );(1)	(C++17)
scoped_lock( std::adopt_lock_t, MutexTypes&... m );(2)	(C++17)

取得给定互斥 m 的所有权。

1)sizeof...(MutexTypes) == 0 则不做任何事。否则若 sizeof...(MutexTypes) == 1 ,
则等效地调用 m.lock() 。否则,等效地调用 std::lock(m...) 。所有互斥锁都会被锁定。
2) 取得互斥 m... 的所有权而不试图锁定任何互斥。除非当前线程在 m... 中的每个对象上保有一个非共享锁
(即以 lock、 try_lock、 try_lock_for 或 try_lock_until 取得的锁),否则行为未定义。
adopt_lock表示当前线程已经锁定了所有互斥锁,不要二次锁定。通常是获取互斥锁之前,手动锁定过。

若 m 在 scoped_lock 对象之前被销毁,则行为未定义。

示例:
std::mutex mutex1, mutex2;
//手动锁定,
mutex1.lock();
mutex2.lock();
std::scoped_lock<std::mutex, std::mutex> lock(std::adopt_lock, mutex1, mutex2);
//或者,也可以创建不带lock的匿名锁,且自动锁定互斥锁
std::scoped_lock<std::mutex, std::mutex> (mutex1, mutex2);
//当然,可以让编译器根据模板自己推导参数类型
std::scoped_lock lock(mutex1, mutex2);
示例一
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutex1, mutex2;
void thread_func()
{
    std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);
    std::cout << "Thread" << std::this_thread::get_id() 
        << " acquired mutex1 and mutex2" << std::endl;
    //可做些事儿
    std::cout << "Thread" << std::this_thread::get_id() 
        << " released mutex1 and mutex2" << std::endl;
}
int main()
{
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    
    t1.join();
    t2.join();

    getchar();
    return 0;
}
示例二:cppreference-scoped_lock
#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <functional>
#include <chrono>
#include <string>
 
struct Employee {
    Employee(std::string id) : id(id) {}
    std::string id;
    std::vector<std::string> lunch_partners;
    std::mutex m;
    std::string output() const
    {
        std::string ret = "Employee " + id + " has lunch partners: ";
        for( const auto& partner : lunch_partners )
            ret += partner + " ";
        return ret;
    }
};
 
void send_mail(Employee &, Employee &)
{
    // 模拟耗时的发信操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
 
void assign_lunch_partner(Employee &e1, Employee &e2)
{	
    static std::mutex io_mutex; 
    {
        std::lock_guard<std::mutex> lk(io_mutex);
        std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
    }
 
    {
        // 用 std::scoped_lock 取得二个锁,而无需担心
        // 其他对 assign_lunch_partner 的调用死锁我们
        // 而且它亦提供便利的 RAII 风格机制
 
        std::scoped_lock lock(e1.m, e2.m);
 
        // 等价代码 1 (用 std::lock 和 std::lock_guard )
        // std::lock(e1.m, e2.m);
        // std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
        // std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
 
        // 等价代码 2 (若需要 unique_lock ,例如对于条件变量)
        // std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
        // std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
        // std::lock(lk1, lk2);
        {
            std::lock_guard<std::mutex> lk(io_mutex);
            std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
        }
        e1.lunch_partners.push_back(e2.id);
        e2.lunch_partners.push_back(e1.id);
    }
 
    send_mail(e1, e2);
    send_mail(e2, e1);
}
 
int main()
{
    Employee alice("alice"), bob("bob"), christina("christina"), dave("dave");
 
    // 在并行线程中指派,因为就午餐指派发邮件消耗很长时间
    std::vector<std::thread> threads;
    threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
    threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
    threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob));
 
    for (auto &thread : threads) thread.join();
    std::cout << alice.output() << '\n'  << bob.output() << '\n'
              << christina.output() << '\n' << dave.output() << '\n';
}

重要解释
lock_guard是一个互斥锁的包装类,用于在一个作用域内用有锁,离开lock_guard对象的作用域时,会被析构,同时释放互斥锁。

io_mutex是个静态的互斥锁,用于保护std::cout这一全局变量,阻止同时被多个线程访问,不然出现混乱。

静态成员只有一份(类的静态成员不属于实例,同类共享),保证所有对象均使用一个锁来访问cout

上述代码中的std::lock_guard<std::mutex> lk(io_mutex);分别保护对应的cout。
assign_launch_partner中几处大括号去掉的话,lock_guard,scoped_lock对象作用域就会扩大到整个函数体,可能导致不必要的锁定和解锁操作,影响性能和正确性。{}用以精确地控制锁的范围和时间。(虽然不一定出问题)

unique_lock

类 unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。
类 unique_lock 可移动,但不可复制
类 unique_lock 满足基本可锁定 (BasicLockable) 要求。若 Mutex 满足可锁定 (Lockable) 要求,则 unique_lock 亦满足可锁定 (Lockable) 要求(例如:能用于 std::lock ) ;若 Mutex 满足可定时锁定 (TimedLockable) 要求,则 unique_lock 亦满足可定时锁定 (TimedLockable) 要求。

std::unique_lock<Mutex>::unique_lock

unique_lock() noexcept;

//移动构造函数。以 other 的内容初始化 unique_lock ,
unique_lock( unique_lock&& other ) noexcept;

//接收一个互斥体引用,尝试获得互斥体上的锁,并阻塞直到获得锁。
//自动给它锁定lock 与lock_guard同
explicit unique_lock( mutex_type& m );

//存储互斥体的引用,但不立即尝试获得锁,锁可以稍后获得
unique_lock( mutex_type& m, std::defer_lock_t t ) noexcept;

//尝试获得引用的互斥体上的锁,未能获得也不阻塞,此时,稍后获得锁
unique_lock( mutex_type& m, std::try_to_lock_t t );

//假定调用线程已经获得引用的互斥体上的锁。锁管理互斥体,并在销毁时自动释放互斥体
unique_lock( mutex_type& m, std::adopt_lock_t t );

/*
通过调用 m.try_lock_for(timeout_duration) 尝试锁定。阻塞到经过指定的 timeout_duration(要阻塞的最大时长) 或获得锁这两个事件的先到来者为止。
可能阻塞长于 timeout_duration 。
若 Mutex 不满足可定时锁定 (TimedLockable) 则行为未定义。
*/

template< class Rep, class Period >
unique_lock( mutex_type& m,
             const std::chrono::duration<Rep,Period>& timeout_duration );
/*
通过调用 m.try_lock_until(timeout_time) 尝试锁定关联互斥。阻塞到抵达指定的 timeout_time(要阻塞到的最大时间点) 或获得锁这两个事件的先到来者为止。
可能阻塞长于抵达 timeout_time 。若 Mutex 不满足可定时锁定 (TimedLockable) 则行为未定义。
*/
template< class Clock, class Duration >
unique_lock( mutex_type& m,
             const std::chrono::time_point<Clock,Duration>& timeout_time );

unique_lock同样有以下类方法:lock,try_lock,try_lock_for,try_lock_until,unlock

std::unique_lock<Mutex>::mutex
mutex_type* mutex() const noexcept;(C++11)
返回指向关联互斥的指针,或若无关联互斥则返回空指针

bool owns_lock() const noexcept;(C++11)
检查 *this 是否占有锁定的互斥
拥有关联互斥且已获得其所有权则为 true ,否则为 false
#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <chrono>
 
int main()
{
    int counter = 0;
    std::mutex counter_mutex;
    std::vector<std::thread> threads;
 
    auto worker_task = [&](int id) {
        std::unique_lock<std::mutex> lock(counter_mutex);
        ++counter;
        std::cout << id << ", initial counter: " << counter << '\n';
        lock.unlock();
 
        // 我们模拟昂贵操作时不保有锁
        std::this_thread::sleep_for(std::chrono::seconds(1));
 
        lock.lock();
        ++counter;
        std::cout << id << ", final counter: " << counter << '\n';
    }; 
    for (int i = 0; i < 10; ++i) threads.emplace_back(worker_task, i); 
    for (auto &thread : threads) thread.join();
}
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>

std::timed_mutex mtx;

void print(int n)
{
  std::unique_lock<std::timed_mutex> lck(mtx, std::defer_lock);
  // 尝试获取锁,最多等待1秒
  if (lck.try_lock_for(std::chrono::seconds(1))) {
    std::cout << "Thread " << n << " got the lock\n";
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟工作
  }
  else {
    std::cout << "Thread " << n << " failed to get the lock\n";
  }
}

int main()
{
  std::thread threads[3];
  // 启动3个线程
  for (int i = 0; i < 3; ++i)
    threads[i] = std::thread(print, i + 1);
  for (auto& th : threads) th.join();
  return 0;
}
#include <mutex>
#include <chrono>
#include <iostream>
#include <thread>
std::timed_mutex mtx;

void print(int n)
{
  // 使用带相对时间的unique_lock构造函数
  std::unique_lock<std::timed_mutex> lck(mtx, std::chrono::seconds(1));

  if (lck.owns_lock()) {
    std::cout << "Thread " << n << " got the lock\n";
  }
  else {
    std::cout << "Thread " << n << " failed to get the lock\n";
  }
}

int main()
{
  std::thread threads[3];
  // 启动3个线程
  for (int i = 0; i < 3; ++i)
    threads[i] = std::thread(print, i + 1);
  for (auto& th : threads) th.join();
  return 0;
}
shared_lock

定义于<shared_mutex>中,构造函数和方法与unique_lock相同。区别在于shared_lock在底层的共享互斥体调用与共享所有权相关的方法。因此,shared_lock方法为lock等,但在底层的共享互斥体上,称为lock_shared.所以与unique_lock有相同的接口,可替代之。但获得的是共享锁,而不是独占锁。

一次获得多个锁

可变参数模板函数lock,try_lock,在std空间中

std::lock
在标头 <mutex> 定义
template< class Lockable1, class Lockable2, class... LockableN >
void lock( Lockable1& lock1, Lockable2& lock2, LockableN&... lockn );(C++11)

锁定给定的可锁定 (Lockable) 对象 lock1 、 lock2 、 ... 、 lockn ,用免死锁算法避免死锁。
且不按指定顺序

以对 lock 、 try_lock 和 unlock 的未指定系列调用锁定对象。若调用 lock 或 unlock 导致异常,则在重抛前对任何已锁的对象调用 unlock 。


std::try_lock
在标头 <mutex> 定义
template< class Lockable1, class Lockable2, class... LockableN >
int try_lock( Lockable1& lock1, Lockable2& lock2, LockableN&... lockn );(C++11)
尝试锁定每个给定的可锁定 (Lockable) 对象 lock1、 lock2、 ...、 lockn ,通过以从头开始的顺序调用 try_lock 。

若调用 try_lock 失败,则不再进一步调用 try_lock ,并对任何已锁对象调用 unlock ,返回锁定失败对象的 0 底下标。

若调用 try_lock 抛出异常,则在重抛前对任何已锁对象调用 unlock 。
23.4.3 std::call_once

结合使用std::call_once()和std::once_flag可确保函数或方法正好只调用一次,不论有多少个线程试图调用call_once()(在同一once_flag上)都是如此。
如果给定的函数不抛出异常,称为有效的call_once()调用。抛出异常,异常将传回调用者,选择另一个调用者来执行此函数。
对于同一个once_flag,一个线程的有效调用完成后,其他线程的call_once才能继续执行,之前线程都是阻塞状态。

template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
参数
flag	-	对象,对于它只有一个函数得到执行
f	-	要调用的可调用 (Callable) 对象
args...	-	传递给函数的参数

异常
如果有任何条件阻止对 std::call_once 的调用按规定执行,那么就会抛出 std::system_error
任何 f 抛出的异常
注解
如果对 std::call_once 进行并发调用时分别传递不同的函数 f ,那么哪个 f 将被执行是不明确的。
被选中的函数会在与之对应的 std::call_once 的被调用线程中执行。
由于函数局域静态对象(首次经过它的声明时才会被初始化)的初始化在多线程调用下也保证只触发一次,
这可能比使用 std::call_once 的等价代码更为高效。

准确执行一次可调用 (Callable) 对象 f,即使同时从多个线程调用。
准确地说:
如果在调用 std::call_once 的时刻,flag 指示 f 已经调用过,那么 std::call_once 会立即返回称这种对 std::call_once 的调用为消极)。
否则,std::call_once 会调用 INVOKE(std::forward<Callable>(f), std::forward<Args>(args)…)。与 std::thread 的构造函数或 std::async 不同,不会移动或复制参数,因为不需要转移它们到另一执行线程(称这种对 std::call_once 的调用为积极)。
如果该调用抛出了异常,那么将异常传播给 std::call_once 的调用方,并且不翻转 flag,这样还可以尝试后续调用(称这种对 std::call_once 的调用为异常)。
如果该调用正常返回(称这种对 std::call_once 的调用为返回),那么翻转 flag,并保证以同一 flag 对 std::call_once 的其他调用为消极。


#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;
void myFunction()
{
    std::cout << std::this_thread::get_id() << 
        " Function called" << std::endl;
}
void myThreadFunction()
{
    std::call_once(flag, myFunction);
}

int main()
{
    std::thread t1(myThreadFunction);
    std::thread t2(myThreadFunction);
    std::thread t3(myThreadFunction);

    t1.join();
    t2.join();
    t3.join();
    
    return 0;
}
/*可能的输出
2 Function called
*/
23.4.4 示例——双重检查锁定

double-checked locking 实际上是一种反模式。避免使用。

旨在尝试避免使用互斥体对象。保证线程安全的同时,减少锁的使用次数。
通常用于实现单例模式。单例模式是指一个类只能创建一个实例,并提供一个全局访问点来访问该实例,因此多线程下要保证线程安全,双重检查锁定就是一种常见实现方式。

基本思路:
第一次访问时检查实例是否已创建,没有,则加锁创建,否则直接返回已经创建的实例。
此时,使用互斥锁保证创建线程安全。为了避免每次访问都加锁,因此可以在加锁前使用一次判断来看是否已经创建实例。
第二次检查用于确保没有其他线程在第一次检查和获得锁之间执行初始化。

mport <iostream>;
import <thread>;
import <mutex>;
import <vector>;
import <atomic>;

using namespace std;

void initializeSharedResources()
{
	// ... Initialize shared resources to be used by multiple threads.
	cout << "Shared resources initialized." << endl;
}

atomic<bool> g_initialized{ false };
mutex g_mutex;

void processingFunction()
{
	if (!g_initialized) { //第一次
		unique_lock lock{ g_mutex };
		if (!g_initialized) { //第二次
			initializeSharedResources();
			g_initialized = true;
		}
	}
	cout << "OK" << endl;
}

int main()
{
	vector<thread> threads;

	for (int i{ 0 }; i < 5; ++i) {
		threads.push_back(thread{ processingFunction });
	}

	for (auto& t : threads) {
		t.join();
	}
}

23.5 条件变量<condition_variable>

23.2.6 取消线程 中

具体来说,您可以创建一个名为 stop_thread 的布尔变量,以指示线程是否应该继续运行。然后,您可以创建一个 std::condition_variable 对象,并使用它来等待外部停止信号。在主线程中设置 stop_thread 为 true,并调用 notify_all() 函数来通知所有等待线程停止运行。线程可以在运行时检查 stop_thread 变量,并在其为 true 时退出。	

condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。。允许显式的线程间通信

在这里插入图片描述
condition_variable_any 类是 std::condition_variable 的泛化。相对于只在 std::unique_lock、<std::mutex> 上工作的 std::condition_variable , condition_variable_any 能在任何满足基本可锁定 (BasicLockable) (提供lock,unlock)要求的锁上工作。

成员方法

std::condition_variable::notify_one
void notify_one() noexcept; //唤醒等待这个条件变量的线程之一
std::condition_variable::notify_all
void notify_all() noexcept; //唤醒等待这个条件变量的所有线程

wait
调用时,已经获得lock上的锁。
它将阻塞线程,并释放互斥量知道有cv.notify通知,它将被唤起并重新获得并锁定互斥量
在这里插入图片描述
wait_for
在这里插入图片描述
wait_until

template< class Clock, class Duration, class Pred >
bool wait_until( std::unique_lock<std::mutex>& lock,
                 const std::chrono::time_point<Clock, Duration>& timeout_time,
                 Pred pred );
参数
lock	-	std::unique_lock<std::mutex> 类型对象,必须被当前线程锁定
timeout_time	-	表示停止等待时间的 std::chrono::time_point 类型对象
pred	-	是否应该持续等待则返回 ​false 的谓词。
谓词函数的签名应等价于如下者:

 bool pred(); 

cv_status
在这里插入图片描述

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>

std::mutex m;
std::condition_variable cv;
bool stop_thread = false;

void worker_thread() {
    std::unique_lock<std::mutex> lock(m);
    while (!stop_thread) {
        // 等待外部停止信号
        cv.wait(lock);
        // 处理任务
        std::cout << "Worker thread is running" << std::endl;
    }
    std::cout << "Worker thread is stopped" << std::endl;
}
int main() {
    std::thread t(worker_thread);

    // 等待一段时间后停止线程
    std::this_thread::sleep_for(std::chrono::seconds(3));
    {
        std::lock_guard<std::mutex> lock(m);
        stop_thread = true;
    }
    cv.notify_all();
    t.join();
    return 0;
}
在该示例中,worker_thread 函数中的 while 循环会在等待 cv 条件变量时阻塞,直到收到外部停止信号才会退出循环。在主线程中,我们等待一段时间后设置 stop_thread 为 true,并调用 notify_all() 函数来通知等待线程停止运行。最后,我们等待线程完成并退出。
重要例子
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    // 等待直至 main() 发送数据
    /*
	创建线程时,尝试获取m,但缺少ready,cv将阻塞该函数所在线程,释放m,等待ready
	m被之后的main线程获取
	*/
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{return ready;});
 	//ready为true,m也被main释放。唤醒该线程
    // 等待后,我们占有锁。
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    // 发送数据回 main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    // 通知前完成手动解锁,释放互斥量m,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
    //不然cv通知后出了该函数,才释放m,白白唤醒main了,还是被阻塞。,
    lk.unlock();
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 	//worker线程因少ready而阻塞,释放m
    data = "Example data";
    // 发送数据到 worker 线程
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    //唤醒worker
    cv.notify_one();
 
    // 等候 worker
    {	/*与此同时,main尝试获取m,但是cv发现不但没有m(worker线程占有),
    	processed条件也不满足,阻塞main		
		*/
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    //worker线程执行完成
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
}
/*
输出:
main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing
*/

notify_all_at_thread_exit

在标头 <condition_variable> 定义
void notify_all_at_thread_exit( std::condition_variable& cond,
                                std::unique_lock<std::mutex> lk );(C++11)
notify_all_at_thread_exit 提供机制,通知其他线程给定的线程已完全完成,包括销毁所有 thread_local 对象。它操作如下:

将先前获得的锁 lk 的所有权转移到内部存储。
修改执行环境,以令当前线程退出时,如同以下列方式通知 condition_variable cond :
lk.unlock();
cond.notify_all();
参数
cond	-	在线程退出时通知的 conditional_variable
lk	-	关联到 condition_variable cond 的锁
23.5.1 假唤醒

等待CV的线程在另一个线程调用notify_one()或nofify_all()醒来,或在系统时间超过给定时间醒来,也可能不合时宜地醒来。即,其他线程没有任何通知,线程也醒过来。解决办法是,使用带谓词的wait版本来检查是否收到了通知。

23.6 <future>

标准库提供了一些工具来获取异步任务(即在单独的线程中启动的函数)的返回值,并捕捉其所抛出的异常。这些值在共享状态中传递,其中异步任务可以写入其返回值或存储异常,而且可以由持有该引用该共享态的 std::future 或 std::shared_future 实例的线程检验、等待或是操作这个状态。

future在promise中存储结果,通过future获取promise中存储的结果。promise是结果的输入端,future是结果的输出端。

future<T> myfuture = ...;
T result = myFuture.get()

get()获取结果,如果另一个线程尚未结算完成,则get()调用将阻塞,直到结果值可用。只能在future上调用一次get,第二次行为不确定。

23.6.1 std::promise和std::future

可在promise上调用set_value()来存出结果,set_exception()存储异常指针 p。
只能对特定的promise上调用set_value或set_exception 一次。多次,则抛出std::future_error异常

线程A启动另一个线程B计算,则A可创建一个promise,将其传给B(promise不可复制,但可移动)。线程B使用promise存储结果。promise移入B之前,A在创建promise上调用get_future(),这样B完成后,A就可以访问结果。

#include <iostream>
#include <thread>
#include <future>

using namespace std;

void doWork(promise<int> thePromise)
{
	// ... Do some work ...
	// And ultimately store the result in the promise.
	thePromise.set_value(42);
}

int main()
{
	// Create a promise to pass to the thread.
	promise<int> myPromise;
	// Get the future of the promise.
	auto theFuture{ myPromise.get_future() };
	// Create a thread and move the promise into it.
	thread theThread{ doWork, move(myPromise) };

	// Do some more work...

	// Get the result.
	int result{ theFuture.get() };
	cout << "Result: " << result << endl;

	// Make sure to join the thread.
	theThread.join();
}

future

类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象能指代同一共享状态
若每个线程通过其自身的 shared_future 对象副本访问,则从多个线程访问同一共享状态是安全的。

构造
template< class T > class future;(1)	(C++11 起)
template< class T > class future<T&>;(2)	(C++11 起)
template<>          class future<void>;(3)	(C++11 起)
类模板 std::future 提供访问异步操作结果的机制:
1.(通过 std::async、std::packaged_task 或 std::promise 创建的)异步操作能提供一个 std::future 对象给该异步操作的创建者。
2.然后,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。若异步操作仍未提供值,则这些方法可能阻塞。
3.异步操作准备好发送结果给创建者时,它能通过修改链接到创建者的 std::future 的共享状态(例如 std::promise::set_value )进行。
注意, std::future 所引用的共享状态不与另一异步返回对象共享(与 std::shared_future 相反)。

std::future<T>::future
future() noexcept;(1)	(C++11 起)
future( future&& other ) noexcept;(2)	(C++11 起)
future( const future& other ) = delete;(3)	(C++11 起)
构造 std::future 对象。
1) 默认构造函数。构造无共享状态的 std::future 。构造后, valid() == false 。
2) 移动构造函数。用移动语义,以 other 的共享状态构造 std::future 。构造后 other.valid() == false 。
3) std::future 不可复制构造 (CopyConstructible) 。
参数
other	-	获得共享状态来源的另一 std::future
share
std::future<T>::share
std::shared_future<T> share() noexcept;
转移 *this 的共享状态,若存在,到 std::shared_future 对象。多个 std::shared_future 对象可引用同一共享对象,这对于 std::future 不可能。
在 std::future 上调用 share 后 valid() == false 。
返回值
含有先前 *this 所保有的共享状态(若存在)的 std::shared_future 对象,如同以 std::shared_future<T>(std:move(*this)) 构造。
get
std::future<T>::get

T get();(1)	(仅为泛型 future 模板的成员)(C++11)
T& get();(2)	(仅为 future<T&> 模板特化的成员)(C++11)
void get();(3)	(仅为 future<void> 模板特化的成员)(C++11)
get 方法等待直至 future 拥有合法结果并(依赖于使用哪个模板)获取它。它等效地调用 wait() 等待结果。
泛型模板和二个模板特化各含单个 get 版本。 get 的三个版本仅在返回类型有别。
若调用此函数前 valid()false 则行为未定义。
释放任何共享状态。调用此方法后 valid()false 。
返回值
1) 存储于共享状态的值 v ,如同 std::move(v)2) 存储于共享状态的值的引用。
3) 无。
异常
若 future 所引用的共享状态中存储异常(例如,通过调用 std::promise::set_exception() ),则抛出该异常。
valid
bool valid() const noexcept;(C++11)
检查期货是否指代共享状态。
这是非默认构造或被移动的(例如为 std::promise::get_future() 、 std::packaged_task::get_future() 或 std::async() 所返回)在首次调用 get()share() 前的唯一情况。
若在不指代共享状态的 future 上调用任何析构函数、移动赋值运算符或 valid 以外的成员函数,则行为未定义(尽管鼓励实现在此情况下抛出指示 no_state 的 std::future_error )。从 valid()false 的 future 对象移动是合法的。
返回值
若 *this 指代共享状态则为 true ,否则为 false
operator=
future& operator=( future&& other ) noexcept;(1)	(C++11)
赋值另一 future 对象的内容。
1) 释放任何共享状态并移动赋值 other 的内容给 *this 。赋值后, other.valid() == falsethis->valid() 将产生与 other.valid() 在赋值前相同的值。
wait
void wait() const;(C++11)
阻塞直至结果变得可用。调用后 valid() == true 。
若调用此函数前 valid() == false 则行为未定义。


等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回
std::future<T>::wait_for
template< class Rep, class Period >
std::future_status wait_for( const std::chrono::duration<Rep,Period>& timeout_duration ) const;
等待结果变得可用。阻塞直至经过指定的 timeout_duration ,或结果变为可用,两者的先到来者。返回值鉴别结果的状态。
此函数可能由于调度或资源争议延迟而阻塞长于 timeout_duration 。
推荐标准库用稳定时钟度量时长。若实现用系统时钟代替,则等待时间可能也对时钟调整敏感。
若调用此函数前 valid()== false 则行为未定义。
参数
timeout_duration	-	要阻塞的最大时长
返回值
std::future_status
常量					解释
future_status::deferred	共享状态持有的函数正在延迟运行,结果将仅在显式请求时计算
future_status::ready	共享状态就绪
future_status::timeout	共享状态在经过指定的等待时间内仍未就绪
例如
std::future_status status;
status = future.wait_for(std::chrono::seconds(1));



template< class Clock, class Duration >
std::future_status wait_until( const std::chrono::time_point<Clock,Duration>& timeout_time ) const;
参数
timeout_time	-	要阻塞到的最大时间点
例如
std::chrono::system_clock::time_point two_seconds_passed
   = std::chrono::system_clock::now() + std::chrono::seconds(2);
std::future_status = f_completes.wait_until(two_seconds_passed))
promise

promise 可以对共享状态做三件事
使就绪: promise 存储结果或异常于共享状态。标记共享状态为就绪,并解除阻塞任何等待于与该共享状态关联的 future 上的线程。
释放: promise 放弃其对共享状态的引用。若这是最后一个这种引用,则销毁共享状态。除非这是 std::async 所创建的未就绪的共享状态,否则此操作不阻塞。
抛弃: promise 存储以 std::future_errc::broken_promise 为 error_code 的 std::future_error 类型异常,令共享状态为就绪,然后释放它。

构造
std::promise<R>::promise
promise();(1)	(C++11)
template< class Alloc >
promise( std::allocator_arg_t, const Alloc& alloc );(2)	(C++11)
promise( promise&& other ) noexcept;(3)	(C++11)
promise( const promise& other ) = delete;(4)	(C++11)
构造 std::promise 对象。

1) 默认构造函数,构造一个共享状态为空的 std::promise ;
2) 构造一个共享状态为空的 std::promise,由 alloc 分配共享状态, Alloc 必须满足分配器 (Allocator) 的要求;
3) 移动构造函数,用原属 other 的共享状态构造新的 std::promise 对象,使用移动语义。构造完毕后, other 无共享状态;
4) std::promise 不可复制。
参数
alloc	-	分配器,用于分配共享状态;
other	-	另一 std::promise 对象,作为获得共享状态的来源。
set_value
set_value_at_thread_exit参数同set_value
原子地存储 value 到共享状态,而不立即令状态就绪。
在当前线程退出时,销毁所有拥有线程局域存储期的对象后,再令状态就绪。

std::promise<R>::set_value
void set_value( const R& value );(1)	(C++11)(仅为泛型 promise 模板的成员)
void set_value( R&& value );(2)	(C++11)(仅为泛型 promise 模板的成员)
void set_value( R& value );(3)	(C++11)(仅为 promise<R&> 模板特化的成员)
void set_value();(4)	(C++11)(仅为 promise<void> 模板特化的成员)
1-3) 原子地存储 value 到共享状态,并令状态就绪。
4) 使状态就绪。
set_value 、 set_exception 、 set_value_at_thread_exit 和 set_exception_at_thread_exit 的操作表现类似。在更新 promise 对象时获得单个与 promise 对象关联的互斥。
若无共享状态或共享状态已存储值或异常,则抛出异常。
对此函数的调用和对 get_future 的调用不会造成数据竞争(但它们不需要彼此同步)。
参数
value	-	要存储于共享状态的值
set_exception
void set_exception( std::exception_ptr p );(C++11)
存储异常指针 p 到共享状态中,并令状态就绪。
例如
std::promise<int> p;
std::future<int> f = p.get_future();
// 存储任何抛出的异常于 promise
p.set_exception(std::current_exception());
get_future
std::future<T> get_future();(C++11)
返回与 *this 关联同一状态的 future 对象。
若 *this 无共享状态,或已调用 get_future 则抛出异常。可使用 std::future::share 以获取 promise-future 交流通道的多个“弹出”端。

23.6.2 std::packaged_task

类模板 std::packaged_task 包装任何可调用 (Callable) 目标(函数、 lambda 表达式、 bind 表达式或其他函数对象–见开头),使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。

std::packaged_task<R(Args...)>::packaged_task

packaged_task() noexcept;(1)	(C++11)
template< class F >explicit packaged_task( F&& f );(2)	(C++11)
packaged_task( const packaged_task& ) = delete;(4)	(C++11)
packaged_task( packaged_task&& rhs ) noexcept;(5)	(C++11)
构造新的 std::packaged_task 对象。
1) 构造无任务且无共享状态的 std::packaged_task 对象。
4) 复制构造函数被删除, std::packaged_task 只能移动。
5) 以 rhs 之前所占有的共享状态和任务构造 std::packaged_task,令 rhs 留在无共享状态且拥有被移动后的任务的状态。
参数
f	-	要执行的可调用目标(函数、成员函数、lambda 表达式、函数对象)
a	-	存储任务时所用的分配器
rhs	-	要移动的 std::packaged_task
#include <future>
#include <iostream>
#include <thread>
 
int fib(int n)
{
    if (n < 3)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}
 
int main()
{
    std::packaged_task<int(int)> fib_task(&fib); 
 
    std::cout << "开始任务\n";
    auto result = fib_task.get_future();
    //std::future<int> result = fib_task.get_future();
    std::thread t(std::move(fib_task), 42);
 
    std::cout << "等待任务结束..." << std::endl;
    std::cout << result.get() << '\n';
 
    std::cout << "任务完成\n";
    t.join();
}
/*
开始任务
等待任务结束...
267914296
任务完成
*/
promise存储一个值以进行异步获取
packaged_task打包一个函数,存储其返回值以进行异步获取
future等待被异步设置的值
shared_future等待被异步设置的值(可能为其他 future 所引用)
23.6.3 std::async

异步运行一个函数(有可能在新线程中执行),并返回保有它的结果的 std::future

人为控制是否创建一个线程以进行计算。接收一个将要执行的函数,并返回可用于检索结果的future。两种方法运行函数:

  1. 创建一个新的线程,异步运行提供的函数
  2. 在返回的future上调用get()方法,在主线程上同步运行函数。

如果没有通过额外参数调用async(),C++会根据一些因素自动选择一种。也可指定策略:

  1. launch::async:强制C++在不同的现呈上异步地执行函数
  2. launch::deferred:强制C++运行时调用get(),在主调线程上同步地执行函数
  3. launch::async | launch::deferred:默认的,自己选择。
async( std::launch policy, Function&& f, Args&&... args );
参数
f	-	要调用的可调用 (Callable) 对象
args...	-	传递给 f 的参数
policy	-	位掩码值,每个单独位控制允许的执行方法 可选
位					解释
std::launch::async	启用异步求值
std::launch::deferred	启用惰性求值
例如:auto a3 = std::async(std::launch::async, X(), 43);
必须有变量接收async返回值,因为调用它返回的future会在析构函数中阻塞,直到结果可用。未捕获返回的future,async将真正成为阻塞调用。
23.6.4 异常处理

future的优点是自动在线程间传递异常。
future上调用get,要么返回计算结果,要么重新抛出future关联的promise中存储的一切异常。
package_task 或async从已启动的函数抛出的任何异常自动存储在promise中

如果std::promise用作promise,可调用set_exception以在其中存储异常。

import <iostream>;
import <future>;
import <exception>;
import <stdexcept>;

using namespace std;

int calculate()
{
	throw runtime_error{ "Exception thrown from calculate()." };
}

int main()
{
	// Use the launch::async policy to force asynchronous execution.
	auto myFuture{ async(launch::async, calculate) };

	// Do some more work...

	// Get the result.
	try {
		int result{ myFuture.get() };
		cout << result << endl;
	} catch (const exception& ex) {
		cout << "Caught exception: " << ex.what() << endl;
	}
}

shared_future 可用于同时向多个线程发信,类似 std::condition_variable::notify_all()

#include <iostream>
#include <future>
#include <chrono>
 
int main()
{   
    std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
    //用于同步线程
    std::shared_future<void> ready_future{ready_promise.get_future()};
 
    std::chrono::time_point<std::chrono::high_resolution_clock> start;
 
    auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli> 
    {
        t1_ready_promise.set_value();
        ready_future.wait(); // 等待来自 main() 的信号
        return std::chrono::high_resolution_clock::now() - start;
    };
 
 
    auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli> 
    {
        t2_ready_promise.set_value();
        ready_future.wait(); // 等待来自 main() 的信号
        return std::chrono::high_resolution_clock::now() - start;
    };
 
    auto result1 = std::async(std::launch::async, fun1);
    auto result2 = std::async(std::launch::async, fun2);
 
    // 等待线程变为就绪
    t1_ready_promise.get_future().wait();
    t2_ready_promise.get_future().wait();
 
    // 线程已就绪,开始时钟
    start = std::chrono::high_resolution_clock::now();
 
    // 向线程发信使之运行
    ready_promise.set_value();
 
    std::cout << "Thread 1 received the signal "
              << result1.get().count() << " ms after start\n"
              << "Thread 2 received the signal "
              << result2.get().count() << " ms after start\n";
}

chatgpt解释:—待学习
好的,我可以尝试用中文解释这段代码。这段代码使用了 C++11 的特性,来演示如何使用 std::promisestd::future 来实现线程间的异步通信¹²。

一个 std::promise 是一个对象,它可以存储一个值或一个异常,然后由一个 std::future 对象异步地获取²。一个 promise 可以使共享状态变为就绪,释放它,或者放弃它²。一个 future 可以获取值,等待它,或者检查它是否可用²。

这段代码定义了一个 main 函数,它创建了两个 promise,分别给每个线程一个,和一个共享的 future,它用第一个 promise 的 future 来初始化。这些 promise 用来信号线程何时就绪,而这个 future 用来同步线程。

这段代码还定义了两个 lambda 函数,fun1 和 fun2,它们接受共享的 future 作为参数,并返回从开始时间到收到主线程信号的时间间隔。这些 lambda 函数使用了 ready_future.wait() 方法来阻塞,直到共享状态就绪²。

这段代码然后创建了两个异步任务,result1 和 result2,它们在不同的线程中运行 lambda 函数。这些任务返回 future,可以用来获取 lambda 函数的结果。

这段代码然后等待线程变为就绪,通过调用 t1_ready_promise.get_future().wait()t2_ready_promise.get_future().wait()。这些方法阻塞,直到 promise 被 lambda 函数设置²。

这段代码然后设置开始时间为当前时间,并向线程发信使之运行,通过调用 ready_promise.set_value()。这个方法设置共享状态的值,并解除任何等待它的线程的阻塞²。

这段代码然后打印 lambda 函数的结果,通过调用 result1.get()result2.get()。这些方法阻塞,直到 future 就绪,并返回共享状态的值²。

这段代码然后加入线程,通过调用 producer.join()consumer.join()。这些方法阻塞,直到线程完成它们的执行³。

这段代码的输出应该显示两个线程几乎同时收到信号,因为它们被共享的 future 同步了。

P570

23.8 线程池
23.9 线程设计和最佳时间P573
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值