diff --git a/404.html b/404.html new file mode 100644 index 00000000..ca339bdb --- /dev/null +++ b/404.html @@ -0,0 +1,40 @@ + + + + + + + + + + 现代C++并发编程教程 + + + + + +
跳至主要內容

404

页面不存在

这里什么也没有

+ + + diff --git a/SUMMARY.html b/SUMMARY.html new file mode 100644 index 00000000..d5e98c79 --- /dev/null +++ b/SUMMARY.html @@ -0,0 +1,40 @@ + + + + + + + + + + Summary | 现代C++并发编程教程 + + + + + +
跳至主要內容

Summary

小于 1 分钟

+ + + diff --git "a/assets/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.html-D9zIyv03.js" "b/assets/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.html-D9zIyv03.js" new file mode 100644 index 00000000..1b67704c --- /dev/null +++ "b/assets/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.html-D9zIyv03.js" @@ -0,0 +1,53 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as c,c as l,a as n,b as s,d as e,e as t}from"./app-Oub5ASTw.js";const r={},i=n("h1",{id:"std-thread-的构造-源码解析",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#std-thread-的构造-源码解析"},[n("span",null,[n("code",null,"std::thread"),s(" 的构造-源码解析")])])],-1),u=n("p",null,[s("我们这单章是为了专门解释一下 C++11 引入的 "),n("code",null,"std::thread"),s(" 是如何构造的,是如何创建线程传递参数的,让你彻底了解这个类。")],-1),d=n("strong",null,"MSVC",-1),_={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread",target:"_blank",rel:"noopener noreferrer"},k=n("code",null,"std::thread",-1),h=n("strong",null,"C++14",-1),m=n("strong",null,"C++17",-1),v={href:"https://zh.cppreference.com/w/cpp/utility/functional/invoke",target:"_blank",rel:"noopener noreferrer"},g=n("code",null,"invoke",-1),b=n("strong",null,"C++14",-1),f=n("h2",{id:"std-thread-的数据成员",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#std-thread-的数据成员"},[n("span",null,[n("code",null,"std::thread"),s(" 的数据成员")])])],-1),T=n("ul",null,[n("li",null,[n("strong",null,"了解一个庞大的类,最简单的方式就是先看它的数据成员有什么"),s("。")])],-1),y=n("code",null,"std::thread",-1),w={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L163",target:"_blank",rel:"noopener noreferrer"},x=n("code",null,"_Thr",-1),A=t(`
private:
+    _Thrd_t _Thr;
+
`,1),E={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/__msvc_threads_core.hpp#L20-L24",target:"_blank",rel:"noopener noreferrer"},S=n("code",null,"_Thrd_t",-1),C=t(`
using _Thrd_id_t = unsigned int;
+struct _Thrd_t { // thread identifier for Win32
+    void* _Hnd; // Win32 HANDLE
+    _Thrd_id_t _Id;
+};
+

结构很明确,这个结构体的 _Hnd 成员是指向线程的句柄,_Id 成员就是保有线程的 ID。

在64 位操作系统,因为内存对齐,指针 8 ,无符号 int 4,这个结构体 _Thrd_t 就是占据 16 个字节。也就是说 sizeof(std::thread) 的结果应该为 16

std::thread 的构造函数

`,4),I=n("code",null,"std::thread",-1),D={href:"https://zh.cppreference.com/w/cpp/thread/thread/thread",target:"_blank",rel:"noopener noreferrer"},L=t(`

默认构造函数,构造不关联线程的新 std::thread 对象。

thread() noexcept : _Thr{} {}
+
`,2),B={href:"https://zh.cppreference.com/w/cpp/language/value_initialization#:~:text=%E5%87%BD%E6%95%B0%E7%9A%84%E7%B1%BB%EF%BC%89%EF%BC%8C-,%E9%82%A3%E4%B9%88%E9%9B%B6%E5%88%9D%E5%A7%8B%E5%8C%96%E5%AF%B9%E8%B1%A1,-%EF%BC%8C%E7%84%B6%E5%90%8E%E5%A6%82%E6%9E%9C%E5%AE%83",target:"_blank",rel:"noopener noreferrer"},z=n("code",null,"_Hnd",-1),F=n("code",null,"_Id",-1),q={href:"https://zh.cppreference.com/w/cpp/language/zero_initialization",target:"_blank",rel:"noopener noreferrer"},R=t(`

移动构造函数,转移线程的所有权,构造 other 关联的执行线程的 std::thread 对象。此调用后 other 不再表示执行线程失去了线程的所有权。

thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}
+
`,2),N={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/yvals_core.h#L1934",target:"_blank",rel:"noopener noreferrer"},H=n("code",null,"::std::",-1),V={href:"https://zh.cppreference.com/w/cpp/utility/exchange",target:"_blank",rel:"noopener noreferrer"},G=n("code",null,"::std::exchange",-1),O=n("code",null,"_Other._Thr",-1),M=n("code",null,"{}",-1),X=n("code",null,"_Other._Thr",-1),U=n("code",null,"_Thr",-1),P=t(`
  • 复制构造函数被定义为弃置的,std::thread 不可复制。两个 std::thread 不可表示一个线程,std::thread 对线程资源是独占所有权。

    thread(const thread&) = delete;
    +
  • 构造新的 std::thread 对象并将它与执行线程关联。表示新的执行线程开始执行

    template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
    +    _NODISCARD_CTOR_THREAD explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
    +        _Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
    +    }
    +
  • `,2),J=n("hr",null,null,-1),W=n("p",null,[s("前三个构造函数都没啥要特别聊的,非常简单,只有第四个构造函数较为复杂,且是我们本章重点,需要详细讲解。("),n("em",null,[s("注意 MSVC 使用标准库的内容很多时候不加 "),n("strong",null,"std::"),s(",脑补一下就行")]),s(")")],-1),Y={href:"https://zh.cppreference.com/w/cpp/language/sfinae",target:"_blank",rel:"noopener noreferrer"},j={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},K=n("code",null,"std::thread",-1),Q={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L72-L87",target:"_blank",rel:"noopener noreferrer"},Z=n("strong",null,[n("code",null,"_Start")],-1),$=t(`
    template <class _Fn, class... _Args>
    +void _Start(_Fn&& _Fx, _Args&&... _Ax) {
    +    using _Tuple                 = tuple<decay_t<_Fn>, decay_t<_Args>...>;
    +    auto _Decay_copied           = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
    +    constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
    +
    +    _Thr._Hnd =
    +        reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
    +
    +    if (_Thr._Hnd) { // ownership transferred to the thread
    +        (void) _Decay_copied.release();
    +    } else { // failed to start thread
    +        _Thr._Id = 0;
    +        _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
    +    }
    +}
    +
    `,1),nn=n("li",null,[n("p",null,[s("它也是一个可变参数成员函数模板,接受一个可调用对象 "),n("code",null,"_Fn"),s(" 和一系列参数 "),n("code",null,"_Args..."),s(" ,这些东西用来创建一个线程。")])],-1),sn=n("p",null,[n("code",null,"using _Tuple = tuple, decay_t<_Args>...>")],-1),an={href:"https://zh.cppreference.com/w/cpp/utility/tuple",target:"_blank",rel:"noopener noreferrer"},en=n("code",null,"_Tuple",-1),tn={href:"https://zh.cppreference.com/w/cpp/types/decay",target:"_blank",rel:"noopener noreferrer"},on=n("code",null,"decay_t",-1),pn=n("p",null,[n("code",null,"auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...)")],-1),cn={href:"https://zh.cppreference.com/w/cpp/memory/unique_ptr/make_unique",target:"_blank",rel:"noopener noreferrer"},ln=n("code",null,"make_unique",-1),rn=n("code",null,"_Tuple",-1),un=n("strong",null,"存储了传入的函数对象和参数的副本",-1),dn=n("p",null,[n("code",null,"constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{})")],-1),_n={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L65-L68",target:"_blank",rel:"noopener noreferrer"},kn=n("code",null,"_Get_invoke",-1),hn=n("code",null,"_Tuple",-1),mn={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L55-L63",target:"_blank",rel:"noopener noreferrer"},vn=n("code",null,"_Invoke",-1),gn=t(`
     template <class _Tuple, size_t... _Indices>
    + _NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept {
    +     return &_Invoke<_Tuple, _Indices...>;
    + }
    + 
    + template <class _Tuple, size_t... _Indices>
    + static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ {
    +     // adapt invoke of user's callable object to _beginthreadex's thread procedure
    +     const unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals));
    +     _Tuple& _Tup = *_FnVals.get(); // avoid ADL, handle incomplete types
    +     _STD invoke(_STD move(_STD get<_Indices>(_Tup))...);
    +     _Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABI
    +     return 0;
    + }
    +

    _Get_invoke 函数很简单,就是接受一个元组类型,和形参包的索引,传递给 _Invoke 静态成员函数模板,实例化,获取它的函数指针。

    `,2),bn={href:"https://en.cppreference.com/w/cpp/utility/integer_sequence",target:"_blank",rel:"noopener noreferrer"},fn=n("code",null,"index_sequence",-1),Tn=n("code",null,"make_index_sequence",-1),yn={href:"https://godbolt.org/z/dv88aPGac",target:"_blank",rel:"noopener noreferrer"},wn=n("p",null,[n("strong",null,"_Invoke 是重中之重,它是线程实际执行的函数"),s(",如你所见它的形参类型是 "),n("code",null,"void*"),s(" ,这是必须的,要符合 "),n("code",null,"_beginthreadex"),s(" 执行函数的类型要求。虽然是 "),n("code",null,"void*"),s(",但是我可以将它转换为 "),n("code",null,"_Tuple*"),s(" 类型,构造一个独占智能指针,然后调用 get() 成员函数获取底层指针,解引用指针,得到元组的引用初始化"),n("code",null,"_Tup"),s(" 。")],-1),xn={href:"https://zh.cppreference.com/w/cpp/utility/functional/invoke",target:"_blank",rel:"noopener noreferrer"},An=n("code",null,"std::invoke",-1),En=n("code",null,"std::move",-1),Sn=n("code",null,"_STD get<_Indices>(_Tup))...",-1),Cn=n("code",null,"std::get<>",-1),In=n("code",null,"_Indices",-1),Dn=n("p",null,[n("code",null,"_Thr._Hnd = reinterpret_cast(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id))")],-1),Ln={href:"https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/beginthread-beginthreadex?view=msvc-170",target:"_blank",rel:"noopener noreferrer"},Bn=n("code",null,"_beginthreadex",-1),zn=n("code",null,"_Thr._Hnd",-1),Fn=n("code",null,"_Invoker_proc",-1),qn=n("strong",null,"_Invoke",-1),Rn=n("code",null,"_Decay_copied.get()",-1),Nn=t("
  • if (_Thr._Hnd) {

    • 如果线程句柄 _Thr._Hnd 不为空,则表示线程已成功启动,将独占指针的所有权转移给线程。
  • (void) _Decay_copied.release()

    • 释放独占指针的所有权,因为已经将参数传递给了线程。
  • } else { // failed to start thread

    • 如果线程启动失败,则进入这个分支
  • _Thr._Id = 0;

    • 将线程ID设置为0。
  • _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);

    • 抛出一个 C++ 错误,表示资源不可用,请再次尝试。
  • ",5),Hn=n("h2",{id:"总结",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#总结"},[n("span",null,"总结")])],-1),Vn=n("code",null,"sizeof(std::thread)",-1),Gn=n("strong",null,"8",-1),On={href:"https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/std_thread.h#L123",target:"_blank",rel:"noopener noreferrer"},Mn=n("code",null,"std::thread::id",-1),Xn={href:"https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/std_thread.h#L81-L85",target:"_blank",rel:"noopener noreferrer"},Un=n("code",null,"win32",-1),Pn=n("code",null,"POSIX",-1),Jn=n("code",null,"_GLIBCXX_HAS_GTHREADS",-1),Wn={href:"https://docs.gtk.org/glib/struct.Thread.html",target:"_blank",rel:"noopener noreferrer"},Yn=t(`
     class thread
    +  {
    +  public:
    +#ifdef _GLIBCXX_HAS_GTHREADS
    +    using native_handle_type = __gthread_t;
    +#else
    +    using native_handle_type = int;
    +#endif
    +

    __gthread_tvoid*

    我们这里的源码解析涉及到的 C++ 技术很多,我们也没办法每一个都单独讲,那会显得文章很冗长,而且也不是重点。

    `,2),jn=n("strong",null,"不会模板,你阅读标准库源码,是无稽之谈",-1),Kn={href:"https://github.com/Mq-b/Modern-Cpp-templates-tutorial",target:"_blank",rel:"noopener noreferrer"},Qn=n("strong",null,"现代C++模板教程",-1);function Zn($n,ns){const a=p("ExternalLinkIcon");return c(),l("div",null,[i,u,n("p",null,[s("我们以 "),d,s(" 实现的 "),n("a",_,[k,e(a)]),s(" 代码进行讲解,MSVC STL 很早之前就不支持 C++11 了,它的实现完全基于 "),h,s(",出于某些原因 "),m,s(" 的一些库(如 "),n("a",v,[g,e(a)]),s(", _v 变量模板)被向后移植到了 "),b,s(" 模式,所以即使是 C++11 标准库设施,实现中可能也是使用到了 C++14、17 的东西。")]),f,T,n("p",null,[y,s(" 只保有一个私有数据成员 "),n("a",w,[x,e(a)]),s(":")]),A,n("p",null,[n("a",E,[S,e(a)]),s(" 是一个结构体,它保有两个数据成员:")]),C,n("p",null,[I,s(" 有四个"),n("a",D,[s("构造函数"),e(a)]),s(",分别是:")]),n("ol",null,[n("li",null,[L,n("p",null,[n("a",B,[s("值初始化"),e(a)]),s("了数据成员 _Thr ,这里的效果相当于给其成员 "),z,s(" 和 "),F,s(" 都进行"),n("a",q,[s("零初始化"),e(a)]),s("。")])]),n("li",null,[R,n("p",null,[n("a",N,[s("_STD"),e(a)]),s(" 是一个宏,展开就是 "),H,s(",也就是 "),n("a",V,[G,e(a)]),s(",将 "),O,s(" 赋为 "),M,s(" (也就是置空),返回 "),X,s(" 的旧值用以初始化当前对象的数据成员 "),U,s("。")])]),P]),J,W,n("p",null,[s("如你所见,这个构造函数本身并没有做什么,它只是一个可变参数成员函数模板,增加了一些 "),n("a",Y,[s("SFINAE"),e(a)]),s(" 进行约束我们传入的"),n("a",j,[s("可调用"),e(a)]),s("对象的类型不能是 "),K,s("。函数体中调用了一个函数 "),n("a",Q,[Z,e(a)]),s(",将我们构造函数的参数全部完美转发,去调用它,这个函数才是我们的重点,如下:")]),$,n("ol",null,[nn,n("li",null,[sn,n("ul",null,[n("li",null,[s("定义了一个"),n("a",an,[s("元组"),e(a)]),s("类型 "),en,s(" ,它包含了可调用对象和参数的类型,这里使用了 "),n("a",tn,[on,e(a)]),s(" 来去除了类型的引用和 cv 限定。")])])]),n("li",null,[pn,n("ul",null,[n("li",null,[s("使用 "),n("a",cn,[ln,e(a)]),s(" 创建了一个独占指针,指向的是 "),rn,s(" 类型的对象,"),un,s("。")])])]),n("li",null,[dn,n("ul",null,[n("li",null,[s("调用 "),n("a",_n,[kn,e(a)]),s(" 函数,传入 "),hn,s(" 类型和一个参数序列的索引序列(为了遍历形参包)。这个函数用于获取一个函数指针,指向了一个静态成员函数 "),n("a",mn,[vn,e(a)]),s(",用来实际执行线程。这两个函数都非常的简单,我们来看看:")])]),gn,n("blockquote",null,[n("p",null,[s("它的形参类型我们不再过多介绍,你只需要知道 "),n("a",bn,[fn,e(a)]),s(" 这个东西可以用来接收一个由 "),Tn,s(" 创建的索引形参包,帮助我们进行遍历元组即可。"),n("a",yn,[s("示例代码"),e(a)]),s("。")])]),wn,n("p",null,[s("此时,我们就可以进行调用了,使用 "),n("a",xn,[An,e(a)]),s(" + "),En,s("(默认移动) ,这里有一个形参包展开,"),Sn,s(",_Tup 就是 std::tuple 的引用,我们使用 "),Cn,s(" 获取元组存储的数据,需要传入一个索引,这里就用到了 "),In,s("。展开之后,就等于 invoke 就接受了我们构造 std::thread 传入的可调用对象,调用可调用对象的参数,invoke 就可以执行了。")])]),n("li",null,[Dn,n("ul",null,[n("li",null,[s("调用 "),n("a",Ln,[Bn,e(a)]),s(" 函数来启动一个线程,并将线程句柄存储到 "),zn,s(" 中。传递给线程的参数为 "),Fn,s("(一个静态函数指针,就是我们前面讲的 "),qn,s(")和 "),Rn,s("(存储了函数对象和参数的副本的指针)。")])])]),Nn]),Hn,n("p",null,[s("需要注意,libstdc++ 和 libc++ 可能不同,就比如它们 64 位环境下 "),Vn,s(" 的结果就可能是 "),Gn,s("。libstdc++ 的实现只"),n("a",On,[s("保有一个 "),Mn,e(a)]),s("。"),n("a",Xn,[s("参见"),e(a)]),s("。不过实测 gcc 不管是 "),Un,s(" 还是 "),Pn,s(" 线程模型,线程对象的大小都是 8,宏 "),Jn,s(" 的值都为 1("),n("a",Wn,[s("GThread"),e(a)]),s(")。")]),Yn,n("p",null,[s("相信你也感受到了,"),jn,s(",市面上很多教程教学,教导一些实现容器,过度简化了,真要去出错了去看标准库的代码,那是不现实的。不需要模板的水平有多高,也不需要会什么元编程,但是基本的需求得能做到,得会,这里推荐一下:"),n("a",Kn,[Qn,e(a)]),s("。")])])}const es=o(r,[["render",Zn],["__file","01thread的构造与源码解析.html.vue"]]),ts=JSON.parse('{"path":"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html","title":"std::thread 的构造-源码解析","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"std::thread 的数据成员","slug":"std-thread-的数据成员","link":"#std-thread-的数据成员","children":[]},{"level":2,"title":"std::thread 的构造函数","slug":"std-thread-的构造函数","link":"#std-thread-的构造函数","children":[]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1710214174000,"updatedTime":1711681670000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":8},{"name":"A. Jiang","email":"de34@live.cn","commits":1}]},"readingTime":{"minutes":7.05,"words":2114},"filePathRelative":"md/详细分析/01thread的构造与源码解析.md","localizedDate":"2024年3月12日","excerpt":"\\n

    我们这单章是为了专门解释一下 C++11 引入的 std::thread 是如何构造的,是如何创建线程传递参数的,让你彻底了解这个类。

    \\n

    我们以 MSVC 实现的 std::thread 代码进行讲解,MSVC STL 很早之前就不支持 C++11 了,它的实现完全基于 C++14,出于某些原因 C++17 的一些库(如 invoke, _v 变量模板)被向后移植到了 C++14 模式,所以即使是 C++11 标准库设施,实现中可能也是使用到了 C++14、17 的东西。

    "}');export{es as comp,ts as data}; diff --git "a/assets/01\345\237\272\346\234\254\346\246\202\345\277\265.html-OWekP6KD.js" "b/assets/01\345\237\272\346\234\254\346\246\202\345\277\265.html-OWekP6KD.js" new file mode 100644 index 00000000..b5510ada --- /dev/null +++ "b/assets/01\345\237\272\346\234\254\346\246\202\345\277\265.html-OWekP6KD.js" @@ -0,0 +1 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,e as l}from"./app-Oub5ASTw.js";const p={},t=l('

    基本概念

    前言

      在我们谈起“并发编程”,其实可以直接简单理解为“多线程编程”,我知道你或许有疑问:“那多进程呢?” C++ 语言层面没有进程的概念,并发支持库也不涉及多进程,所以在本教程中,不用在意。

      我们完全使用标准 C++ 进行教学。

    并发

    并发,指两个或两个以上的独立活动同时发生。

    并发在生活中随处可见,我们可以一边走路一边说话,也可以两只手同时做不同的动作,又或者一边看电视一边吃零食。

    在计算机中的并发

    计算机中的并发有两种方式:

    1. 多核机器的真正并行

    2. 单核机器的任务切换

      在早期,一些单核机器,它要想并发,执行多个任务,那就只能是任务切换,任务切换会给你一种“好像这些任务都在同时执行”的假象。只有硬件上是多核的,才能进行真正的并行,也就是真正的”同时执行任务“。

      在现在,我们日常使用的机器,基本上是二者都有。我们现在的 CPU 基本都是多核,而操作系统调度基本也一样有任务切换,因为要执行的任务非常之多,CPU 是很快的,但是核心却没有那么多,不可能每一个任务都单独给一个核心。大家可以打开自己电脑的任务管理器看一眼,进程至少上百个,线程更是上千。这基本不可能每一个任务分配一个核心,都并行,而且也没必要。正是任务切换使得这些后台任务可以运行,这样系统使用者就可以同时运行文字处理器、编译器、编辑器和 Web 浏览器。

    并发与并行

    事实上,对于这两个术语,并没有非常公认的说法。

    1. 有些人认为二者毫无关系,指代的东西完全不同。

    2. 有些人认为二者大多数时候是相同的,只是用于描述一些东西的时候关注点不同。

    我喜欢第二种,那我们就讲第二种。

    对多线程来说,这两个概念大部分是重叠的。对于很多人来说,它们没有什么区别。 这两个词是用来描述硬件同时执行多个任务的方式:

    这两个术语存在的目的,就是为了区别多线程中不同的关注点。

    总结

      概念从来不是我们的重点,尤其是某些说法准确性也一般,假设开发者对操作系统等知识有基本了解。

      我们也不打算特别介绍什么 C++ 并发库的历史发展、什么时候你该使用多线程、什么时候不该使用多线程... 类似问题应该是看你自己的,而我们回到代码上即可。

    ',22),s=[t];function i(r,h){return n(),a("div",null,s)}const d=e(p,[["render",i],["__file","01基本概念.html.vue"]]),m=JSON.parse('{"path":"/md/01%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5.html","title":"基本概念","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"前言","slug":"前言","link":"#前言","children":[]},{"level":2,"title":"并发","slug":"并发","link":"#并发","children":[]},{"level":2,"title":"在计算机中的并发","slug":"在计算机中的并发","link":"#在计算机中的并发","children":[]},{"level":2,"title":"并发与并行","slug":"并发与并行","link":"#并发与并行","children":[]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1709618654000,"updatedTime":1709886872000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":4}]},"readingTime":{"minutes":2.79,"words":838},"filePathRelative":"md/01基本概念.md","localizedDate":"2024年3月5日","excerpt":"\\n

    前言

    \\n

      在我们谈起“并发编程”,其实可以直接简单理解为“多线程编程”,我知道你或许有疑问:“那多进程呢?” C++ 语言层面没有进程的概念,并发支持库也不涉及多进程,所以在本教程中,不用在意。

    \\n

      我们完全使用标准 C++ 进行教学。

    \\n

    并发

    \\n

    并发,指两个或两个以上的独立活动同时发生。

    \\n

    并发在生活中随处可见,我们可以一边走路一边说话,也可以两只手同时做不同的动作,又或者一边看电视一边吃零食。

    \\n

    在计算机中的并发

    \\n

    计算机中的并发有两种方式:

    "}');export{d as comp,m as data}; diff --git "a/assets/02scoped_lock\346\272\220\347\240\201\350\247\243\346\236\220.html-14rPQ6Fa.js" "b/assets/02scoped_lock\346\272\220\347\240\201\350\247\243\346\236\220.html-14rPQ6Fa.js" new file mode 100644 index 00000000..1c557f6e --- /dev/null +++ "b/assets/02scoped_lock\346\272\220\347\240\201\350\247\243\346\236\220.html-14rPQ6Fa.js" @@ -0,0 +1,88 @@ +import{_ as c}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as u,a as n,b as s,d as a,w as i,e as p}from"./app-Oub5ASTw.js";const k={},d=n("h1",{id:"std-scoped-lock-的源码实现与解析",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#std-scoped-lock-的源码实现与解析"},[n("span",null,[n("code",null,"std::scoped_lock"),s(" 的源码实现与解析")])])],-1),r=n("p",null,[s("本单章专门介绍标准库在 C++17 引入的类模板 "),n("code",null,"std::scoped_lock"),s(" 的实现,让你对它再无疑问。")],-1),m=n("code",null,"std::thread",-1),_=n("strong",null,"不会模板,你阅读标准库源码,是无稽之谈",-1),v={href:"https://mq-b.github.io/Modern-Cpp-templates-tutorial/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/mutex#L476-L528",target:"_blank",rel:"noopener noreferrer"},h=n("code",null,"std::scoped_lock",-1),g={href:"https://github.com/gcc-mirror/gcc/blob/7a01cc711f33530436712a5bfd18f8457a68ea1f/libstdc%2B%2B-v3/include/std/mutex#L743-L802",target:"_blank",rel:"noopener noreferrer"},f=n("code",null,"libstdc++",-1),x={href:"https://github.com/llvm/llvm-project/blob/7ac7d418ac2b16fd44789dcf48e2b5d73de3e715/libcxx/include/mutex#L424-L488",target:"_blank",rel:"noopener noreferrer"},y=n("code",null,"libc++",-1),w=p(`

    std::scoped_lock 的数据成员

    std::scoped_lock 是一个类模板,它有两个特化,也就是有三个版本,其中的数据成员也是不同的。并且它们都不可移动不可拷贝,“管理类”应该如此。

    1. 主模板,是一个可变参数类模板,声明了一个类型形参包 _Mutexes存储了一个 std::tuple,具体类型根据类型形参包决定。

      _EXPORT_STD template <class... _Mutexes>
      +class _NODISCARD_LOCK scoped_lock { // class with destructor that unlocks mutexes
      +public:
      +    explicit scoped_lock(_Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) { // construct and lock
      +        _STD lock(_Mtxes...);
      +    }
      +
      +    explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened
      +        : _MyMutexes(_Mtxes...) {} // construct but don't lock
      +
      +    ~scoped_lock() noexcept {
      +        _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes);
      +    }
      +
      +    scoped_lock(const scoped_lock&)            = delete;
      +    scoped_lock& operator=(const scoped_lock&) = delete;
      +
      +private:
      +    tuple<_Mutexes&...> _MyMutexes;
      +};
      +
    2. 对模板类型形参包只有一个类型情况的偏特化,是不是很熟悉,和 lock_guard 几乎没有任何区别,保有一个互斥量的引用,构造上锁,析构解锁,提供一个额外的构造函数让构造的时候不上锁。所以用 scoped_lock 替代 lock_guard 不会造成任何额外开销。

      template <class _Mutex>
      +class _NODISCARD_LOCK scoped_lock<_Mutex> {
      +public:
      +    using mutex_type = _Mutex;
      +
      +    explicit scoped_lock(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
      +        _MyMutex.lock();
      +    }
      +
      +    explicit scoped_lock(adopt_lock_t, _Mutex& _Mtx) noexcept // strengthened
      +        : _MyMutex(_Mtx) {} // construct but don't lock
      +
      +    ~scoped_lock() noexcept {
      +        _MyMutex.unlock();
      +    }
      +
      +    scoped_lock(const scoped_lock&)            = delete;
      +    scoped_lock& operator=(const scoped_lock&) = delete;
      +
      +private:
      +    _Mutex& _MyMutex;
      +};
      +
    3. 对类型形参包为空的情况的全特化,没有数据成员

      template <>
      +class scoped_lock<> {
      +public:
      +    explicit scoped_lock() = default;
      +    explicit scoped_lock(adopt_lock_t) noexcept /* strengthened */ {}
      +
      +    scoped_lock(const scoped_lock&)            = delete;
      +    scoped_lock& operator=(const scoped_lock&) = delete;
      +};
      +

    std::mutex m1,m2;
    +
    +std::scoped_lock<std::mutex>lc{ m1 };                   // 匹配到偏特化版本  保有一个 std::mutex&
    +std::scoped_lock<std::mutex, std::mutex>lc2{ m1,m2 };   // 匹配到主模板     保有一个 std::tuple<std::mutex&,std::mutex&>
    +std::scoped_lock<> lc3;                                 // 匹配到全特化版本  空
    +

    std::scoped_lock的构造与析构

    在上一节讲 scoped_lock 的数据成员的时候已经把这个模板类的全部源码,三个版本的代码都展示了,就不再重复。

    这三个版本中,只有两个版本需要介绍,也就是

    1. 形参包元素数量为一的偏特化,只管理一个互斥量的。
    2. 主模板,可以管理任意个数的互斥量。

    那这两个的共同点是什么呢?构造上锁,析构解锁。这很明显,明确这一点我们就开始讲吧。


    std::mutex m;
    +void f(){
    +    m.lock();
    +    std::lock_guard<std::mutex> lc{ m, std::adopt_lock };
    +}
    +void f2(){
    +    m.lock();
    +    std::scoped_lock<std::mutex>sp{ std::adopt_lock,m };
    +}
    +

    这段代码为你展示了 std::lock_guardstd::scoped_lock 形参包元素数量为一的偏特化的唯一区别:调用不会上锁的构造函数的参数顺序不同。那么到此也就够了。

    接下来我们进入 std::scoped_lock 主模板的讲解:

    explicit scoped_lock(_Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) { // construct and lock
    +        _STD lock(_Mtxes...);
    +    }
    +
    `,15),M=n("code",null,"_MyMutexes",-1),E={href:"https://zh.cppreference.com/w/cpp/thread/lock",target:"_blank",rel:"noopener noreferrer"},A=n("code",null,"std::lock",-1),C=p(`
    explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened
    +    : _MyMutexes(_Mtxes...) {} // construct but don't lock
    +

    这个构造函数不上锁,只是初始化数据成员 _MyMutexes让它保有这些互斥量的引用。

    ~scoped_lock() noexcept {
    +    _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes);
    +}
    +
    `,3),B={href:"https://zh.cppreference.com/w/cpp/utility/apply",target:"_blank",rel:"noopener noreferrer"},T=n("code",null,"std::apply",-1),L={href:"https://zh.cppreference.com/w/cpp/utility/tuple",target:"_blank",rel:"noopener noreferrer"},q=n("code",null,"std::tuple",-1),S=n("code",null,"std::apply",-1),z=n("code",null,"unlock()",-1),D=n("code",null,"(void)",-1),N={href:"https://zh.cppreference.com/w/cpp/language/expressions#.E5.BC.83.E5.80.BC.E8.A1.A8.E8.BE.BE.E5.BC.8F",target:"_blank",rel:"noopener noreferrer"},V=n("em",null,"弃值表达式",-1),j=n("code",null,"std::thread",-1),F={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/thread#L82",target:"_blank",rel:"noopener noreferrer"},O={href:"https://zh.cppreference.com/w/cpp/thread#.E4.BA.92.E6.96.A5",target:"_blank",rel:"noopener noreferrer"},R=n("code",null,"unlock()",-1),I=n("code",null,"void",-1),K={href:"https://github.com/gcc-mirror/gcc/blob/7a01cc711f33530436712a5bfd18f8457a68ea1f/libstdc%2B%2B-v3/include/std/mutex#L757-L758",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/llvm/llvm-project/blob/7ac7d418ac2b16fd44789dcf48e2b5d73de3e715/libcxx/include/mutex#L472-L475",target:"_blank",rel:"noopener noreferrer"},J=n("code",null,"unlock()",-1),X=n("code",null,"void",-1),Q={href:"https://zh.cppreference.com/w/cpp/named_req/Mutex",target:"_blank",rel:"noopener noreferrer"},Z=n("em",null,"互斥体",-1),G=n("em",null,"(Mutex)",-1),H=n("code",null,"unlock()",-1),U=p(`
    template< class F, class Tuple >
    +constexpr decltype(auto) apply( F&& f, Tuple&& t );
    +
    `,2),W={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},Y=n("em",null,"可调用",-1),$=n("em",null,"(Callable)",-1),nn=n("code",null,"std::thread",-1),sn=p(`
    template<class Callable, class Tuple, std::size_t...index>
    +constexpr decltype(auto) Apply_impl(Callable&& obj,Tuple&& tuple,std::index_sequence<index...>){
    +    return std::invoke(std::forward<Callable>(obj), std::get<index>(std::forward<Tuple>(tuple))...);
    +}
    +
    +template<class Callable, class Tuple>
    +constexpr decltype(auto) apply(Callable&& obj, Tuple&& tuple){
    +    return Apply_impl(std::forward<Callable>(obj), std::forward<Tuple>(tuple),
    +        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
    +}
    +

    其实就是把元组给解包了,利用了 std::index_sequence + std::make_index_sequence 然后就用 std::get 形参包展开用 std::invoke 调用可调用对象即可,非常经典的处理可变参数做法,这个非常重要,一定要会使用。

    举一个简单的调用例子:

    std::tuple<int, std::string, char>tuple{66,"😅",'c'};
    +::apply([](const auto&... t) { ((std::cout << t << ' '), ...); }, tuple);
    +
    `,4),an={href:"https://godbolt.org/z/n4aKo4xbr",target:"_blank",rel:"noopener noreferrer"},tn={href:"https://zh.cppreference.com/w/cpp/language/fold",target:"_blank",rel:"noopener noreferrer"},pn=p('

    总结

    如你所见,其实这很简单。至少使用与了解其设计原理是很简单的。唯一的难度或许只有那点源码,处理可变参数,这会涉及不少模板技术,既常见也通用。还是那句话:“不会模板,你阅读标准库源码,是无稽之谈”。

    相对于 std::thread 的源码解析,std::scoped_lock 还是简单的多。

    ',3);function on(en,cn){const e=o("RouteLink"),t=o("ExternalLinkIcon");return l(),u("div",null,[d,r,n("p",null,[s("这会涉及到不少的模板技术,这没办法,就如同我们先前聊 "),a(e,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html"},{default:i(()=>[m,s(" 的构造与源码分析")]),_:1}),s("最后说的:“"),_,s("”。建议学习"),n("a",v,[s("现代C++模板教程"),a(t)]),s("。")]),n("p",null,[s("我们还是一样的,以 MSVC STL 实现的 "),n("a",b,[h,a(t)]),s(" 代码进行讲解,不用担心,我们也查看了 "),n("a",g,[f,a(t)]),s(" 、"),n("a",x,[y,a(t)]),s("的实现,并没有太多区别,更多的是一些风格上的。而且个人觉得 MSVC 的实现是最简单直观的。")]),w,n("p",null,[s("这个构造函数做了两件事情,初始化数据成员 "),M,s("让它保有这些互斥量的引用,以及给所有互斥量上锁,使用了 "),n("a",E,[A,a(t)]),s(" 帮助我们完成这件事情。")]),C,n("p",null,[s("析构函数就要稍微聊一下了,主要是用 "),n("a",B,[T,a(t)]),s(" 去遍历 "),n("a",L,[q,a(t)]),s(" ,让元组保有的互斥量引用都进行解锁。简单来说是 "),S,s(" 可以将元组存储的参数全部拿出,用于调用这个可变参数的可调用对象,我们就能利用折叠表达式展开形参包并对其调用 "),z,s("。")]),n("blockquote",null,[n("p",null,[s("不在乎其返回类型只用来实施它的副作用,显式转换为 "),D,s(" 也就是"),n("a",N,[V,a(t)]),s("。在我们之前讲的 "),j,s(" 源码中也有这种"),n("a",F,[s("用法"),a(t)]),s("。")]),n("p",null,[s("不过你可能有疑问:“我们的标准库的那些"),n("a",O,[s("互斥量"),a(t)]),s(),R,s(" 返回类型都是 "),I,s(" 呀,为什么要这样?”")]),n("p",null,[s("的确,这是个好问题,"),n("a",K,[s("libstdc++"),a(t)]),s(" 和 "),n("a",P,[s("libc++"),a(t)]),s(" 都没这样做,或许 MSVC STL 想着会有人设计的互斥量让它的 "),J,s(" 返回类型不为 "),X,s(",毕竟 "),n("a",Q,[Z,s(),G,a(t)]),s(" 没有要求 "),H,s(" 的返回类型。")])]),U,n("p",null,[s("这个函数模板接受两个参数,一个"),n("a",W,[Y,s(),$,a(t)]),s("对象 f,以及一个元组 t,用做调用 f 。我们可以自己简单实现一下它,其实不算难,这种遍历元组的方式在之前讲 "),nn,s(" 的源码的时候也提到过。")]),sn,n("blockquote",null,[n("p",null,[n("a",an,[s("运行测试"),a(t)]),s("。")])]),n("p",null,[s("使用了"),n("a",tn,[s("折叠表达式"),a(t)]),s("展开形参包,打印了元组所有的元素。")]),pn])}const kn=c(k,[["render",on],["__file","02scoped_lock源码解析.html.vue"]]),dn=JSON.parse('{"path":"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/02scoped_lock%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html","title":"std::scoped_lock 的源码实现与解析","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"std::scoped_lock 的数据成员","slug":"std-scoped-lock-的数据成员","link":"#std-scoped-lock-的数据成员","children":[]},{"level":2,"title":"std::scoped_lock的构造与析构","slug":"std-scoped-lock的构造与析构","link":"#std-scoped-lock的构造与析构","children":[]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1711004504000,"updatedTime":1711454227000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":6},{"name":"A. Jiang","email":"de34@live.cn","commits":1},{"name":"LeeZQXML","email":"2919625053@qq.com","commits":1}]},"readingTime":{"minutes":5.6,"words":1681},"filePathRelative":"md/详细分析/02scoped_lock源码解析.md","localizedDate":"2024年3月21日","excerpt":"\\n

    本单章专门介绍标准库在 C++17 引入的类模板 std::scoped_lock 的实现,让你对它再无疑问。

    \\n

    这会涉及到不少的模板技术,这没办法,就如同我们先前聊 std::thread 的构造与源码分析最后说的:“不会模板,你阅读标准库源码,是无稽之谈”。建议学习现代C++模板教程

    "}');export{kn as comp,dn as data}; diff --git "a/assets/02\344\275\277\347\224\250\347\272\277\347\250\213.html-Cw8uz5dR.js" "b/assets/02\344\275\277\347\224\250\347\272\277\347\250\213.html-Cw8uz5dR.js" new file mode 100644 index 00000000..66d3d54a --- /dev/null +++ "b/assets/02\344\275\277\347\224\250\347\272\277\347\250\213.html-Cw8uz5dR.js" @@ -0,0 +1,396 @@ +import{_ as c}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as u,a as n,b as s,d as a,w as i,e as p}from"./app-Oub5ASTw.js";const d={},r=n("h1",{id:"使用线程",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#使用线程"},[n("span",null,"使用线程")])],-1),k={href:"https://zh.cppreference.com/w/cpp/thread/thread",target:"_blank",rel:"noopener noreferrer"},v=n("code",null,"std::thread",-1),m=n("code",null,"std::thread",-1),b=p(`

    Hello World

    在我们初学 C++ 的时候应该都写过这样一段代码:

    #include <iostream>
    +
    +int main(){
    +    std::cout << "Hello World!" << std::endl;
    +}
    +
    `,3),h={href:"https://zh.cppreference.com/w/cpp/io/manip/endl",target:"_blank",rel:"noopener noreferrer"},_=p(`

    我们可以启动一个线程来做这件事情:

    #include <iostream>
    +#include <thread>  // 引入线程支持头文件
    +
    +void hello(){     // 定义一个函数用作打印任务
    +    std::cout << "Hello World" << std::endl;
    +}
    +
    +int main(){
    +    std::thread t{ hello };
    +    t.join();
    +}
    +
    `,2),g=n("code",null,"std::thread t{ hello };",-1),f=n("code",null,"t",-1),w=n("code",null,"hello",-1),y={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},E=n("code",null,"hello",-1),j=n("code",null,"t.join();",-1),x=n("code",null,"t",-1),z=n("code",null,"std::thread",-1),B={href:"https://zh.cppreference.com/w/cpp/error/terminate",target:"_blank",rel:"noopener noreferrer"},q=n("code",null,"std::terminate()",-1),A=n("code",null,"t",-1),C=n("code",null,"join()",-1),T={href:"https://zh.cppreference.com/w/cpp/thread/thread/joinable",target:"_blank",rel:"noopener noreferrer"},F=n("code",null,"std::thread::joinable()",-1),I=n("code",null,"false",-1),W=n("code",null,"std::thread",-1),D=n("code",null,"joinable()",-1),P=n("code",null,"true",-1),H=n("code",null,"std::terminate()",-1),M=n("hr",null,null,-1),L=n("p",null,"如你所见,std::thread 高度封装,其成员函数也很少,我们可以轻易的创建线程执行任务,不过,它的用法也还远不止如此,我们慢慢介绍。",-1),R=n("h2",{id:"当前环境支持并发线程数",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#当前环境支持并发线程数"},[n("span",null,"当前环境支持并发线程数")])],-1),S={href:"https://zh.cppreference.com/w/cpp/thread/thread/hardware_concurrency",target:"_blank",rel:"noopener noreferrer"},X=n("code",null,"hardware_concurrency",-1),N=n("code",null,"std::thread",-1),U=p(`
    #include <iostream>
    +#include <thread>
    + 
    +int main(){
    +    unsigned int n = std::thread::hardware_concurrency();
    +    std::cout << "支持 " << n << " 个并发线程。\\n";
    +}
    +
    `,1),V={href:"https://www.intel.cn/content/www/cn/zh/gaming/resources/hyper-threading.html",target:"_blank",rel:"noopener noreferrer"},G=p(`

    英特尔® 超线程技术是一项硬件创新,允许在每个内核上运行多个线程。更多的线程意味着可以并行完成更多的工作。

    AMD 超线程技术被称为 SMT(Simultaneous Multi-Threading),它与英特尔的技术实现有所不同,不过使用类似。

    举个例子:一款 4 核心 8 线程的 CPU,这里的 8 线程其实是指所谓的逻辑处理器,也意味着这颗 CPU 最多可并行执行 8 个任务。

    我们的 hardware_concurrency() 获取的值自然也会是 8

    当然了,都 2024 年了,我们还得考虑一个问题:“ 英特尔从 12 代酷睿开始,为其处理器引入了全新的“大小核”混合设计架构”。

    比如我的 CPU i7 13700H 它是 14 核心,20 线程,有 6 个能效核,6 个性能核。不过我们说了,物理核心这个通常不看重,hardware_concurrency() 输出的值会为 20。


    我们可以举个简单的例子运用这个值:

    template<typename ForwardIt>
    +auto sum(ForwardIt first, ForwardIt last){
    +    using value_type = std::iter_value_t<ForwardIt>;
    +    std::size_t num_threads = std::thread::hardware_concurrency();
    +    std::ptrdiff_t distance = std::distance(first, last);
    +
    +    if(distance > 1024000){
    +        // 计算每个线程处理的元素数量
    +        std::size_t chunk_size = distance / num_threads;
    +        std::size_t remainder = distance % num_threads;
    +
    +        // 存储每个线程的结果
    +        std::vector<value_type>results(num_threads);
    +
    +        // 存储关联线程的线程对象
    +        std::vector<std::thread> threads;
    +
    +        // 创建并启动线程
    +        auto start = first;
    +        for (std::size_t i = 0; i < num_threads; ++i) {
    +            auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0));
    +            threads.emplace_back([start, end, &results, i] {
    +                results[i] = std::accumulate(start, end, value_type{});
    +            });
    +            start = end; // 开始迭代器不断向前
    +        }
    +
    +        // 等待所有线程执行完毕
    +        for (auto& thread : threads)
    +            thread.join();
    +
    +        // 汇总线程的计算结果
    +        value_type total_sum = std::accumulate(results.begin(), results.end(), value_type{});
    +        return total_sum;
    +    }
    +
    +    value_type total_sum = std::accumulate(first, last, value_type{});
    +    return total_sum;
    +}
    +
    `,9),Y={href:"https://godbolt.org/z/8oq3MnvT5",target:"_blank",rel:"noopener noreferrer"},J=n("p",null,[s("我们写了这样一个求和函数 "),n("code",null,"sum"),s(",接受两个迭代器计算它们范围中对象的和。")],-1),K=n("code",null,"value_type",-1),O={href:"https://zh.cppreference.com/w/cpp/iterator/iter_t",target:"_blank",rel:"noopener noreferrer"},Q=n("code",null,"std::iter_value_t",-1),Z={href:"https://zh.cppreference.com/w/cpp/language/function#.E8.BF.94.E5.9B.9E.E7.B1.BB.E5.9E.8B.E6.8E.A8.E5.AF.BC",target:"_blank",rel:"noopener noreferrer"},$=p(`
    template<typename ForwardIt>
    +typename std::iterator_traits<ForwardIt>::value_type sum(ForwardIt first, ForwardIt last);
    +
    `,1),nn={href:"https://godbolt.org/z/4E17nTs4d",target:"_blank",rel:"noopener noreferrer"},sn=n("code",null,"num_threads",-1),an={href:"https://zh.cppreference.com/w/cpp/iterator/distance",target:"_blank",rel:"noopener noreferrer"},tn=n("code",null,"std::distance",-1),pn=n("strong",null,[n("code",null,"1024000")],-1),on={href:"https://zh.cppreference.com/w/cpp/algorithm/accumulate",target:"_blank",rel:"noopener noreferrer"},en=n("code",null,"std::accumulate",-1),cn=p("

    多线程求和只需要介绍三个地方

    1. chunk_size 是每个线程分配的任务,但是这是可能有余数的,比如 10 个任务分配三个线程,必然余 1。但是我们也需要执行这个任务,所以还定义了一个对象 remainder ,它存储的就是余数。

    2. auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0)); 这行代码是获取当前线程的执行范围,其实也就是要 chunk_size 再加上我们的余数 remainder 。这里写了一个三目运算符是为了进行分配任务,比如:

      假设有 3 个线程执行,并且余数是 2。那么,每个线程的处理情况如下:

      • i = 0 时,由于 0 < 2,所以这个线程会多分配一个元素。
      • i = 1 时,同样因为 1 < 2,这个线程也会多分配一个元素。
      • i = 2 时,由于 2 >= 2,所以这个线程只处理平均数量的元素。

      这确保了剩余的 2 个元素被分配给了前两个线程,而第三个线程只处理了平均数量的元素。这样就确保了所有的元素都被正确地分配给了各个线程进行处理。

    3. auto start = first; 在创建线程执行之前先定义了一个开始迭代器。在传递给线程执行的lambda表达式中,最后一行是:start = end; 这是为了让迭代器一直向前。

    由于求和不涉及数据竞争之类的问题,所以我们甚至可以在刚讲完 Hello World 就手搓了一个“并行求和”的简单的模板函数。主要的难度其实在于对 C++ 的熟悉程度,而非对线程类 std::thread 的使用了,这里反而是最简单的,无非是用容器存储线程对象管理,最后进行 join() 罢了。

    ",3),ln={href:"https://zh.cppreference.com/w/cpp/algorithm/reduce",target:"_blank",rel:"noopener noreferrer"},un=n("code",null,"std::reduce",-1),dn={href:"https://zh.cppreference.com/w/cpp/algorithm/execution_policy_tag",target:"_blank",rel:"noopener noreferrer"},rn=n("code",null,"sum",-1),kn=p(`

    线程管理

    在 C++ 标准库中,只能管理与 std::thread 关联的线程,类 std::thread 的对象就是指代线程的对象,我们说“线程管理”,其实也就是管理 std::thread 对象。

    启动新线程

    使用 C++ 线程库启动线程,就是构造 std::thread 对象。

    当然了,如果是默认构造之类的,那么 std::thread 线程对象没有关联线程的,自然也不会启动线程执行任务。

    std::thread t; //  构造不表示线程的新 std::thread 对象
    +
    `,5),vn=n("code",null,"std::thread",-1),mn=n("code",null,"std::thread",-1),bn={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},hn=n("code",null,"operator()",-1),_n=p(`
    class Task{
    +public:
    +    void operator()()const {
    +        std::cout << "operator()()const\\n";
    +    }
    +};
    +

    我们显然没办法直接像函数使用函数名一样,使用“类名”,函数名可以隐式转换到指向它的函数指针,而类名可不会直接变成对象,我们想使用 Task 自然就得构造对象了

    std::thread t{ Task{} };
    +t.join();
    +

    直接创建临时对象即可,可以简化代码并避免引入不必要的局部对象。

    不过有件事情需要注意,当我们使用函数对象用于构造 std::thread 的时候,如果你传入的是一个临时对象,且使用的都是 “()”小括号初始化,那么编译器会将此语法解析为函数声明

    std::thread t( Task() ); // 函数声明
    +

    这被编译器解析为函数声明,是一个返回类型为 std::thread,函数名为 t,接受一个返回 Task 的空参的函数指针类型,也就是 Task(*)()

    之所以我们看着抽象是因为这里的形参是无名的,且写了个函数类型。

    我们用一个简单的示例为你展示:

    void h(int(int));         //#1 声明
    +void h(int (*p)(int)){}   //#2 定义
    +

    即使我还没有为你讲述概念,我相信你也发现了,#1 和 #2 的区别无非是,#1 省略了形参的名称,还有它的形参是函数类型而不是函数指针类型,没有 *

    `,11),gn=n("strong",null,"函数类型 T 的形参会调整为具有类型“指向 T 的指针”",-1),fn={href:"https://zh.cppreference.com/w/cpp/language/function#.E5.BD.A2.E5.8F.82.E7.B1.BB.E5.9E.8B.E5.88.97.E8.A1.A8",target:"_blank",rel:"noopener noreferrer"},wn=p(`

    显然,int(int) 是一个函数类型,它被调整为了一个指向这个函数类型的指针类型。

    那么回到我们最初的:

    std::thread t( Task() );                    // #1 函数声明
    +std::thread t( Task (*p)() ){ return {}; }  // #2 函数定义
    +

    #2我们写出了函数形参名称 p,再将函数类型写成函数指针类型,事实上完全等价。我相信,这样,也就足够了。

    所以总而言之,建议使用 {} 进行初始化,这是好习惯,大多数时候它是合适的。

    `,5),yn=n("code",null,"std::thread",-1),En={href:"https://cppinsights.io/s/c448ad3d",target:"_blank",rel:"noopener noreferrer"},jn={href:"https://zh.cppreference.com/w/cpp/language/lambda#:~:text=lambda%20%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%98%AF%E7%BA%AF%E5%8F%B3%E5%80%BC%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%8C%E5%AE%83%E7%9A%84%E7%B1%BB%E5%9E%8B%E6%98%AF%E7%8B%AC%E6%9C%89%E7%9A%84%E6%97%A0%E5%90%8D%E9%9D%9E%E8%81%94%E5%90%88%E4%BD%93%E9%9D%9E%E8%81%9A%E5%90%88%E4%BD%93%E9%9D%9E%E7%BB%93%E6%9E%84%E5%8C%96%E7%B1%BB%E7%B1%BB%E5%9E%8B%EF%BC%8C%E8%A2%AB%E7%A7%B0%E4%B8%BA%E9%97%AD%E5%8C%85%E7%B1%BB%E5%9E%8B",target:"_blank",rel:"noopener noreferrer"},xn=p(`
    #include <iostream>
    +#include <thread>
    +
    +int main(){
    +    std::thread thread{ [] {std::cout << "Hello World!\\n"; } };
    +    thread.join();
    +}
    +

    `,2),zn=n("code",null,"std::thread",-1),Bn={href:"https://zh.cppreference.com/w/cpp/thread/thread/%7Ethread",target:"_blank",rel:"noopener noreferrer"},qn=n("code",null,"std::thread::~thread",-1),An={href:"https://zh.cppreference.com/w/cpp/thread/thread/join",target:"_blank",rel:"noopener noreferrer"},Cn=n("code",null,"join()",-1),Tn={href:"https://zh.cppreference.com/w/cpp/thread/thread/detach",target:"_blank",rel:"noopener noreferrer"},Fn=n("code",null,"detach()",-1),In=n("p",null,[s("我们先前使用的就是 join(),我们聊一下 "),n("strong",null,"detach()"),s(",当 "),n("code",null,"std::thread"),s(" 线程对象调用了 detach(),那么就是线程对象放弃了对线程资源的所有权,不再管理此线程,允许此线程独立的运行,在线程退出时释放所有分配的资源。")],-1),Wn=n("p",null,[s("放弃了对线程资源的所有权,也就是线程对象没有关联活跃线程了,此时 joinable 为 "),n("strong",null,[n("code",null,"false")]),s("。")],-1),Dn={href:"https://zh.cppreference.com/w/cpp/language/ub",target:"_blank",rel:"noopener noreferrer"},Pn=p(`

    比如函数结束,那么函数局部对象的生存期都已经结束了,都被销毁了,此时线程函数还持有函数局部对象的指针或引用。

    #include <iostream>
    +#include <thread>
    +
    +struct func {
    +    int& m_i;
    +    func(int& i) :m_i{ i } {}
    +    void operator()(int n)const {
    +        for (int i = 0; i <= n; ++i) {
    +            m_i += i;           // 可能悬空引用
    +        }
    +    }
    +};
    +
    +int main(){
    +    int n = 0;
    +    std::thread my_thread{ func{n},100 };
    +    my_thread.detach();        // 分离,不等待线程结束
    +}                              // 分离的线程可能还在运行
    +
    1. 主线程(main)创建局部对象 n、创建线程对象 my_thread 启动线程,执行任务 func{n},局部对象 n 的引用被子线程持有。传入 100 用于调用 func 的 operator(int)。

    2. my_thread.detach();,joinable() 为 false。线程分离,线程对象不再持有线程资源,线程独立的运行。

    3. 主线程不等待,此时分离的子线程可能没有执行完毕,但是主线程(main)已经结束,局部对象 n 生存期结束,被销毁,而此时子线程还持有它的引用,访问悬空引用,造成未定义行为。my_thread 已经没有关联线程资源,正常析构,没有问题。

    解决方法很简单,将 detach() 替换为 join()。

    通常非常不推荐使用 detach(),因为程序员必须确保所有创建的线程正常退出,释放所有获取的资源并执行其它必要的清理操作。这意味着通过调用 detach() 放弃线程的所有权不是一种选择,因此 join 应该在所有场景中使用。 一些老式特殊情况不聊。

    另外提示一下,也不要想着 detach() 之后,再次调用 join()

    my_thread.detach();
    +// todo..
    +my_thread.join();
    +// 函数结束
    +

    认为这样可以确保被分离的线程在这里阻塞执行完?

    我们前面聊的很清楚了,detach() 是线程分离,线程对象放弃了线程资源的所有权,此时我们的 my_thread 它现在根本没有关联任何线程。调用 join() 是:“阻塞当前线程直至 *this 所标识的线程结束其执行”,我们的线程对象都没有线程,堵塞什么?执行什么呢?

    简单点说,必须是 std::thread 的 joinable() 为 true 即线程对象有活跃线程,才能调用 join() 和 detach()。

    顺带的,我们还得处理线程运行后的异常问题,举个例子:你在一个函数中构造了一个 std::thread 对象,线程开始执行,函数继续执行下面别的代码,但是如果抛出了异常呢?下面我的 join() 就会被跳过

    std::thread my_thread{func{n},10};
    +//todo.. 抛出异常的代码
    +my_thread.join();
    +

    避免程序被抛出的异常所终止,在异常处理过程中调用 join(),从而避免线程对象析构产生问题。

    struct func; // 复用之前
    +void f(){
    +    int n = 0;
    +    std::thread t{ func{n},10 };
    +    try{
    +        // todo.. 一些当前线程可能抛出异常的代码
    +        f2();
    +    }
    +    catch (...){
    +        t.join(); // 1
    +        throw;
    +    }
    +    t.join();    // 2
    +}
    +

    我知道你可能有很多疑问,我们既然 catch 接住了异常,为什么还要 throw?以及为什么我们要两个 join()?

    这两个问题其实也算一个问题,如果代码里抛出了异常,就会跳转到 catch 的代码中,执行 join() 确保线程正常执行完成,线程对象可以正常析构。然而此时我们必须再次 throw 抛出异常,因为你要是不抛出,那么你不是还得执行一个 t.join()?显然逻辑不对,自然抛出。

    `,16),Hn=n("strong",null,"函数产生的异常,由调用方进行处理",-1),Mn={href:"https://godbolt.org/z/jo5sPvPGE",target:"_blank",rel:"noopener noreferrer"},Ln=p(`

    我知道你可能会想到:“我在 try 块中最后一行写一个 t.join() ,这样如果前面的代码没有抛出异常,就能正常的调用 join(),如果抛出了异常,那就调用 catch 中的 t.join() 根本不需要最外部 2 那里的 join(),也不需要再次 throw 抛出异常”

    void f(){
    +    int n = 0;
    +    std::thread t{ func{n},10 };
    +    try{
    +        // todo.. 一些当前线程可能抛出异常的代码
    +        f2();
    +        t.join(); // try 最后一行调用 join()
    +    }
    +    catch (...){
    +        t.join(); // 如果抛出异常,就在 这里调用 join()
    +    }
    +}
    +
    `,2),Rn={href:"https://godbolt.org/z/Wo7Tj95Tz",target:"_blank",rel:"noopener noreferrer"},Sn=n("p",null,[n("strong",null,"但是这是不对的"),s(",你要注意我们的注释:“"),n("strong",null,"一些当前线程可能抛出异常的代码"),s("”,而不是 "),n("code",null,"f2()"),s(",我们的 "),n("code",null,"try"),s(),n("code",null,"catch"),s(" 只是为了让线程对象关联的线程得以正确执行完毕,以及线程对象正确析构。并没有处理什么其他的东西,不掩盖错误,try"),n("code",null,"块中的代码抛出了异常,"),s("catch` 接住了,我们理所应当再次抛出。")],-1),Xn=n("h3",{id:"raii",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#raii"},[n("span",null,"RAII")])],-1),Nn={href:"https://zh.cppreference.com/w/cpp/language/raii",target:"_blank",rel:"noopener noreferrer"},Un=p(`

    简单的说是:构造函数申请资源,析构函数释放资源,让对象的生命周期和资源绑定。当异常抛出时,C++ 会自动调用栈上所有对象的析构函数。

    我们可以提供一个类,在析构函数中使用 join() 确保线程执行完成,线程对象正常析构。

    class thread_guard{
    +    std::thread& m_t;
    +public:
    +    explicit thread_guard(std::thread& t) :m_t{ t } {}
    +    ~thread_guard(){
    +        std::puts("析构");     // 打印 不用在乎
    +        if (m_t.joinable()) { // 没有关联活跃线程
    +            m_t.join();
    +        }
    +    }
    +    thread_guard(const thread_guard&) = delete;
    +    thread_guard& operator=(const thread_guard&) = delete;
    +};
    +void f(){
    +    int n = 0;
    +    std::thread t{ func{n},10 };
    +    thread_guard g(t);
    +    f2(); // 可能抛出异常
    +}
    +
    `,3),Vn=n("strong",null,"调用析构函数",-1),Gn=n("strong",null,"即使函数 f2() 抛出了一个异常,这个销毁依然会发生(前提是你捕获了这个异常)",-1),Yn={href:"https://godbolt.org/z/MaWjW73P4",target:"_blank",rel:"noopener noreferrer"},Jn={href:"https://zh.cppreference.com/w/cpp/error/terminate",target:"_blank",rel:"noopener noreferrer"},Kn=n("strong",null,"实现定义",-1),On=n("p",null,"我们的测试代码是捕获了异常的,为了观测,看到它一定打印“析构”。",-1),Qn=n("p",null,[s("在 thread_guard 的析构函数中,我们要判断 "),n("code",null,"std::thread"),s(" 线程对象现在是否有关联的活跃线程,如果有,我们才会执行 "),n("strong",null,[n("code",null,"join()")]),s(",阻塞当前线程直到线程对象关联的线程执行完毕。如果不想等待线程结束可以使用 "),n("code",null,"detach()"),s(" ,但是这让 "),n("code",null,"std::thread"),s(" 对象失去了线程资源的所有权,难以掌控,具体如何,看情况分析。")],-1),Zn=n("code",null,"=delete",-1),$n={href:"https://zh.cppreference.com/w/cpp/language/rule_of_three#.E4.BA.94.E4.B9.8B.E6.B3.95.E5.88.99",target:"_blank",rel:"noopener noreferrer"},ns=n("strong",null,"阻止",-1),ss=p(`

    不允许这些操作主要在于:这是个管理类,而且顾名思义,它就应该只是单纯的管理线程对象仅此而已,只保有一个引用,单纯的做好 RAII 的事情就行,允许其他操作没有价值。

    严格来说其实这里倒也不算 RAII,因为 thread_guard 的构造函数其实并没有申请资源,只是保有了线程对象的引用,在析构的时候进行了 join() 。

    传递参数

    向可调用对象或函数传递参数很简单,我们前面也都写了,只需要将这些参数作为 std::thread 的构造参数即可。需要注意的是,这些参数会拷贝到新线程的内存空间中,即使函数中的参数是引用,依然实际是拷贝

    void f(int, const int& a);
    +
    +int n = 1;
    +std::thread t(f, 3, n);
    +

    线程对象 t 的构造没有问题,可以通过编译,但是这个 n 实际上并没有按引用传递,而是拷贝了,我们可以打印地址来验证我们的猜想。

    void f(int, const int& a) { // a 并非引用了局部对象 n
    +    std::cout << &a << '\\n'; 
    +}
    +
    +int main() {
    +    int n = 1;
    +    std::cout << &n << '\\n';
    +    std::thread t(f, 3, n);
    +    t.join();
    +}
    +
    `,7),as={href:"https://godbolt.org/z/TzWeW5rxh",target:"_blank",rel:"noopener noreferrer"},ts=n("strong",null,"const 的引用",-1),ps={href:"https://godbolt.org/z/3nMb4asnG",target:"_blank",rel:"noopener noreferrer"},os={href:"https://zh.cppreference.com/w/cpp/utility/functional/ref",target:"_blank",rel:"noopener noreferrer"},es=n("code",null,"std::ref",-1),cs=n("code",null,"std::cref",-1),ls=p(`
    void f(int, int& a) {
    +    std::cout << &a << '\\n'; 
    +}
    +
    +int main() {
    +    int n = 1;
    +    std::cout << &n << '\\n';
    +    std::thread t(f, 3, std::ref(n));
    +    t.join();
    +}
    +
    `,1),us={href:"https://godbolt.org/z/hTP3ex4W7",target:"_blank",rel:"noopener noreferrer"},is=n("strong",null,"ref",-1),ds=n("strong",null,"reference",-1),rs={href:"https://zh.cppreference.com/w/cpp/utility/functional/reference_wrapper",target:"_blank",rel:"noopener noreferrer"},ks=n("code",null,"std::reference_wrapper",-1),vs=p(`

    cref”呢?,这个“c”就是“const”,就是返回了 std::reference_wrapper<const T>。我们不详细介绍他们的实现,你简单认为reference_wrapper可以隐式转换为被包装对象的引用即可,

    int n = 0;
    +std::reference_wrapper<int> r = std::ref(n);
    +int& p = r; // r 隐式转换为 n 的引用 此时 p 引用的就是 n
    +
    int n = 0;
    +std::reference_wrapper<const int> r = std::cref(n);
    +const int& p = r; // r 隐式转换为 n 的 const 的引用 此时 p 引用的就是 n
    +
    `,3),ms={href:"https://www.bilibili.com/video/BV1np4y1j78L",target:"_blank",rel:"noopener noreferrer"},bs=n("hr",null,null,-1),hs=n("code",null,"void f(int, int&)",-1),_s=n("code",null,"std::ref",-1),gs=n("code",null,"void f(int, const int&)",-1),fs={href:"https://godbolt.org/z/xhrhs6Ke5",target:"_blank",rel:"noopener noreferrer"},ws=n("strong",null,"编译错误",-1),ys=n("code",null,"std::thread",-1),Es=n("strong",null,"右值表达式进行传递",-1),js=n("strong",null,"只支持移动的类型",-1),xs=p(`
    struct move_only {
    +    move_only() { std::puts("默认构造"); }
    +    move_only(const move_only&) = delete;
    +    move_only(move_only&&)noexcept {
    +        std::puts("移动构造");
    +    }
    +};
    +
    +void f(move_only){}
    +
    +int main(){
    +    move_only obj;
    +    std::thread t{ f,std::move(obj) };
    +    t.join();
    +}
    +
    `,1),zs={href:"https://godbolt.org/z/b6fYWaf3Y",target:"_blank",rel:"noopener noreferrer"},Bs=n("p",null,[s("没有 "),n("code",null,"std::ref"),s(" 自然是会保有一个副本,所以有两次移动构造,一次是被 "),n("code",null,"std::thread"),s(" 构造函数中初始化副本,一次是调用函数 "),n("code",null,"f"),s("。")],-1),qs=n("p",null,[s("如果还有不理解,不用担心,记住,这一切的问题都会在后面的 "),n("a",{href:"#stdthread-%E7%9A%84%E6%9E%84%E9%80%A0-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90"},[n("code",null,"std::thread"),s(" 的构造-源码解析")]),s(" 解释清楚。")],-1),As=n("hr",null,null,-1),Cs={href:"https://zh.cppreference.com/w/cpp/language/pointer#.E6.88.90.E5.91.98.E5.87.BD.E6.95.B0.E6.8C.87.E9.92.88",target:"_blank",rel:"noopener noreferrer"},Ts=n("strong",null,"成员函数指针",-1),Fs={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},Is=n("em",null,"可调用",-1),Ws=n("em",null,"Callable",-1),Ds=n("code",null,"std::thread",-1),Ps=p(`
    struct X{
    +    void task_run(int)const;
    +};
    +
    + X x;
    + int n = 0;
    + std::thread t{ &X::task_run,&x,n };
    + t.join();
    +

    传入成员函数指针、与其配合使用的对象、调用成员函数的参数,构造线程对象 t,启动线程。

    如果你是第一次见到成员指针,那么我们稍微聊一下,&X::task_run 是一个整体,它们构成了成员指针,&类名::非静态成员

    成员指针必须和对象一起使用,这是唯一标准用法,成员指针不可以转换到函数指针单独使用,即使是非静态成员函数没有使用任何数据成员。

    `,4),Hs={href:"https://zh.cppreference.com/w/cpp/utility/functional/bind",target:"_blank",rel:"noopener noreferrer"},Ms=n("code",null,"std::bind",-1),Ls=p(`
    std::thread t{ std::bind(&X::task_run, &x ,n) };
    +
    `,1),Rs=n("code",null,"std::bind",-1),Ss={href:"https://godbolt.org/z/c5bh8Easd",target:"_blank",rel:"noopener noreferrer"},Xs=n("strong",null,"拷贝",-1),Ns=p(`
    struct X {
    +    void task_run(int& a)const{
    +        std::cout << &a << '\\n';
    +    }
    +};
    +
    +X x;
    +int n = 0;
    +std::cout << &n << '\\n';
    +std::thread t{ std::bind(&X::task_run,&x,n) };
    +t.join();
    +
    `,1),Us=n("code",null,"n",-1),Vs=n("code",null,"std::ref",-1),Gs={href:"https://godbolt.org/z/P9Gn5b66W",target:"_blank",rel:"noopener noreferrer"},Ys=p(`
    std::thread t{ std::bind(&X::task_run,&x,std::ref(n)) };
    +

    void f(const std::string&);
    +std::thread t{ f,"hello" };
    +

    代码创建了一个调用 f("hello") 的线程。注意,函数 f 实际需要的是一个 std::string 类型的对象作为参数,但这里使用的是字符串字面量,我们要明白“A的引用只能引用A,或者以任何形式转换到A”,字符串字面量的类型是 const char[N] ,它会退化成指向它的const char* 指针,被线程对象保存。在调用 f 的时候,这个指针可以通过 std::string 的转换构造函数,构造出一个临时的 std::string 对象,就能成功调用。

    `,4),Js={href:"https://zh.cppreference.com/w/cpp/language/storage_duration#.E5.AD.98.E5.82.A8.E6.9C.9F",target:"_blank",rel:"noopener noreferrer"},Ks=n("em",null,"存储期",-1),Os=p(`
    void f(const std::string&);
    +void test(){
    +    char buffer[1024]{};
    +    //todo.. code
    +    std::thread t{ f,buffer };
    +    t.detach();
    +}
    +
    `,1),Qs=n("code",null,"std::thread",-1),Zs={href:"https://zh.cppreference.com/w/cpp/standard_library/decay-copy",target:"_blank",rel:"noopener noreferrer"},$s=n("em",null,[n("code",null,"decay-copy")],-1),na=n("strong",null,"隐式转换为了指向这个数组的指针",-1),sa=n("p",null,[s("我们要特别强调,"),n("code",null,"std::thread"),s(" 构造是代表“启动线程”,而不是调用我们传递的可调用对象。")],-1),aa=n("code",null,"std::thread",-1),ta={href:"https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/beginthread-beginthreadex?view=msvc-170",target:"_blank",rel:"noopener noreferrer"},pa=n("code",null,"_beginthreadex",-1),oa=n("code",null,"f",-1),ea=n("code",null,"f(buffer)",-1),ca=n("code",null,"std::thread",-1),la=n("code",null,"test",-1),ua=n("code",null,"f(buffer)",-1),ia=n("code",null,"std::string",-1),da=n("strong",null,"悬空",-1),ra=p(`
    1. detach() 替换为 join()

      void f(const std::string&);
      +void test(){
      +    char buffer[1024]{};
      +    //todo.. code
      +    std::thread t{ f,buffer };
      +    t.join();
      +}
      +
    2. 显式将 buffer 转换为 std::string

      void f(const std::string&);
      +void test(){
      +    char buffer[1024]{};
      +    //todo.. code
      +    std::thread t{ f,std::string(buffer) };
      +    t.detach();
      +}
      +

    std::this_thread

    这个命名空间包含了管理当前线程的函数。

    `,3),ka={href:"https://zh.cppreference.com/w/cpp/thread/yield",target:"_blank",rel:"noopener noreferrer"},va=n("code",null,"yield",-1),ma={href:"https://zh.cppreference.com/w/cpp/thread/get_id",target:"_blank",rel:"noopener noreferrer"},ba=n("code",null,"get_id",-1),ha={href:"https://zh.cppreference.com/w/cpp/thread/sleep_for",target:"_blank",rel:"noopener noreferrer"},_a=n("code",null,"sleep_for",-1),ga={href:"https://zh.cppreference.com/w/cpp/thread/sleep_until",target:"_blank",rel:"noopener noreferrer"},fa=n("code",null,"sleep_until",-1),wa=n("strong",null,"停止到",-1),ya=n("p",null,[s("它们之中最常用的是 "),n("code",null,"get_id"),s(",其次是 "),n("code",null,"sleep_for"),s(",再然后 "),n("code",null,"yield"),s(","),n("code",null,"sleep_until"),s(" 较少。")],-1),Ea=n("code",null,"get_id",-1),ja={href:"https://godbolt.org/z/fPcaj7xTv",target:"_blank",rel:"noopener noreferrer"},xa=p(`
    int main() {
    +    std::cout << std::this_thread::get_id() << '\\n';
    +
    +    std::thread t{ [] {
    +        std::cout << std::this_thread::get_id() << '\\n';
    +    } };
    +    t.join();
    +}
    +
    `,1),za=p(`

    使用 sleep_for 延时。当 Sleep 之类的就行,但是它需要接受的参数不同,是 std::chrono 命名空间中的时间对象。

    int main() {
    +    std::this_thread::sleep_for(std::chrono::seconds(3));
    +}
    +
    `,2),Ba=n("code",null,"seconds",-1),qa={href:"https://zh.cppreference.com/w/cpp/chrono/duration",target:"_blank",rel:"noopener noreferrer"},Aa=n("code",null,"std::chrono::duration",-1),Ca={href:"https://zh.cppreference.com/w/cpp/symbol_index/chrono_literals",target:"_blank",rel:"noopener noreferrer"},Ta={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/__msvc_chrono.hpp#L718-L780",target:"_blank",rel:"noopener noreferrer"},Fa=n("code",null,"std::chrono_literals",-1),Ia=p(`
    using namespace std::chrono_literals;
    +
    +int main() {
    +    std::this_thread::sleep_for(3s);
    +}
    +

    简单直观。

    `,2),Wa=p(`
  • yield 减少 CPU 的占用。

    while (!isDone()){
    +    std::this_thread::yield();
    +}
    +

    线程需要等待某个操作完成,如果你直接用一个循环不断判断这个操作是否完成就会使得这个线程占满 CPU 时间,这会造成资源浪费。此时可以判断操作是否完成,如果还没完成就调用 yield 交出 CPU 时间片让其他线程执行,过一会儿再来判断是否完成,这样这个线程占用 CPU 时间会大大减少。

  • `,1),Da=p(`

    使用 sleep_until 让当前线程延迟到具体的时间。我们延时 5 秒就是。

    int main() {
    +    // 获取当前时间点
    +    auto now = std::chrono::system_clock::now();
    +
    +    // 设置要等待的时间点为当前时间点之后的5秒
    +    auto wakeup_time = now + 5s;
    +
    +    // 输出当前时间
    +    auto now_time = std::chrono::system_clock::to_time_t(now);
    +    std::cout << "Current time:\\t\\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S") << std::endl;
    +
    +    // 输出等待的时间点
    +    auto wakeup_time_time = std::chrono::system_clock::to_time_t(wakeup_time);
    +    std::cout << "Waiting until:\\t\\t" << std::put_time(std::localtime(&wakeup_time_time), "%H:%M:%S") << std::endl;
    +
    +    // 等待到指定的时间点
    +    std::this_thread::sleep_until(wakeup_time);
    +
    +    // 输出等待结束后的时间
    +    now = std::chrono::system_clock::now();
    +    now_time = std::chrono::system_clock::to_time_t(now);
    +    std::cout << "Time after waiting:\\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S") << std::endl;
    +}
    +
    `,2),Pa=n("code",null,"sleep_until",-1),Ha={href:"https://godbolt.org/z/4qYGbcvYW",target:"_blank",rel:"noopener noreferrer"},Ma=p(`

    介绍了一下 std::this_thread 命名空间中的四个函数的基本用法,我们后续会经常看到这些函数的使用,不用着急。

    std::thread 转移所有权

    传入可调用对象以及参数,构造 std::thread 对象,启动线程,而线程对象拥有了线程的所有权,线程是一种系统资源,所以可称作“线程资源”。

    std::thread 不可复制。两个 std::thread 不可表示一个线程,std::thread 对线程资源是独占所有权。移动就是转移它的线程资源的所有权给别的 std::thread 对象。

    int main() {
    +    std::thread t{ [] {
    +        std::cout << std::this_thread::get_id() << '\\n';
    +    } };
    +    std::cout << t.joinable() << '\\n'; // 线程对象 t 当前关联了活跃线程 打印 1
    +    std::thread t2{ std::move(t) };    // 将 t 的线程资源的所有权移交给 t2
    +    std::cout << t.joinable() << '\\n'; // 线程对象 t 当前没有关联活跃线程 打印 0
    +    //t.join(); // Error! t 没有线程资源
    +    t2.join();  // t2 当前持有线程资源
    +}
    +

    这段代码通过移动构造转移了线程对象 t 的线程资源所有权到 t2,这里虽然有两个 std::thread 对象,但是从始至终只有一个线程资源,让持有线程资源的 t2 对象最后调用 join() 堵塞让其线程执行完毕。tt2 都能正常析构。

    我们还可以使用移动赋值来转移线程资源的所有权:

    int main() {
    +    std::thread t;      // 默认构造,没有关联活跃线程
    +    std::cout << t.joinable() << '\\n'; // 0
    +    std::thread t2{ [] {} };
    +    t = std::move(t2); // 转移线程资源的所有权到 t
    +    std::cout << t.joinable() << '\\n'; // 1
    +    t.join();
    +    
    +    t2 = std::thread([] {});
    +    t2.join();
    +}
    +

    我们只需要介绍 t2 = std::thread([] {}) ,临时对象是右值表达式,不用调用 std::move,这里相当于是将临时的 std::thread 对象所持有的线程资源转移给 t2t2 再调用 join() 正常析构。

    函数返回 std::thread 对象:

    std::thread f(){
    +    std::thread t{ [] {} };
    +    return t;
    +}
    +
    +int main(){
    +    std::thread rt = f();
    +    rt.join();
    +}
    +
    `,11),La={href:"https://godbolt.org/z/14d7b9qn9",target:"_blank",rel:"noopener noreferrer"},Ra=n("code",null,"std::thread",-1),Sa=p(`

    这里的 return t 重载决议[1]选择到了移动构造,将 t 线程资源的所有权转移给函数调用 f() 返回的临时 std::thread 对象中,然后这个临时对象再用来初始化 rt ,临时对象是右值表达式,这里一样选择到移动构造,将临时对象的线程资源所有权移交给 rt。此时 rt 具有线程资源的所有权,由它调用 join() 正常析构。

    如果标准达到 C++17,RVO 保证这里少一次移动构造的开销(临时对象初始化 rt 的这次)。

    所有权也可以在函数内部传递

    void f(std::thread t){
    +    t.join();
    +}
    +
    +int main(){
    +    std::thread t{ [] {} };
    +    f(std::move(t));
    +    f(std::thread{ [] {} });
    +}
    +

    std::move 将 t 转换为了一个右值表达式,初始化函数f 形参 t,选择到了移动构造转移线程资源的所有权,在函数中调用 t.join() 后正常析构。std::thread{ [] {} } 构造了一个临时对象,本身就是右值表达式,初始化函数f 形参 t,移动构造转移线程资源的所有权到 tt.join() 后正常析构。

    本节内容总体来说是很简单的,如果你有些地方无法理解,那只有一种可能,“对移动语义不了解”,不过这也不是问题,在后续我们详细介绍 std::thread 构造函数的源码即可,不用着急。

    `,6),Xa={id:"std-thread-的构造-源码解析",tabindex:"-1"},Na={class:"header-anchor",href:"#std-thread-的构造-源码解析"},Ua=n("code",null,"std::thread",-1),Va=p(`

    我们上一个大节讲解了线程管理,也就是 std::thread 的管理,其中的重中之重就是它的构造,传递参数。我们用源码实现为各位从头讲解。

    了解其实现,才能更好的使用它。

    实现 joining_thread

    这个类和 std::thread 的区别就是析构函数会自动 join 。如果您好好的学习了上一节的内容,阅读了 std::thread 的源码,以下内容不会对您构成任何的难度。

    我们存储一个 std::thread 作为底层数据成员,稍微注意一下构造函数和赋值运算符的实现即可。

    class joining_thread {
    +    std::thread t;
    +public:
    +    joining_thread()noexcept = default;
    +    template<typename Callable, typename... Args>
    +    explicit joining_thread(Callable&& func, Args&&...args) :
    +        t{ std::forward<Callable>(func), std::forward<Args>(args)... } {}
    +    explicit joining_thread(std::thread t_)noexcept : t{ std::move(t_) } {}
    +    joining_thread(joining_thread&& other)noexcept : t{ std::move(other.t) } {}
    +
    +    joining_thread& operator=(std::thread&& other)noexcept {
    +        if (joinable()) { // 如果当前有活跃线程,那就先执行完
    +            join();
    +        }
    +        t = std::move(other);
    +        return *this;
    +    }
    +    ~joining_thread() {
    +        if (joinable()) {
    +            join();
    +        }
    +    }
    +    void swap(joining_thread& other)noexcept {
    +        t.swap(other.t);
    +    }
    +    std::thread::id get_id()const noexcept {
    +        return t.get_id();
    +    }
    +    bool joinable()const noexcept {
    +        return t.joinable();
    +    }
    +    void join() {
    +        t.join();
    +    }
    +    void detach() {
    +        t.detach();
    +    }
    +    std::thread& data()noexcept {
    +        return t;
    +    }
    +    const std::thread& data()const noexcept {
    +        return t;
    +    }
    +};
    +
    `,6),Ga={href:"https://godbolt.org/z/bM7Ka7be5",target:"_blank",rel:"noopener noreferrer"},Ya=p(`
    int main(){
    +    std::cout << std::this_thread::get_id() << '\\n';
    +    joining_thread thread{[]{
    +            std::cout << std::this_thread::get_id() << '\\n';
    +    } };
    +    joining_thread thread2{ std::move(thread) };
    +}
    +

    使用容器管理线程对象,等待线程执行结束

    void do_work(std::size_t id){
    +    std::cout << id << '\\n';
    +}
    +
    +int main(){
    +    std::vector<std::thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i){
    +        threads.emplace_back(do_work, i); // 产生线程
    +    }
    +    for(auto& thread:threads){
    +        thread.join();                   // 对每个线程对象调用 join()
    +    }
    +}
    +
    `,3),Ja={href:"https://godbolt.org/z/rf4h7s63M",target:"_blank",rel:"noopener noreferrer"},Ka=p(`

    线程对象代表了线程,管理线程对象也就是管理线程,这个 vector 对象管理 10 个线程,保证他们的执行、退出。

    使用我们这节实现的 joining_thread 则不需要最后的循环 join()

    int main(){
    +    std::vector<joining_thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i){
    +        threads.emplace_back(do_work, i);
    +    }
    +}
    +
    `,3),Oa={href:"https://godbolt.org/z/8qa95vMz4",target:"_blank",rel:"noopener noreferrer"},Qa=p('

    如果你自己编译了这些代码,相信你注意到了,打印的是乱序的,没什么规律,而且重复运行的结果还不一样,这是正常现象。多线程执行就是如此,无序且操作可能被打断。使用互斥量可以解决这些问题,这也就是下一章节的内容了。

    总结

    本章节的内容围绕着:“使用线程”,也就是"使用 std::thread"展开, std::thread 是我们学习 C++ 并发支持库的重中之重,本章的内容并不少见,但是却是少有的准确与完善。即使你早已学习过乃至使用 C++ 标准库进行多线程编程已经很久,我相信本章也一定可以让你收获良多。

    如果是第一次学习,有还不够理解的地方,则一定要多思考,或记住,以后多看。

    我尽量讲的简单与通俗易懂。学完本章,你大概率还无法在实际环境使用多线程提升程序效率,至少也要学习到使用互斥量,保护共享数据,才可实际使用。


    ',6),Za={class:"footnotes"},$a={class:"footnotes-list"},nt={id:"footnote1",class:"footnote-item"},st={href:"https://zh.cppreference.com/w/cpp/language/overload_resolution",target:"_blank",rel:"noopener noreferrer"},at=n("strong",null,"选择最合适的函数重载进行调用",-1),tt=n("a",{href:"#footnote-ref1",class:"footnote-backref"},"↩︎",-1);function pt(ot,et){const t=o("ExternalLinkIcon"),e=o("RouteLink");return l(),u("div",null,[r,n("p",null,[s("在标准 C++ 中,"),n("a",k,[v,a(t)]),s(" 可以指代线程,使用线程也就是使用 "),m,s(" 类。")]),b,n("p",null,[s('这段代码将"Hello World!"写入到标准输出流,换行并'),n("a",h,[s("刷新"),a(t)]),s("。")]),_,n("p",null,[g,s(" 创建了一个线程对象 "),f,s(",将 "),w,s(" 作为它的"),n("a",y,[s("可调用(Callable)"),a(t)]),s("对象,在新线程中执行。线程对象关联了一个线程资源,我们无需手动控制,在线程对象构造成功,就自动在新线程开始执行函数 "),E,s("。")]),n("p",null,[j,s(" 等待线程对象 "),x,s(" 关联的线程执行完毕,否则将一直堵塞。这里的调用是必须的,否则 "),z,s(" 的析构函数将调用 "),n("a",B,[q,a(t)]),s(" 无法正确析构。")]),n("p",null,[s("这是因为我们创建线程对象 "),A,s(" 的时候就关联了一个活跃的线程,调用 "),C,s(" 就是确保线程对象关联的线程已经执行完毕,然后会修改对象的状态,让 "),n("a",T,[F,a(t)]),s(" 返回 "),I,s(",表示线程对象目前没有关联活跃线程。"),W,s(" 的析构函数,正是通过 "),D,s(" 判断线程对象目前是否有关联活跃线程,如果为 "),P,s(",那么就当做有关联活跃线程,会调用 "),H,s("。")]),M,L,R,n("p",null,[s("使用 "),n("a",S,[X,a(t)]),s(" 可以获得我们当前硬件支持的并发线程数量,它是 "),N,s(" 的静态成员函数。")]),U,n("p",null,[s("本节其实是要普及一下计算机常识,一些古老的书籍比如 csapp 应该也会提到“"),n("strong",null,[n("a",V,[s("超线程技术"),a(t)])]),s("”。")]),G,n("blockquote",null,[n("p",null,[n("a",Y,[s("运行"),a(t)]),s("测试。")])]),J,n("p",null,[s("我们先获取了迭代器所指向的值的类型,定义了一个别名 "),K,s(",我们这里使用到的 "),n("a",O,[Q,a(t)]),s(" 是 C++20 引入的,"),n("a",Z,[s("返回类型推导"),a(t)]),s("是 C++14 引入。如果希望代码可以在 C++11 的环境运行也可以自行修改为:")]),$,n("blockquote",null,[n("p",null,[n("a",nn,[s("运行"),a(t)]),s("测试。")])]),n("p",null,[sn,s(" 是当前硬件支持的并发线程的值。"),n("a",an,[tn,a(t)]),s(" 用来计算 first 到 last 的距离,也就是我们要进行求和的元素个数了。")]),n("p",null,[s("我们这里的设计比较简单,毕竟是初学,所以只对元素个数大于 "),pn,s(" 的进行多线程求和,而小于这个值的则直接使用标准库函数 "),n("a",on,[en,a(t)]),s(" 求和即可。")]),cn,n("p",null,[s("本节代码只是为了学习,而且只是百万数据通常没必要多线程,上亿的话差不多。如果你需要多线程求和,可以使用 C++17 引入的求和算法 "),n("a",ln,[un,a(t)]),s(" 并指明"),n("a",dn,[s("执行策略"),a(t)]),s("。它的效率接近我们实现的 "),rn,s(" 的两倍,当前环境核心越多数据越多,和单线程效率差距越明显。")]),kn,n("p",null,[s("我们上一节的示例是传递了一个函数给 "),vn,s(" 对象,函数会在新线程中执行。"),mn,s(" 支持的形式还有很多,只要是"),n("a",bn,[s("可调用(Callable)"),a(t)]),s("对象即可,比如重载了 "),hn,s(" 的类对象(也可以直接叫函数对象)。")]),_n,n("blockquote",null,[n("p",null,[s("在确定每个形参的类型后,类型是 “T 的数组”或某个"),gn,s("。"),n("a",fn,[s("文档"),a(t)]),s("。")])]),wn,n("p",null,[s("C++11 引入的 Lambda 表达式,同样可以作为构造 "),yn,s(" 的参数,因为 Lambda 本身就是"),n("a",En,[s("生成"),a(t)]),s("了一个函数对象,它自身就是"),n("a",jn,[s("类类型"),a(t)]),s("。")]),xn,n("p",null,[s("启动线程后(也就是构造 "),zn,s(" 对象)我们必须在线程对象的生存期结束之前,即 "),n("a",Bn,[qn,a(t)]),s(" 调用之前,决定它的执行策略,是 "),n("a",An,[Cn,a(t)]),s("(合并)还是 "),n("a",Tn,[Fn,a(t)]),s("(分离)。")]),In,Wn,n("p",null,[s("在单线程的代码中,对象销毁之后再去访问,会产生"),n("a",Dn,[s("未定义行为"),a(t)]),s(",多线程增加了这个问题发生的几率。")]),Pn,n("p",null,[s("至于这个"),Hn,s(",我们只是确保函数 f 中创建的线程正常执行完成,其局部对象正常析构释放。"),n("a",Mn,[s("测试代码"),a(t)]),s("。")]),n("blockquote",null,[Ln,n("p",null,[s("你是否觉得这样也可以?也没问题?简单的"),n("a",Rn,[s("测试"),a(t)]),s("运行的确没问题。")]),Sn]),Xn,n("p",null,[s("“"),n("a",Nn,[s("资源获取即初始化"),a(t)]),s("”(RAII,Resource Acquisition Is Initialization)。")]),Un,n("p",null,[s("函数 f 执行完毕,局部对象就要逆序销毁了。因此,thread_guard 对象 g 是第一个被销毁的,"),Vn,s("。"),Gn,s("。这确保了线程对象 t 所关联的线程正常的执行完毕以及线程对象的正常析构。"),n("a",Yn,[s("测试代码"),a(t)]),s("。")]),n("blockquote",null,[n("p",null,[s("如果异常被抛出但未被捕获那么就会调用 "),n("a",Jn,[s("std::terminate"),a(t)]),s("。是否对未捕获的异常进行任何栈回溯由"),Kn,s("。(简单的说就是不一定会调用析构)")]),On]),Qn,n("p",null,[s("拷贝赋值和拷贝构造定义为 "),Zn,s(" 可以防止编译器隐式生成,同时会"),n("a",$n,[ns,a(t)]),s("移动构造函数和移动赋值运算符的隐式定义。这样的话,对 thread_guard 对象进行拷贝或赋值等操作会引发一个编译错误。")]),ss,n("p",null,[n("a",as,[s("运行代码"),a(t)]),s(",打印的地址截然不同。")]),n("p",null,[s("可以通过编译,但通常这不符合我们的需求,因为我们的函数中的参数是引用,我们自然希望能引用调用方传递的参数,而不是拷贝。如果我们的 f 的形参类型不是 "),ts,s(",则会产生一个"),n("a",ps,[s("编译错误"),a(t)]),s("。")]),n("p",null,[s("想要解决这个问题很简单,我们可以使用标准库的设施 "),n("a",os,[es,a(t)]),s(" 、 "),cs,s(" 函数模板。")]),ls,n("blockquote",null,[n("p",null,[n("a",us,[s("运行代码"),a(t)]),s(",打印地址完全相同。")])]),n("p",null,[s("我们来解释一下,“"),is,s("” 其实就是 “"),ds,s("”(引用)的缩写,意思也很简单,返回“引用”,当然了,不是真的返回引用,它们返回一个包装类 "),n("a",rs,[ks,a(t)]),s(",顾名思义,这个类就是包装引用对象类模板,将对象包装,可以隐式转换为被包装对象的引用。")]),vs,n("blockquote",null,[n("p",null,[s("如果对他们的实现感兴趣,可以观看"),n("a",ms,[s("视频"),a(t)]),s("。")])]),bs,n("p",null,[s("以上代码"),hs,s(" 如果不使用 "),_s,s(" 并不会和前面 "),gs,s(" 一样只是多了拷贝,而是会产生"),n("a",fs,[ws,a(t)]),s(",这是因为 "),ys,s(" 内部会将保有的参数副本转换为"),Es,s(",这是为了那些"),js,s(",左值引用没办法引用右值表达式,所以产生编译错误。")]),xs,n("blockquote",null,[n("p",null,[n("a",zs,[s("运行"),a(t)]),s("测试。")])]),Bs,qs,As,n("p",null,[n("a",Cs,[Ts,a(t)]),s("也是"),n("a",Fs,[Is,a(t)]),s("("),Ws,s(")的 ,可以传递给 "),Ds,s(" 作为构造参数,让其关联的线程执行成员函数。")]),Ps,n("p",null,[s("我们还可以使用模板函数 "),n("a",Hs,[Ms,a(t)]),s("与成员指针一起使用")]),Ls,n("p",null,[s("不过需要注意,"),Rs,s(" 也是默认"),n("a",Ss,[Xs,a(t)]),s("的,即使我们的成员函数形参类型为引用:")]),Ns,n("p",null,[s("除非给参数 "),Us,s(" 加上 "),Vs,s(",就是按"),n("a",Gs,[s("引用"),a(t)]),s("传递了:")]),Ys,n("p",null,[s("字符串字面量具有静态"),n("a",Js,[Ks,a(t)]),s(",指向它的指针这当然没问题了,不用担心生存期的问题,但是如果是指向“动态”对象的指针,就要特别注意了:")]),Os,n("p",null,[s("以上代码可能导致一些问题,buffer 是一个数组对象,作为 "),Qs,s(" 构造参数的传递的时候会"),n("a",Zs,[$s,a(t)]),s(" (确保实参在按值传递时会退化) "),na,s("。")]),sa,n("p",null,[aa,s(" 的构造函数中调用了创建线程的函数(windows 下可能为 "),n("a",ta,[pa,a(t)]),s("),它将我们传入的参数,f、buffer ,传递给这个函数,在新线程中执行函数 "),oa,s("。也就是说,调用和执行 "),ea,s(" 并不是说要在 "),ca,s(" 的构造函数中,而是在创建的新线程中,具体什么时候执行,取决于操作系统的调度,所以完全有可能函数 "),la,s(" 先执行完,而新线程此时还没有进行 "),ua,s(" 的调用,转换为"),ia,s(",那么 buffer 指针就"),da,s("了,会导致问题。解决方案:")]),ra,n("ol",null,[n("li",null,[n("a",ka,[va,a(t)]),s(" 建议实现重新调度各执行线程。")]),n("li",null,[n("a",ma,[ba,a(t)]),s(" 返回当前线程 id。")]),n("li",null,[n("a",ha,[_a,a(t)]),s(" 使当前线程停止执行指定时间。")]),n("li",null,[n("a",ga,[fa,a(t)]),s(" 使当前线程执行"),wa,s("指定的时间点。")])]),ya,n("ul",null,[n("li",null,[n("p",null,[s("使用 "),Ea,s(),n("a",ja,[s("打印"),a(t)]),s("主线程和子线程的 ID。")]),xa]),n("li",null,[za,n("p",null,[s("主线程延时 3 秒,这个传入了一个临时对象 "),Ba,s(" ,它是模板 "),n("a",qa,[Aa,a(t)]),s(" 的别名,以及还有很多其他的时间类型,都基于这个类。说实话挺麻烦的,如果您支持 C++14,建议使用"),n("a",Ca,[s("时间字面量"),a(t)]),s(",在 "),n("a",Ta,[Fa,a(t)]),s(" 命名空间中。我们可以改成下面这样:")]),Ia]),Wa,n("li",null,[Da,n("p",null,[Pa,s(" 本身设置使用很简单,是打印时间格式、设置时区麻烦。"),n("a",Ha,[s("运行结果"),a(t)]),s("。")])])]),Ma,n("p",null,[s("这段代码可以"),n("a",La,[s("通过编译"),a(t)]),s(",你是否感到奇怪?我们在函数 f() 中创建了一个局部的 "),Ra,s(" 对象,启动线程,然后返回它。")]),Sa,n("h2",Xa,[n("a",Na,[n("span",null,[a(e,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html"},{default:i(()=>[Ua,s(" 的构造-源码解析")]),_:1})])])]),Va,n("p",null,[s("简单"),n("a",Ga,[s("使用"),a(t)]),s("一下:")]),Ya,n("blockquote",null,[n("p",null,[n("a",Ja,[s("运行测试"),a(t)]),s("。")])]),Ka,n("blockquote",null,[n("p",null,[n("a",Oa,[s("运行测试"),a(t)]),s("。")])]),Qa,n("section",Za,[n("ol",$a,[n("li",nt,[n("p",null,[n("a",st,[s("重载决议"),a(t)]),s("简单来说就是编译器必须要根据规则"),at,s("。 "),tt])])])])])}const ut=c(d,[["render",pt],["__file","02使用线程.html.vue"]]),it=JSON.parse('{"path":"/md/02%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B.html","title":"使用线程","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"Hello World","slug":"hello-world","link":"#hello-world","children":[]},{"level":2,"title":"当前环境支持并发线程数","slug":"当前环境支持并发线程数","link":"#当前环境支持并发线程数","children":[]},{"level":2,"title":"线程管理","slug":"线程管理","link":"#线程管理","children":[{"level":3,"title":"启动新线程","slug":"启动新线程","link":"#启动新线程","children":[]},{"level":3,"title":"RAII","slug":"raii","link":"#raii","children":[]},{"level":3,"title":"传递参数","slug":"传递参数","link":"#传递参数","children":[]},{"level":3,"title":"std::this_thread","slug":"std-this-thread","link":"#std-this-thread","children":[]},{"level":3,"title":"std::thread 转移所有权","slug":"std-thread-转移所有权","link":"#std-thread-转移所有权","children":[]}]},{"level":2,"title":"std::thread 的构造-源码解析","slug":"std-thread-的构造-源码解析","link":"#std-thread-的构造-源码解析","children":[]},{"level":2,"title":"实现 joining_thread","slug":"实现-joining-thread","link":"#实现-joining-thread","children":[]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1709646719000,"updatedTime":1714709501000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":10},{"name":"A. Jiang","email":"de34@live.cn","commits":2}]},"readingTime":{"minutes":30.36,"words":9109},"filePathRelative":"md/02使用线程.md","localizedDate":"2024年3月5日","excerpt":"\\n

    在标准 C++ 中,std::thread 可以指代线程,使用线程也就是使用 std::thread 类。

    \\n

    Hello World

    \\n

    在我们初学 C++ 的时候应该都写过这样一段代码:

    \\n
    #include <iostream>\\n\\nint main(){\\n    std::cout << \\"Hello World!\\" << std::endl;\\n}\\n
    "}');export{ut as comp,it as data}; diff --git "a/assets/03async\344\270\216future\346\272\220\347\240\201\350\247\243\346\236\220.html-CgLju1xM.js" "b/assets/03async\344\270\216future\346\272\220\347\240\201\350\247\243\346\236\220.html-CgLju1xM.js" new file mode 100644 index 00000000..039b6cff --- /dev/null +++ "b/assets/03async\344\270\216future\346\272\220\347\240\201\350\247\243\346\236\220.html-CgLju1xM.js" @@ -0,0 +1 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c,a as e,b as t}from"./app-Oub5ASTw.js";const r={},n=e("h1",{id:"st-async-与-std-future-源码解析",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#st-async-与-std-future-源码解析"},[e("span",null,[e("code",null,"st::async"),t(" 与 "),e("code",null,"std::future"),t(" 源码解析")])])],-1),o=[n];function d(u,i){return s(),c("div",null,o)}const _=a(r,[["render",d],["__file","03async与future源码解析.html.vue"]]),f=JSON.parse('{"path":"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/03async%E4%B8%8Efuture%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html","title":"st::async 与 std::future 源码解析","lang":"zh-CN","frontmatter":{},"headers":[],"git":{"createdTime":1711507419000,"updatedTime":1711507419000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":9},"filePathRelative":"md/详细分析/03async与future源码解析.md","localizedDate":"2024年3月27日","excerpt":"\\n"}');export{_ as comp,f as data}; diff --git "a/assets/03\345\205\261\344\272\253\346\225\260\346\215\256.html-C-aV2-mo.js" "b/assets/03\345\205\261\344\272\253\346\225\260\346\215\256.html-C-aV2-mo.js" new file mode 100644 index 00000000..7557c472 --- /dev/null +++ "b/assets/03\345\205\261\344\272\253\346\225\260\346\215\256.html-C-aV2-mo.js" @@ -0,0 +1,403 @@ +import{_ as c}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as l,c as u,a as n,d as a,b as s,w as i,e as o}from"./app-Oub5ASTw.js";const d={},r=o(`

    共享数据

    本章节主要内容:

    在上一章内容,我们对于线程的基本使用和管理,可以说已经比较了解了,甚至深入阅读了部分的 std::thread 源码。所以如果你好好学习了上一章,本章也完全不用担心。

    我们本章,就要开始聊共享数据的那些事。

    条件竞争

    在多线程的情况下,每个线程都抢着完成自己的任务。在大多数情况下,即使会改变执行顺序,也是良性竞争,这是无所谓的。比如两个线程都要往标准输出输出一段字符,谁先谁后并不会有什么太大影响。

    void f() { std::cout << "❤️\\n"; }
    +void f2() { std::cout << "😢\\n"; }
    +
    +int main(){
    +    std::thread t{ f };
    +    std::thread t2{ f2 };
    +    t.join();
    +    t2.join();
    +}
    +
    `,8),k={href:"https://zh.cppreference.com/w/cpp/io/cout",target:"_blank",rel:"noopener noreferrer"},v=n("code",null,"std::cout",-1),m=n("em",null,"同步的 C++ 流保证是线程安全的(从多个线程输出的单独字符可能交错,但无数据竞争)",-1),b=o(`

    只有在涉及多线程修改相同共享数据的时候,才会导致“恶性的条件竞争”。

    std::vector<int>v;
    +
    +void f() { v.emplace_back(1); }
    +void f2() { v.erase(v.begin()); }
    +
    +int main() {
    +    std::thread t{ f };
    +    std::thread t2{ f2 };
    +    t.join();
    +    t2.join();
    +    std::cout << v.size() << '\\n';
    +}
    +

    比如这段代码就是典型的恶性条件竞争,两个线程共享一个 vector,并对它进行修改。可能导致许多问题,比如 f2 先执行,此时 vector 还没有元素,导致抛出异常。又或者 f 执行了一半,调用了 f2(),等等。

    当然了,也有可能先执行 f,然后执行 f2,最后打印了 0,程序老老实实执行完毕。

    但是我们显然不能寄希望于这种操作系统的调度。

    `,5),_=n("code",null,"emplace_back",-1),h={href:"https://zh.cppreference.com/w/cpp/language/memory_model#.E7.BA.BF.E7.A8.8B.E4.B8.8E.E6.95.B0.E6.8D.AE.E7.AB.9E.E4.BA.89",target:"_blank",rel:"noopener noreferrer"},g=n("em",null,"未定义的行为",-1),f=n("code",null,"emplace_back",-1),w=n("em",null,"未定义",-1),x=n("blockquote",null,[n("p",null,[s("当某个表达式的求值写入某个内存位置,而另一求值读或修改同一内存位置时,称这些"),n("strong",null,"表达式冲突"),s("。"),n("strong",null,"拥有两个冲突的求值的程序就有数据竞争"),s(",除非")]),n("ul",null,[n("li",null,"两个求值都在同一线程上,或者在同一信号处理函数中执行,或"),n("li",null,"两个冲突的求值都是原子操作(见 std::atomic),或"),n("li",null,"一个冲突的求值发生早于 另一个(见 std::memory_order)")]),n("p",null,[n("strong",null,"如果出现数据竞争,那么程序的行为未定义。")])],-1),y=n("em",null,"数据竞争",-1),E={href:"https://zh.cppreference.com/w/cpp/language/memory_model#.E7.BA.BF.E7.A8.8B.E4.B8.8E.E6.95.B0.E6.8D.AE.E7.AB.9E.E4.BA.89",target:"_blank",rel:"noopener noreferrer"},q=n("em",null,"未定义行为",-1),B=o(`
    int cnt = 0;
    +auto f = [&]{cnt++;};
    +std::thread t1{f}, t2{f}, t3{f}; // 未定义行为
    +

    使用互斥量

    互斥量(Mutex),又称为互斥锁,是一种用来保护临界区[1]的特殊对象,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:

    1. 如果互斥锁是锁定的, 通常说某个特定的线程正持有这个互斥锁

    2. 如果没有线程持有这个互斥量,那么这个互斥量就处于解锁状态


    概念从来不是我们的重点,用一段对比代码为你直观的展示互斥量的作用:

    void f() {
    +    std::cout << std::this_thread::get_id() << '\\n';
    +}
    +
    +int main() {
    +    std::vector<std::thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i)
    +        threads.emplace_back(f);
    +
    +    for (auto& thread : threads)
    +        thread.join();
    +}
    +
    `,7),z={href:"https://godbolt.org/z/K7hcYxec9",target:"_blank",rel:"noopener noreferrer"},A={href:"https://zh.cppreference.com/w/cpp/thread/mutex",target:"_blank",rel:"noopener noreferrer"},C=o(`
    #include <mutex> // 必要标头
    +std::mutex m;
    +
    +void f() {
    +    m.lock();
    +    std::cout << std::this_thread::get_id() << '\\n';
    +    m.unlock();
    +}
    +
    +int main() {
    +    std::vector<std::thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i)
    +        threads.emplace_back(f);
    +
    +    for (auto& thread : threads)
    +        thread.join();
    +}
    +

    当多个线程执行函数 f 的时候,只有一个线程能成功调用 lock() 给互斥量上锁,其他所有的线程 lock() 的调用将阻塞执行,直至获得锁。第一个调用 lock() 的线程得以继续往下执行,执行我们的 std::cout 输出语句,不会有任何其他的线程打断这个操作。直到线程执行 unlock(),就解锁了互斥量。

    那么其他线程此时也就能再有一个成功调用 lock...

    至于到底哪个线程才会成功调用,这个是由操作系统调度决定的。

    看一遍描述就可以了,简而言之,被 lock()unlock() 包含在其中的代码是线程安全的,同一时间只有一个线程执行,不会被其它线程的执行所打断。

    std::lock_guard

    `,6),M=n("code",null,"lock()",-1),D=n("code",null,"unlock()",-1),j={href:"https://zh.cppreference.com/w/cpp/thread/lock_guard",target:"_blank",rel:"noopener noreferrer"},O=n("code",null,"std::lock_guard",-1),T=o(`
    void f() {
    +    std::lock_guard<std::mutex>lc{ m };
    +    std::cout << std::this_thread::get_id() << '\\n';
    +}
    +
    `,1),L=n("code",null,"std::lock_guard",-1),F={href:"https://github.com/microsoft/STL/blob/8e2d724cc1072b4052b14d8c5f81a830b8f1d8cb/stl/inc/mutex#L452-L473",target:"_blank",rel:"noopener noreferrer"},X=o(`
    _EXPORT_STD template <class _Mutex>
    +class _NODISCARD_LOCK lock_guard { // class with destructor that unlocks a mutex
    +public:
    +    using mutex_type = _Mutex;
    +
    +    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
    +        _MyMutex.lock();
    +    }
    +
    +    lock_guard(_Mutex& _Mtx, adopt_lock_t) noexcept // strengthened
    +        : _MyMutex(_Mtx) {} // construct but don't lock
    +
    +    ~lock_guard() noexcept {
    +        _MyMutex.unlock();
    +    }
    +
    +    lock_guard(const lock_guard&)            = delete;
    +    lock_guard& operator=(const lock_guard&) = delete;
    +
    +private:
    +    _Mutex& _MyMutex;
    +};
    +
    `,1),S={href:"https://zh.cppreference.com/w/cpp/language/function#.E5.BC.83.E7.BD.AE.E5.87.BD.E6.95.B0",target:"_blank",rel:"noopener noreferrer"},P={href:"https://zh.cppreference.com/w/cpp/language/rule_of_three#.E4.BA.94.E4.B9.8B.E6.B3.95.E5.88.99",target:"_blank",rel:"noopener noreferrer"},R=n("p",null,"它只保有一个私有数据成员,一个引用,用来引用互斥量。",-1),V=n("p",null,[s("构造函数中初始化这个引用,同时上锁,析构函数中解锁,这是一个非常典型的 "),n("code",null,"RAII"),s(" 式的管理。")],-1),I={href:"https://zh.cppreference.com/w/cpp/thread/lock_tag_t",target:"_blank",rel:"noopener noreferrer"},K=n("code",null,"std::adopt_lock_t",-1),N=o(`

    所以有的时候你可能会看到一些这样的代码:

    void f(){
    +    //code..
    +    {
    +        std::lock_guard<std::mutex>lc{ m };
    +        // 涉及共享资源的修改的代码...
    +    }
    +    //code..
    +}
    +

    使用 {} 创建了一个块作用域,限制了对象 lc 的生存期,进入作用域构造 lock_guard 的时候上锁(lock),离开作用域析构的时候解锁(unlock)。

    “粒度”通常用于描述锁定的范围大小,较小的粒度意味着锁定的范围更小,因此有更好的性能和更少的竞争。

    我们举一个例子:

    std::mutex m;
    +
    +void add_to_list(int n, std::list<int>& list) {
    +    std::vector<int> numbers(n + 1);
    +    std::iota(numbers.begin(), numbers.end(), 0);
    +    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
    +
    +    {
    +        std::lock_guard<std::mutex>lc{ m };
    +        list.push_back(sum);
    +    }
    +}
    +void print_list(const std::list<int>& list){
    +    std::lock_guard<std::mutex>lc{ m };
    +    for(const auto& i : list){
    +        std::cout << i << ' ';
    +    }
    +    std::cout << '\\n';
    +}
    +
    std::list<int> list;
    +std::thread t1{ add_to_list,i,std::ref(list) };
    +std::thread t2{ add_to_list,i,std::ref(list) };
    +std::thread t3{ print_list,std::cref(list) };
    +std::thread t4{ print_list,std::cref(list) };
    +t1.join();
    +t2.join();
    +t3.join();
    +t4.join();
    +
    `,8),G={href:"https://godbolt.org/z/ETeEsKhzK",target:"_blank",rel:"noopener noreferrer"},Y=o("

    先看 add_to_list,只有 list.push_back(sum) 涉及到了对共享数据的修改,需要进行保护,我们用 {} 包起来了。

    假设有线程 A、B执行函数 add_to_list() :线程 A 中的 numbers、sum 与线程 B 中的,不是同一个,希望大家分清楚,自然不存在数据竞争,也不需要上锁。线程 A、B执行完了前面求 0-n 的计算,只有一个线程能在 lock_guard 的构造函数中成功调用 lock() 给互斥量上锁。假设线程 A 成功调用 lock(),那么线程 B 的 lock() 调用就阻塞了,必须等待线程 A 执行完里面的代码,然后作用域结束,调用 lock_guard 的析构函数,解锁 unlock(),此时线程 B 就可以进去执行了,避免了数据竞争,不存在一个对象同时被多个线程修改。

    函数 print_list() 就更简单了,打印 list,给整个函数上锁,同一时刻只能有一个线程执行。

    我们的使用代码是多个线程执行这两个函数,两个函数共享了一个锁,这样确保了当执行函数 print_list() 打印的时候,list 的状态是确定的。打印函数 print_listadd_to_list 函数的修改操作同一时间只能有一个线程在执行。print_list() 不可能看到正在被add_to_list() 修改的 list。

    至于到底哪个函数哪个线程会先执行,执行多少次,这些都由操作系统调度决定,也完全有可能连续 4 次都是执行函数 print_list 的线程成功调用 lock,会打印出了一样的值,这都很正常。


    ",6),W={href:"https://zh.cppreference.com/w/cpp/language/class_template_argument_deduction",target:"_blank",rel:"noopener noreferrer"},J=n("code",null,"std::lock_guard",-1),Q=o(`
    std::mutex m;
    +std::lock_guard lc{ m }; // std::lock_guard<std::mutex>
    +
    `,1),Z={href:"https://zh.cppreference.com/w/cpp/thread/scoped_lock",target:"_blank",rel:"noopener noreferrer"},H=n("code",null,"std::scoped_lock",-1),U=n("code",null,"lock_guard",-1),$=n("strong",null,"它可以管理多个互斥量",-1),nn=n("code",null,"lock_guard",-1),sn=o(`
    std::mutex m;
    +std::scoped_lock lc{ m }; // std::scoped_lock<std::mutex>
    +

    我们在后续管理多个互斥量,会详细了解这个类。

    try_lock

    try_lock 是互斥量中的一种尝试上锁的方式。与常规的 lock 不同,try_lock 会尝试上锁,但如果锁已经被其他线程占用,则不会阻塞当前线程,而是立即返回

    它的返回类型是 bool ,如果上锁成功就返回 true,失败就返回 false

    这种方法在多线程编程中很有用,特别是在需要保护临界区的同时,又不想线程因为等待锁而阻塞的情况下。

    std::mutex mtx;
    +
    +void threadFunction(int id) {
    +    // 尝试加锁
    +    if (mtx.try_lock()) {
    +        std::cout << "线程:" << id << " 获得锁" << std::endl;
    +        // 临界区代码
    +        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟临界区操作
    +        mtx.unlock(); // 解锁
    +        std::cout << "线程:" << id << " 释放锁" << std::endl;
    +    } else {
    +        std::cout << "线程:" << id << " 获取锁失败 处理步骤" << std::endl;
    +    }
    +}
    +

    如果有两个线程运行这段代码,必然有一个线程无法成功上锁,要走 else 的分支。

    std::thread t1(threadFunction, 1);
    +std::thread t2(threadFunction, 2);
    +
    +t1.join();
    +t2.join();
    +
    `,9),an={href:"https://godbolt.org/z/ajjxnPGMG",target:"_blank",rel:"noopener noreferrer"},tn=o(`

    保护共享数据

    互斥量主要也就是为了保护共享数据,上一节的使用互斥量也已经为各位展示了一些。

    然而使用互斥量来保护共享数据也并不是在函数中加上一个 std::lock_guard 就万事大吉了。有的时候只需要一个指针或者引用,就能让这种保护形同虚设

    class Data{
    +    int a{};
    +    std::string b{};
    +public:
    +    void do_something(){
    +        // 修改数据成员等...
    +    }
    +};
    +
    +class Data_wrapper{
    +    Data data;
    +    std::mutex m;
    +public:
    +    template<class Func>
    +    void process_data(Func func){
    +        std::lock_guard<std::mutex>lc{m};
    +        func(data);  // 受保护数据传递给函数
    +    }
    +};
    +
    +Data* p = nullptr;
    +
    +void malicious_function(Data& protected_data){
    +    p = &protected_data; // 受保护的数据被传递
    +}
    +
    +Data_wrapper d;
    +
    +void foo(){
    +    d.process_data(malicious_function);  // 传递了一个恶意的函数
    +    p->do_something();                   // 在无保护的情况下访问保护数据
    +}
    +

    成员函数模板 process_data 看起来一点问题也没有,使用 std::lock_guard 对数据做了保护,但是调用方传递了 malicious_function 这样一个恶意的函数,使受保护数据传递给外部,可以在没有被互斥量保护的情况下调用 do_something()

    我们传递的函数就不该是涉及外部副作用的,就应该是单纯的在受互斥量保护的情况下老老实实调用 do_something() 操作受保护的数据。

    process_data 的确算是没问题,用户非要做这些事情也是防不住的,我们只是告诉各位可能的情况。

    死锁:问题与解决

    试想一下,有一个玩具,这个玩具有两个部分,必须同时拿到两部分才能玩。比如一个遥控汽车,需要遥控器和玩具车才能玩。有两个小孩,他们都想玩这个玩具。当其中一个小孩拿到了遥控器和玩具车时,就可以尽情玩耍。当另一个小孩也想玩,他就得等待另一个小孩玩完才行。再试想,遥控器和玩具车被放在两个不同的地方,并且两个小孩都想要玩,并且一个拿到了遥控器,另一个拿到了玩具车。问题就出现了,除非其中一个孩子决定让另一个先玩,他把自己的那个部分给另一个小孩。但如果他们都不愿意,那么这个遥控汽车就谁都没有办法玩。

    我们当然不在乎小孩抢玩具,我们要聊的是线程对锁的竞争:两个线程需要对它们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个线程的互斥量解锁。因为它们都在等待对方释放互斥量,没有线程工作。 这种情况就是死锁。

    避免死锁的一般建议是让两个互斥量以相同的顺序上锁,总在互斥量 B 之前锁住互斥量 A,就通常不会死锁。反面示例

    std::mutex m1,m2;
    +std::size_t n{};
    +
    +void f(){
    +    std::lock_guard<std::mutex>lc1{ m1 };
    +    std::lock_guard<std::mutex>lc2{ m2 };;
    +    ++n;
    +}
    +void f2() {
    +    std::lock_guard<std::mutex>lc1{ m2 };
    +    std::lock_guard<std::mutex>lc2{ m1 };
    +    ++n;
    +}
    +

    ff2 因为互斥量上锁顺序不同,就有死锁风险。函数 f 先锁定 m1,然后再尝试锁定 m2,而函数 f2 先锁定 m2 再锁定 m1 。如果两个线程同时运行,它们就可能会彼此等待对方释放其所需的锁,从而造成死锁。

    `,15),on={href:"https://godbolt.org/z/b9zYs44of",target:"_blank",rel:"noopener noreferrer"},pn=o(`

    但是有的时候即使固定锁顺序,依旧会产生问题。当有多个互斥量保护同一个类的对象时,对于相同类型的两个不同对象进行数据的交换操作,为了保证数据交换的正确性,就要避免其它线程修改,确保每个对象的互斥量都锁住自己要保护的区域。如果按照前面的的选择一个固定的顺序上锁解锁,则毫无意义,比如:

    struct X{
    +    X(const std::string& str) :object{ str } {}
    +
    +    friend void swap(X& lhs, X& rhs);
    +private:
    +    std::string object;
    +    std::mutex m;
    +};
    +
    +void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::lock_guard<std::mutex>lock1{ lhs.m }; 
    +    std::lock_guard<std::mutex>lock2{ rhs.m }; 
    +    swap(lhs.object, rhs.object);
    +}
    +

    考虑用户调用的时候将参数交换,就会产生死锁

    X a{ "🤣" }, b{ "😅" };
    +std::thread t{ [&] {swap(a, b); } };  // 1
    +std::thread t2{ [&] {swap(b, a); } }; // 2
    +

    1 执行的时候,先上锁 a 的互斥量,再上锁 b 的互斥量。

    2 执行的时候,先上锁 b 的互斥量,再上锁 a 的互斥量。

    `,7),en=n("code",null,"2",-1),cn=n("strong",null,"死锁",-1),ln={href:"https://godbolt.org/z/eYbjqEx54",target:"_blank",rel:"noopener noreferrer"},un=n("p",null,"其实也就是回到了第一个示例的问题。",-1),dn={href:"https://zh.cppreference.com/w/cpp/thread/lock",target:"_blank",rel:"noopener noreferrer"},rn=n("code",null,"std::lock",-1),kn=o(`
    void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::lock(lhs.m, rhs.m);    // 给两个互斥量上锁
    +    std::lock_guard<std::mutex>lock1{ lhs.m,std::adopt_lock }; 
    +    std::lock_guard<std::mutex>lock2{ rhs.m,std::adopt_lock }; 
    +    swap(lhs.object, rhs.object);
    +}
    +

    因为前面已经使用了 std::lock 上锁,所以后的 std::lock_guard 构造都额外传递了一个 std::adopt_lock 参数,让其选择到不上锁的构造函数。函数退出也能正常解锁。

    std::locklhs.mrhs.m 上锁时若抛出异常,则在重抛前对任何已锁的对象调用 unlock() 解锁,也就是 std::lock 要么将互斥量都上锁,要么一个都不锁。

    `,3),vn={href:"https://zh.cppreference.com/w/cpp/thread/scoped_lock",target:"_blank",rel:"noopener noreferrer"},mn=n("code",null,"std::scoped_lock",-1),bn={href:"https://zh.cppreference.com/w/cpp/language/raii",target:"_blank",rel:"noopener noreferrer"},_n=n("code",null,"std::lock",-1),hn=o(`

    所以我们前面的代码可以改写为:

    void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::scoped_lock guard{ lhs.m,rhs.m };
    +    swap(lhs.object, rhs.object);
    +}
    +
    `,2),gn=n("strong",null,[n("code",null,"std::scoped_lock"),s(" 的源码实现与解析")],-1),fn=o('

    使用 std::scoped_lock 可以将所有 std::lock 替换掉,减少错误发生。

    然而它们的帮助都是有限的,一切最终都是依靠开发者使用与管理。

    死锁是多线程编程中令人相当头疼的问题,并且死锁经常是不可预见,甚至难以复现,因为在大部分时间里,程序都能正常完成工作。我们可以通过一些简单的规则,约束开发者的行为,帮助写出“无死锁”的代码

    std::unique_lock 灵活的锁

    ',5),wn={href:"https://zh.cppreference.com/w/cpp/thread/unique_lock",target:"_blank",rel:"noopener noreferrer"},xn=n("code",null,"std::unique_lock",-1),yn=n("code",null,"std::lock_guard",-1),En={href:"https://zh.cppreference.com/w/cpp/thread#.E6.9D.A1.E4.BB.B6.E5.8F.98.E9.87.8F",target:"_blank",rel:"noopener noreferrer"},qn=n("code",null,"std::lock_guard",-1),Bn=n("code",null,"swap",-1),zn=o(`
    void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::unique_lock<std::mutex>lock1{ lhs.m, std::defer_lock };
    +    std::unique_lock<std::mutex>lock2{ rhs.m, std::defer_lock };
    +    std::lock(lock1, lock2);
    +    swap(lhs.object, rhs.object);
    +    ++n;
    +}
    +
    `,1),An=n("code",null,"std::defer_lock",-1),Cn={href:"https://github.com/microsoft/STL/blob/main/stl/inc/mutex#L136-L273",target:"_blank",rel:"noopener noreferrer"},Mn=o(`
    private:
    +    _Mutex* _Pmtx = nullptr;
    +    bool _Owns    = false;
    +

    如你所见很简单,一个互斥量的指针,还有一个就是表示对象是否拥有互斥量所有权的 bool 类型的对象 _Owns 了。我们前面代码会调用构造函数:

    unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
    +    : _Pmtx(_STD addressof(_Mtx)), _Owns(false) {} // construct but don't lock
    +
    `,3),Dn=n("strong",null,[s("这个构造函数没有给互斥量上锁,且 "),n("code",null,"_Owns"),s(" 为 "),n("code",null,"false"),s(" 表示没有互斥量所有权")],-1),jn=n("code",null,"std::unique_lock",-1),On={href:"https://zh.cppreference.com/w/cpp/thread/unique_lock/lock",target:"_blank",rel:"noopener noreferrer"},Tn=n("code",null,"lock()",-1),Ln={href:"https://zh.cppreference.com/w/cpp/thread/unique_lock/try_lock",target:"_blank",rel:"noopener noreferrer"},Fn=n("code",null,"try_lock()",-1),Xn={href:"https://zh.cppreference.com/w/cpp/thread/unique_lock/unlock",target:"_blank",rel:"noopener noreferrer"},Sn=n("code",null,"unlock()",-1),Pn=n("code",null,"std::lock",-1),Rn=n("code",null,"lock()",-1),Vn=o(`
    void lock() { // lock the mutex
    +    _Validate();
    +    _Pmtx->lock();
    +    _Owns = true;
    +}
    +

    如你所见,正常上锁,并且把 _Owns 设置为 true,即表示当前对象拥有互斥量的所有权。那么接下来看析构函数:

    ~unique_lock() noexcept {
    +    if (_Owns) {
    +        _Pmtx->unlock();
    +    }
    +}
    +

    必须得是当前对象拥有互斥量的所有权析构函数才会调用 unlock() 解锁互斥量。我们的代码因为调用了 lock ,所以 _Owns 设置为 true ,函数结束的时候会解锁互斥量。


    设计挺奇怪的对吧,这个所有权语义。其实上面的代码还不够简单直接,我们再举个例子:

    std::mutex m;
    +
    +int main() {
    +    std::unique_lock<std::mutex>lock{ m,std::adopt_lock };
    +    lock.lock();
    +}
    +
    `,7),In={href:"https://godbolt.org/z/KqKrETe6d",target:"_blank",rel:"noopener noreferrer"},Kn=n("code",null,"std::adopt_lock",-1),Nn=n("strong",null,"有所有权",-1),Gn=n("code",null,"_Owns",-1),Yn=n("code",null,"true",-1),Wn=n("code",null,"lock()",-1),Jn=n("code",null,"_Validate()",-1),Qn=o(`
    void _Validate() const { // check if the mutex can be locked
    +    if (!_Pmtx) {
    +        _Throw_system_error(errc::operation_not_permitted);
    +    }
    +
    +    if (_Owns) {
    +        _Throw_system_error(errc::resource_deadlock_would_occur);
    +    }
    +}
    +
    `,1),Zn=n("code",null,"_Owns",-1),Hn=n("code",null,"true",-1),Un={href:"https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/unique_lock.h#L141-L144",target:"_blank",rel:"noopener noreferrer"},$n=o(`
    lock.mutex()->lock();
    +

    也就是说 std::unique_lock 要想调用 lock() 成员函数,必须是当前没有所有权

    所以正常的用法其实是,先上锁了互斥量,然后传递 std::adopt_lock 构造 std::unique_lock 对象表示拥有互斥量的所有权,即可在析构的时候正常解锁。如下:

    std::mutex m;
    +
    +int main() {
    +    m.lock();
    +    std::unique_lock<std::mutex>lock{ m,std::adopt_lock };
    +}
    +

    简而言之:


    我们前面提到了 std::unique_lock 更加灵活,那么灵活在哪?很简单,它拥有 lock()unlock() 成员函数,所以我们能写出如下代码:

    void f() {
    +    //code..
    +    
    +    std::unique_lock<std::mutex>lock{ m };
    +
    +    // 涉及共享资源的修改的代码...
    +
    +    lock.unlock(); // 解锁并释放所有权,析构函数不会再 unlock()
    +
    +    //code..
    +}
    +

    而不是像之前 std::lock_guard 一样使用 {}

    另外再聊一聊开销吧,其实倒也还好,多了一个 bool ,内存对齐,x64 环境也就是 16 字节。这都不是最重要的,主要是复杂性和需求,通常建议优先 std::lock_guard,当它无法满足你的需求或者显得代码非常繁琐,那么可以考虑使用 std::unique_lock

    在不同作用域传递互斥量

    `,13),ns={href:"https://zh.cppreference.com/w/cpp/named_req/Mutex",target:"_blank",rel:"noopener noreferrer"},ss=n("em",null,"互斥体 (Mutex)",-1),as=n("strong",null,"不可复制不可移动",-1),ts=n("code",null,"std::unique_lock",-1),os=n("code",null,"std::unique_lock",-1),ps=n("code",null,"std::unique_lock",-1),es=n("em",null,"就是调用移动构造",-1),cs={href:"https://zh.cppreference.com/w/cpp/language/return#.E8.87.AA.E5.8A.A8.E4.BB.8E.E5.B1.80.E9.83.A8.E5.8F.98.E9.87.8F.E5.92.8C.E5.BD.A2.E5.8F.82.E7.A7.BB.E5.8A.A8",target:"_blank",rel:"noopener noreferrer"},ls=n("code",null,"std::unique_lock",-1),us={href:"https://zh.cppreference.com/w/cpp/utility/move",target:"_blank",rel:"noopener noreferrer"},is=n("code",null,"std::move",-1),ds=o(`

    请勿对移动语义和转移所有权抱有错误的幻想,我们说的无非是调用 std::unique_lock 的移动构造罢了:

    _NODISCARD_CTOR_LOCK unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) {
    +    _Other._Pmtx = nullptr;
    +    _Other._Owns = false;
    +}
    +

    将数据成员赋给新对象,原来的置空,这就是所谓的 所有权”转移,切勿被词语迷惑。

    std::unique_lock 是只能移动不可复制的类,它移动即标志其管理的互斥量的所有权转移了。

    一种可能的使用是允许函数去锁住一个互斥量,并将互斥量的所有权转移到调用者上,所以调用者可以在这个锁保护的范围内执行代码。

    std::unique_lock<std::mutex>get_lock(){
    +    extern std::mutex some_mutex;
    +    std::unique_lock<std::mutex>lk{ some_mutex };
    +    return lk;
    +
    +}
    +void process_data(){
    +    std::unique_lock<std::mutex>lk{ get_lock() };
    +    // 执行一些任务...
    +}
    +

    return lk 这里会调用移动构造,将互斥量的所有权转移给调用方, process_data 函数结束的时候会解锁互斥量。

    `,5),rs=n("code",null,"extern std::mutex some_mutex",-1),ks=n("em",null,"所谓的在不同作用域传递互斥量,其实只是传递了它们的指针或者引用罢了",-1),vs={href:"https://zh.cppreference.com/w/cpp/language/lifetime",target:"_blank",rel:"noopener noreferrer"},ms={href:"https://zh.cppreference.com/w/cpp/language/storage_duration",target:"_blank",rel:"noopener noreferrer"},bs=n("p",null,[s("如果你简单写一个 "),n("code",null,"std::mutex some_mutex"),s(" 那么函数 "),n("code",null,"process_data"),s(" 中的 "),n("code",null,"lk"),s(" 会持有一个悬垂指针。")],-1),_s=n("code",null,"extern std::mutex",-1),hs={href:"https://godbolt.org/z/bWv5fcdGf",target:"_blank",rel:"noopener noreferrer"},gs=n("code",null,"new std::mutex",-1),fs=o('

    std::unique_lock 是灵活的,同样允许在对象销毁之前就解锁互斥量,调用 unlock() 成员函数即可,不再强调。

    保护共享数据的初始化过程

    保护共享数据并非必须使用互斥量,互斥量只是其中一种常见的方式而已,对于一些特殊的场景,也有专门的保护方式,比如对于共享数据的初始化过程的保护。我们通常就不会用互斥量,这会造成很多的额外开销

    我们不想为各位介绍其它乱七八糟的各种保护初始化的方式,我们只介绍三种:双检锁(错误)使用 std::call_once静态局部变量初始化在 C++11 是线程安全

    ',4),ws=o(`
  • 双检锁(错误)线程不安全

    void f(){
    +    if(!ptr){      // 1
    +        std::lock_guard<std::mutex>lk{ m };
    +        if(!ptr){  // 2
    +            ptr.reset(new some);  // 3
    +        }
    +    }
    +    ptr->do_something();  // 4
    +}
    +

    ① 是查看指针是否为空,空才需要初始化,才需要获取锁。指针为空,当获取锁后会再检查一次指针②(这就是双重检查),避免另一线程在第一次检查后再做初始化,并且让当前线程获取锁。

    然而这显然没用,因为有潜在的条件竞争。未被锁保护的读取操作①没有与其他线程里被锁保护的写入操作③进行同步,因此就会产生条件竞争。

    简而言之:一个线程知道另一个线程已经在执行③,但是此时还没有创建 some 对象,而只是分配内存对指针写入。那么这个线程在①的时候就不会进入,直接执行了 ptr->do_something()④,得不到正确的结果,因为对象还没构造。

    如果你觉得难以理解,那就记住 ptr.reset(new some); 并非是不可打断不可交换的固定指令。

    这种错误写法在一些单例中也非常的常见。如果你的同事或上司写出此代码,一般不建议指出,因为不见得你能教会他们,不要“没事找事”,只要不影响自己即可。

  • `,1),xs={href:"https://zh.cppreference.com/w/cpp/thread/call_once",target:"_blank",rel:"noopener noreferrer"},ys=n("code",null,"std::call_once",-1),Es={href:"https://zh.cppreference.com/w/cpp/thread/once_flag",target:"_blank",rel:"noopener noreferrer"},qs=n("code",null,"std::once_flag",-1),Bs=n("code",null,"std::call_once",-1),zs=n("strong",null,[s("使用 "),n("code",null,"std::call_once"),s(" 比显式使用互斥量消耗的资源更少,特别是当初始化完成之后")],-1),As=o(`
    std::shared_ptr<some>ptr;
    +std::mutex m;
    +std::once_flag resource_flag;
    +
    +void init_resource(){
    +    ptr.reset(new some);
    +}
    +
    +void foo(){
    +    std::call_once(resource_flag, init_resource); // 线程安全的一次初始化
    +    ptr->do_something();
    +}
    +
    `,1),Cs=n("code",null,"std::once_flag",-1),Ms=n("code",null,"std::call_once",-1),Ds=n("strong",null,"一次",-1),js=n("code",null,"std::call_once",-1),Os={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},Ts=n("em",null,"可调用 (Callable)",-1),Ls=o(`

    初始化”,自然是一次。但是 std::call_once 也有一些例外情况(比如异常)会让传入的可调用对象被多次调用,即“多次”初始化:

    std::once_flag flag;
    +int n = 0;
    +
    +void f(){
    +    std::call_once(flag, [] {
    +        ++n;
    +        std::cout << "第" << n << "次调用\\n";
    +        throw std::runtime_error("异常");
    +    });
    +}
    +
    +int main(){
    +    try{
    +        f();
    +    }
    +    catch (std::exception&){}
    +    
    +    try{
    +        f();
    +    }
    +    catch (std::exception&){}
    +}
    +
    `,2),Fs={href:"https://godbolt.org/z/aWqfEchd6",target:"_blank",rel:"noopener noreferrer"},Xs=o(`
  • 静态局部变量初始化在 C++11 是线程安全

    class my_class;
    +my_class& get_my_class_instance(){
    +    static my_class instance;  // 线程安全的初始化过程 初始化严格发生一次
    +}
    +

    多线程可以安全的调用 get_my_class_instance 函数,不用为数据竞争而担心。此方式也在单例中多见,是简单合理的做法。

  • `,1),Ss=n("hr",null,null,-1),Ps=n("p",null,[s("其实还有不少其他的做法或者反例,但是觉得没必要再聊了,因为本文不是详尽的文档,而是“"),n("strong",null,"教程"),s("”。")],-1),Rs=n("h2",{id:"保护不常更新的数据结构",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#保护不常更新的数据结构"},[n("span",null,"保护不常更新的数据结构")])],-1),Vs=n("p",null,"试想一下,你有一个数据结构存储了用户的设置信息,每次用户打开程序的时候,都要进行读取,且运行时很多地方都依赖这个数据结构需要读取,所以为了效率,我们使用了多线程读写。这个数据结构很少进行改变,而我们知道,多线程读取,是没有数据竞争的,是安全的,但是有些时候又不可避免的有修改和读取都要工作的时候,所以依然必须得使用互斥量进行保护。",-1),Is=n("code",null,"std::mutex",-1),Ks={href:"https://zh.wikipedia.org/wiki/%E8%AF%BB%E5%86%99%E9%94%81",target:"_blank",rel:"noopener noreferrer"},Ns=n("em",null,[n("strong",null,"读写锁")],-1),Gs={href:"https://zh.cppreference.com/w/cpp/thread/shared_timed_mutex",target:"_blank",rel:"noopener noreferrer"},Ys=n("code",null,"std::shared_timed_mutex",-1),Ws={href:"https://zh.cppreference.com/w/cpp/thread/shared_mutex",target:"_blank",rel:"noopener noreferrer"},Js=n("code",null,"std::shared_mutex",-1),Qs=n("code",null,"std::shared_mutex",-1),Zs=n("code",null,"std::lock_guard",-1),Hs=n("code",null,"std::unique_lock",-1),Us=n("code",null,"std::mutex",-1),$s=n("em",null,"写线程",-1),na=n("em",null,"读线程",-1),sa={href:"https://zh.cppreference.com/w/cpp/thread/shared_lock",target:"_blank",rel:"noopener noreferrer"},aa=n("code",null,"std::shared_lock",-1),ta=o(`
    class Settings {
    +private:
    +    std::map<std::string, std::string> data_;
    +    mutable std::shared_mutex mutex_; // “M&M 规则”:mutable 与 mutex 一起出现
    +
    +public:
    +    void set(const std::string& key, const std::string& value) {
    +        std::lock_guard<std::shared_mutex> lock(mutex_);
    +        data_[key] = value;
    +    }
    +
    +    std::string get(const std::string& key) const {
    +        std::shared_lock<std::shared_mutex> lock(mutex_);
    +        auto it = data_.find(key);
    +        return (it != data_.end()) ? it->second : ""; // 如果没有找到键返回空字符串
    +    }
    +};
    +
    `,1),oa={href:"https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/blob/main/code/03%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE/%E4%BF%9D%E6%8A%A4%E4%B8%8D%E5%B8%B8%E6%9B%B4%E6%96%B0%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.cpp",target:"_blank",rel:"noopener noreferrer"},pa={href:"https://godbolt.org/z/KG84rb8qd",target:"_blank",rel:"noopener noreferrer"},ea=n("p",null,[n("code",null,"std::shared_timed_mutex"),s(" 具有 "),n("code",null,"std::shared_mutex"),s(" 的所有功能,并且额外支持超时功能。所以以上代码可以随意更换这两个互斥量。")],-1),ca=n("h2",{id:"std-recursive-mutex",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#std-recursive-mutex"},[n("span",null,[n("code",null,"std::recursive_mutex")])])],-1),la=n("code",null,"std::mutex",-1),ua={href:"https://zh.cppreference.com/w/cpp/language/ub",target:"_blank",rel:"noopener noreferrer"},ia=n("em",null,"未定义行为",-1),da=n("code",null,"std::recursive_mutex",-1),ra=o(`

    std::recursive_mutex 是 C++ 标准库提供的一种互斥量类型,它允许同一线程多次锁定同一个互斥量,而不会造成死锁。当同一线程多次对同一个 std::recursive_mutex 进行锁定时,只有在解锁与锁定次数相匹配时,互斥量才会真正释放。但它并不影响不同线程对同一个互斥量进行锁定的情况。不同线程对同一个互斥量进行锁定时,会按照互斥量的规则进行阻塞

    #include <iostream>
    +#include <thread>
    +#include <mutex>
    +
    +std::recursive_mutex mtx;
    +
    +void recursive_function(int count) {
    +    // 递归函数,每次递归都会锁定互斥量
    +    mtx.lock();
    +    std::cout << "Locked by thread: " << std::this_thread::get_id() << ", count: " << count << std::endl;
    +    if (count > 0) {
    +        recursive_function(count - 1); // 递归调用
    +    }
    +    mtx.unlock(); // 解锁互斥量
    +}
    +
    +int main() {
    +    std::thread t1(recursive_function, 3);
    +    std::thread t2(recursive_function, 2);
    +
    +    t1.join();
    +    t2.join();
    +}
    +
    `,2),ka={href:"https://godbolt.org/z/aefrYbGd7",target:"_blank",rel:"noopener noreferrer"},va={href:"https://zh.cppreference.com/w/cpp/thread/recursive_mutex/lock",target:"_blank",rel:"noopener noreferrer"},ma=n("strong",null,[n("code",null,"lock")],-1),ba=n("code",null,"lock",-1),_a=n("code",null,"unlock",-1),ha=n("strong",null,"释放",-1),ga={href:"https://zh.cppreference.com/w/cpp/thread/recursive_mutex/unlock",target:"_blank",rel:"noopener noreferrer"},fa=n("strong",null,[n("code",null,"unlock")],-1),wa={href:"https://zh.cppreference.com/w/cpp/thread/recursive_mutex/lock",target:"_blank",rel:"noopener noreferrer"},xa=n("code",null,"unlock()",-1),ya=n("strong",null,"解锁互斥体",-1),Ea=o(`

    我们重点的强调了一下这两个成员函数的这个概念,其实也很简单,总而言之就是 unlock 必须和 lock 的调用次数一样,才会真正解锁互斥量。

    同样的,我们也可以使用 std::lock_guardstd::unique_lock 帮我们管理 std::recursive_mutex,而非显式调用 lockunlock

    void recursive_function(int count) {
    +    std::lock_guard<std::recursive_mutex>lc{ mtx };
    +    std::cout << "Locked by thread: " << std::this_thread::get_id() << ", count: " << count << std::endl;
    +    if (count > 0) {
    +        recursive_function(count - 1);
    +    }
    +}
    +
    `,3),qa={href:"https://godbolt.org/z/rqG613W94",target:"_blank",rel:"noopener noreferrer"},Ba=n("h2",{id:"new、delete-是线程安全的吗",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#new、delete-是线程安全的吗"},[n("span",null,[n("code",null,"new"),s("、"),n("code",null,"delete"),s(" 是线程安全的吗?")])])],-1),za=n("p",null,[s("如果你的标准达到 "),n("strong",null,"C++11"),s(",要求下列"),n("strong",null,"函数"),s("是线程安全的:")],-1),Aa={href:"https://zh.cppreference.com/w/cpp/memory/new/operator_new",target:"_blank",rel:"noopener noreferrer"},Ca=n("code",null,"new",-1),Ma={href:"https://zh.cppreference.com/w/cpp/memory/new/operator_delete",target:"_blank",rel:"noopener noreferrer"},Da=n("code",null,"delete",-1),ja=n("strong",null,"库",-1),Oa=n("li",null,[s("全局 "),n("code",null,"new"),s(" 运算符和 "),n("code",null,"delete"),s(" 运算符的用户替换版本")],-1),Ta={href:"https://zh.cppreference.com/w/cpp/memory/c/calloc",target:"_blank",rel:"noopener noreferrer"},La={href:"https://zh.cppreference.com/w/cpp/memory/c/malloc",target:"_blank",rel:"noopener noreferrer"},Fa={href:"https://zh.cppreference.com/w/cpp/memory/c/realloc",target:"_blank",rel:"noopener noreferrer"},Xa={href:"https://zh.cppreference.com/w/cpp/memory/c/aligned_alloc",target:"_blank",rel:"noopener noreferrer"},Sa={href:"https://zh.cppreference.com/w/cpp/memory/c/free",target:"_blank",rel:"noopener noreferrer"},Pa=o(`

    所以以下函数在多线程运行是线程安全的:

    void f(){
    +    T* p = new T{};
    +    delete p;
    +}
    +

    内存分配、释放操作是线程安全,构造和析构不涉及共享资源。而局部对象 p 对于每个线程来说是独立的。换句话说,每个线程都有其自己的 p 对象实例,因此它们不会共享同一个对象,自然没有数据竞争。

    如果 p 是全局对象(或者外部的,只要可被多个线程读写),多个线程同时对其进行访问和修改时,就可能会导致数据竞争和未定义行为。因此,确保全局对象的线程安全访问通常需要额外的同步措施,比如互斥量或原子操作。

    T* p = nullptr;
    +void f(){
    +    p = new T{}; // 存在数据竞争
    +    delete p;
    +}
    +

    即使 p 是局部对象,如果构造函数(析构同理)涉及读写共享资源,那么一样存在数据竞争,需要进行额外的同步措施进行保护。

    int n = 1;
    +
    +struct X{
    +    X(int v){
    +        ::n += v;
    +    }
    +};
    +
    +void f(){
    +    X* p = new X{ 1 }; // 存在数据竞争
    +    delete p;
    +}
    +
    `,7),Ra=n("code",null,"std::cout",-1),Va={href:"https://godbolt.org/z/acT7bcz8E",target:"_blank",rel:"noopener noreferrer"},Ia=o(`

    值得注意的是,如果是自己重载 operator newoperator delete 替换了库的全局版本,那么它的线程安全就要我们来保证。

    // 全局的 new 运算符,替换了库的版本
    +void* operator new  (std::size_t count){
    +    return ::operator new(count); 
    +}
    +

    以上代码是线程安全的,因为 C++11 保证了 new 运算符的库版本,即 ::operator new 是线程安全的,我们直接调用它自然不成问题。如果你需要更多的操作,就得使用互斥量之类的方式保护了。


    总而言之,new 表达式线程安全要考虑三方面:operator new、构造函数、修改指针。

    delete 表达式线程安全考虑两方面:operator delete、析构函数。

    C++ 只保证了 operator newoperator delete 这两个方面的线程安全(不包括用户定义的),其它方面就得自己保证了。前面的内容也都提到了。

    总结

    本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题。并说明了可以使用互斥量(std::mutex)保护共享数据,并且要注意互斥量上锁的“粒度”。C++标准库提供了很多工具,包括管理互斥量的管理类(std::lock_guard),但是互斥量只能解决它能解决的问题,并且它有自己的问题(死锁)。同时我们讲述了一些避免死锁的方法和技术。还讲了一下互斥量所有权转移。然后讨论了面对不同情况保护共享数据的不同方式,使用 std::call_once() 保护共享数据的初始化过程,使用读写锁(std::shared_mutex)保护不常更新的数据结构。以及特殊情况可能用到的互斥量 recursive_mutex,有些人可能喜欢称作:递归锁。最后聊了一下 newdelete 运算符的库函数实际是线程安全的,以及一些问题。

    `,10),Ka={href:"https://zh.cppreference.com/w/cpp/thread#.E6.9C.AA.E6.9D.A5.E4.BD.93",target:"_blank",rel:"noopener noreferrer"},Na=n("strong",null,"Futures",-1),Ga={href:"https://zh.cppreference.com/w/cpp/thread#.E6.9D.A1.E4.BB.B6.E5.8F.98.E9.87.8F",target:"_blank",rel:"noopener noreferrer"},Ya=n("strong",null,"条件变量",-1),Wa=n("hr",{class:"footnotes-sep"},null,-1),Ja={class:"footnotes"},Qa={class:"footnotes-list"},Za={id:"footnote1",class:"footnote-item"},Ha={href:"https://zh.wikipedia.org/wiki/%E8%87%A8%E7%95%8C%E5%8D%80%E6%AE%B5",target:"_blank",rel:"noopener noreferrer"},Ua=n("a",{href:"#footnote-ref1",class:"footnote-backref"},"↩︎",-1);function $a(nt,st){const t=p("ExternalLinkIcon"),e=p("RouteLink");return l(),u("div",null,[r,n("blockquote",null,[n("p",null,[n("a",k,[v,a(t)]),s(" 的单个 operator<< 调用是线程安全的,不会被打断。即:"),m])]),b,n("p",null,[s("而且即使不是一个添加元素,一个删除元素,全是 "),_,s(" 添加元素,也一样会有问题,由于 std::vector 不是线程安全的容器,因此当多个线程同时访问并修改 v 时,可能会发生"),n("a",h,[g,a(t)]),s("。具体来说,当两个线程同时尝试向 v 中添加元素时,但是 "),f,s(" 函数却是可以被打断的,执行了一半,又去执行另一个线程。可能会导致数据竞争,从而引发"),w,s("的结果。")]),x,n("p",null,[s("标量类型等都同理,有"),y,s(","),n("a",E,[q,a(t)]),s(":")]),B,n("p",null,[s("这段代码你多次"),n("a",z,[s("运行"),a(t)]),s("它会得到毫无规律和格式的结果,我们可以使用"),n("a",A,[s("互斥量"),a(t)]),s("解决这个问题:")]),C,n("p",null,[s("不过一般不推荐这样显式的 "),M,s(" 与 "),D,s(",我们可以使用 C++11 标准库引入的“管理类” "),n("a",j,[O,a(t)]),s(":")]),T,n("p",null,[s("那么问题来了,"),L,s(" 是如何做到的呢?它是怎么实现的呢?首先顾名思义,这是一个“管理类”模板,用来管理互斥量的上锁与解锁,我们来看它在 "),n("a",F,[s("MSVC STL"),a(t)]),s(" 的实现:")]),X,n("p",null,[s("这段代码极其简单,首先管理类,自然不可移动不可复制,我们定义复制构造与复制赋值为"),n("a",S,[s("弃置函数"),a(t)]),s(",同时"),n("a",P,[s("阻止"),a(t)]),s("了移动等函数的隐式定义。")]),R,V,n("p",null,[s("同时它还提供一个有额外"),n("a",I,[K,a(t)]),s("参数的构造函数 ,如果使用这个构造函数,则构造函数不会上锁。")]),N,n("blockquote",null,[n("p",null,[s("完整"),n("a",G,[s("代码测试"),a(t)]),s("。")])]),Y,n("p",null,[s("C++17 添加了一个新的特性,"),n("a",W,[s("类模板实参推导"),a(t)]),s(", "),J,s(" 可以根据传入的参数自行推导,而不需要写明模板类型参数:")]),Q,n("p",null,[s("并且 C++17 还引入了一个新的“管理类”:"),n("a",Z,[H,a(t)]),s(",它相较于 "),U,s("的区别在于,"),$,s("。不过对于处理一个互斥量的情况,它和 "),nn,s(" 几乎完全相同。")]),sn,n("blockquote",null,[n("p",null,[n("a",an,[s("运行"),a(t)]),s("测试。")])]),tn,n("blockquote",null,[n("p",null,[s("简而言之,有可能函数 f 锁定了 m1,函数 f2 锁定了 m2,函数 f 要往下执行,给 m2 上锁,所以在等待 f2 解锁 m2,然而函数 f2 也在等待函数 f 解锁 m1 它才能往下执行。所以死锁。"),n("a",on,[s("测试代码"),a(t)]),s("。")])]),pn,n("blockquote",null,[n("p",null,[s("完全可能线程 A 执行 1 的时候上锁了 a 的互斥量,线程 B 执行 "),en,s(" 上锁了 b 的互斥量。线程 A 往下执行需要上锁 b 的互斥量,线程 B 则要上锁 a 的互斥量执行完毕才能解锁,哪个都没办法往下执行,"),cn,s("。"),n("a",ln,[s("测试代码"),a(t)]),s("。")])]),un,n("p",null,[s("C++ 标准库有很多办法解决这个问题,"),n("strong",null,[s("可以使用 "),n("a",dn,[rn,a(t)])]),s(" ,它能一次性锁住多个互斥量,并且没有死锁风险。修改 swap 代码后如下:")]),kn,n("p",null,[s("C++17 新增了 "),n("a",vn,[mn,a(t)]),s(" ,提供此函数的 "),n("a",bn,[s("RAII"),a(t)]),s(" 包装,通常它比裸调用 "),_n,s(" 更好。")]),hn,n("p",null,[s("对此类有兴趣或任何疑问,建议阅读"),a(e,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/02scoped_lock%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html"},{default:i(()=>[gn]),_:1})]),fn,n("p",null,[n("a",wn,[xn,a(t)]),s(" 是 C++11 引入的一种通用互斥包装器,它相比于 "),yn,s(" 更加的灵活。当然,它也更加的复杂,尤其它还可以与我们下一章要讲的"),n("a",En,[s("条件变量"),a(t)]),s("一起使用。使用它可以将之前使用 "),qn,s(" 的 "),Bn,s(" 改写一下:")]),zn,n("p",null,[s("解释这段代码最简单的方式就是直接展示标准库的源码,首先,我们要了解 "),An,s(" 是“假设调用方线程已拥有互斥体的所有权”。没有所有权自然构造函数就不会上锁,但不止如此。我们还要先知道 std::unique_lock 保有的数据成员(都以 "),n("a",Cn,[s("MSVC STL"),a(t)]),s(" 为例):")]),Mn,n("p",null,[s("如你所见,只是初始化了数据成员而已,注意,"),Dn,s("。并且 "),jn,s(" 是有 "),n("a",On,[Tn,a(t)]),s(" 、"),n("a",Ln,[Fn,a(t)]),s(" 、"),n("a",Xn,[Sn,a(t)]),s(" 成员函数的,所以可以直接传递给 "),Pn,s("、 进行调用。这里还需要提一下 "),Rn,s(" 成员函数的代码:")]),Vn,n("p",null,[s("这段代码运行会"),n("a",In,[s("抛出异常"),a(t)]),s(",原因很简单,因为 "),Kn,s(" 只是不上锁,但是"),Nn,s(",即 "),Gn,s(" 设置为 "),Yn,s(" 了,当运行 "),Wn,s(" 成员函数的时候,调用了 "),Jn,s(" 进行检测,也就是:")]),Qn,n("p",null,[s("满足第二个 if,因为 "),Zn,s(" 为 "),Hn,s(" 所以抛出异常,别的标准库也都有"),n("a",Un,[s("类似设计"),a(t)]),s("。很诡异的设计对吧,正常。除非我们写成:")]),$n,n("p",null,[s("首先我们要明白,互斥量满足"),n("a",ns,[ss,a(t)]),s("的要求,"),as,s("。所谓的在不同作用域传递互斥量,其实只是传递了它们的指针或者引用罢了。可以利用各种类来进行传递,比如前面提到的 "),ts,s("。")]),n("p",null,[os,s(" 可以获取互斥量的所有权,而互斥量的所有权可以通过移动操作转移给其他的 "),ps,s(" 对象。有些时候,这种转移("),es,s(")是自动发生的,比如当"),n("a",cs,[s("函数返回"),a(t)]),s(),ls,s(" 对象。另一种情况就是得显式使用 "),n("a",us,[is,a(t)]),s("。")]),ds,n("p",null,[s("我相信你可能对 "),rs,s(" 有疑问,其实不用感到奇怪,这是一个互斥量的声明,可能别的翻译单元(或 dll 等)有它的定义,成功链接上。我们前面也说了:“"),ks,s("”,所以"),n("strong",null,[s("要特别注意互斥量的"),n("a",vs,[s("生存期"),a(t)])]),s("。")]),n("blockquote",null,[n("p",null,[s("extern 说明符只能搭配变量声明和函数声明(除了类成员或函数形参)。"),n("em",null,[s("它指定外部链接,而且技术上不影响存储期,但它不能用来定义自动存储期的对象,故所有 extern 对象都具有"),n("strong",null,[s("静态或线程"),n("a",ms,[s("存储期"),a(t)]),s("。")])])])]),bs,n("blockquote",null,[n("p",null,[s("举一个使用 "),_s,s(" 的完整"),n("a",hs,[s("运行示例"),a(t)]),s("。当然,其实理论上你 "),gs,s(" 也是完全可行...... 🤣🤣")])]),fs,n("ol",null,[ws,n("li",null,[n("p",null,[s("C++ 标准委员会也认为处理此问题很重要,"),n("strong",null,[s("所以标准库提供了 "),n("a",xs,[ys,a(t)]),s(" 和 "),n("a",Es,[qs,a(t)])]),s(" 来处理这种情况。比起锁住互斥量并显式检查指针,每个线程只需要使用 "),Bs,s(" 就可以。"),zs,s("。")]),As,n("p",null,[s("以上代码 "),Cs,s(" 对象是命名空间作用域声明,如果你有需要,它也可以是类的成员。用于搭配 "),Ms,s(" 使用,保证线程安全的"),Ds,s("初始化。"),js,s(" 只需要接受"),n("a",Os,[Ts,a(t)]),s("对象即可,也不要求一定是函数。")]),n("blockquote",null,[Ls,n("p",null,[n("a",Fs,[s("测试链接"),a(t)]),s("。正常情况会保证传入的可调用对象只调用一次,即初始化只有一次。异常之类的是例外。")])])]),Xs]),Ss,Ps,Rs,Vs,n("p",null,[s("然而使用 "),Is,s(" 的开销是过大的,它不管有没有发生数据竞争(也就是就算全是读的情况)也必须是老老实实上锁解锁,只有一个线程可以运行。如果你学过其它语言或者操作系统,相信这个时候就已经想到了:“"),n("a",Ks,[Ns,a(t)]),s("”。")]),n("p",null,[s("C++ 标准库自然为我们提供了: "),n("a",Gs,[Ys,a(t)]),s("(C++14)、 "),n("a",Ws,[Js,a(t)]),s("(C++17)。它们的区别简单来说,前者支持更多的操作方式,后者有更高的性能优势。")]),n("p",null,[Qs,s(" 同样支持 "),Zs,s("、"),Hs,s("。和 "),Us,s(" 做的一样,保证"),$s,s("的独占访问。"),n("strong",null,[s("而那些无需修改数据结构的"),na,s(",可以使用 "),n("a",sa,[aa,a(t)]),s(" 获取访问权")]),s(",多个线程可以一起读取。")]),ta,n("blockquote",null,[n("p",null,[n("a",oa,[s("完整代码"),a(t)]),s("。"),n("a",pa,[s("测试"),a(t)]),s("链接。标准输出可能交错,但无数据竞争。")])]),ea,ca,n("p",null,[s("线程对已经上锁的 "),la,s(" 再次上锁是错误的,这是"),n("a",ua,[ia,a(t)]),s("。然而在某些情况下,一个线程会尝试在释放一个互斥量前多次获取,所以提供了"),da,s("。")]),ra,n("blockquote",null,[n("p",null,[n("a",ka,[s("运行"),a(t)]),s("测试。")])]),n("ul",null,[n("li",null,[n("p",null,[n("a",va,[ma,a(t)]),s(":线程可以在递归互斥体上重复调用 "),ba,s("。在线程调用 "),_a,s(" 匹配次数后,所有权才会得到"),ha,s("。")])]),n("li",null,[n("p",null,[n("a",ga,[fa,a(t)]),s(":若所有权层数为 1(此线程对 "),n("a",wa,[s("lock()"),a(t)]),s(" 的调用恰好比 "),xa,s(" 多一次 )则"),ya,s(",否则将所有权层数减少 1。")])])]),Ea,n("blockquote",null,[n("p",null,[n("a",qa,[s("运行"),a(t)]),s("测试。")])]),Ba,za,n("ul",null,[n("li",null,[n("a",Aa,[Ca,s(" 运算符"),a(t)]),s("和 "),n("a",Ma,[Da,s(" 运算符"),a(t)]),s("的"),ja,s("版本")]),Oa,n("li",null,[n("a",Ta,[s("std::calloc"),a(t)]),s("、"),n("a",La,[s("std::malloc"),a(t)]),s("、"),n("a",Fa,[s("std::realloc"),a(t)]),s("、"),n("a",Xa,[s("std::aligned_alloc"),a(t)]),s(" (C++17 起)、"),n("a",Sa,[s("std::free"),a(t)])])]),Pa,n("blockquote",null,[n("p",null,[s("一个直观的展示是,我们可以在构造函数中使用 "),Ra,s(",看到无序的输出,"),n("a",Va,[s("例子"),a(t)]),s("。")])]),Ia,n("p",null,[s("下一章,我们将开始讲述同步操作,会使用到 "),n("a",Ka,[Na,a(t)]),s("、"),n("a",Ga,[Ya,a(t)]),s("等设施。")]),Wa,n("section",Ja,[n("ol",Qa,[n("li",Za,[n("p",null,[s('"'),n("em",null,[n("strong",null,[n("a",Ha,[s("临界区"),a(t)])])]),s('"指的是一个访问共享资源的程序片段,而这些共享资源又无法同时被多个线程访问的特性。在临界区中,通常会使用同步机制,比如我们要讲的互斥量(Mutex)。 '),Ua])])])])])}const ot=c(d,[["render",$a],["__file","03共享数据.html.vue"]]),pt=JSON.parse('{"path":"/md/03%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE.html","title":"共享数据","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"条件竞争","slug":"条件竞争","link":"#条件竞争","children":[]},{"level":2,"title":"使用互斥量","slug":"使用互斥量","link":"#使用互斥量","children":[{"level":3,"title":"std::lock_guard","slug":"std-lock-guard","link":"#std-lock-guard","children":[]},{"level":3,"title":"try_lock","slug":"try-lock","link":"#try-lock","children":[]}]},{"level":2,"title":"保护共享数据","slug":"保护共享数据","link":"#保护共享数据","children":[]},{"level":2,"title":"死锁:问题与解决","slug":"死锁-问题与解决","link":"#死锁-问题与解决","children":[]},{"level":2,"title":"std::unique_lock 灵活的锁","slug":"std-unique-lock-灵活的锁","link":"#std-unique-lock-灵活的锁","children":[]},{"level":2,"title":"在不同作用域传递互斥量","slug":"在不同作用域传递互斥量","link":"#在不同作用域传递互斥量","children":[]},{"level":2,"title":"保护共享数据的初始化过程","slug":"保护共享数据的初始化过程","link":"#保护共享数据的初始化过程","children":[]},{"level":2,"title":"保护不常更新的数据结构","slug":"保护不常更新的数据结构","link":"#保护不常更新的数据结构","children":[]},{"level":2,"title":"std::recursive_mutex","slug":"std-recursive-mutex","link":"#std-recursive-mutex","children":[]},{"level":2,"title":"new、delete 是线程安全的吗?","slug":"new、delete-是线程安全的吗","link":"#new、delete-是线程安全的吗","children":[]},{"level":2,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1710730998000,"updatedTime":1714799003000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":26},{"name":"LeeZQXML","email":"2919625053@qq.com","commits":2},{"name":"A. Jiang","email":"de34@live.cn","commits":1}]},"readingTime":{"minutes":32,"words":9600},"filePathRelative":"md/03共享数据.md","localizedDate":"2024年3月18日","excerpt":"\\n

    本章节主要内容:

    \\n
      \\n
    • \\n

      多线程共享数据的问题

      \\n
    • \\n
    • \\n

      使用互斥量保护共享数据

      \\n
    • \\n
    • \\n

      保护共享数据的其它方案

      \\n
    • \\n
    \\n

    在上一章内容,我们对于线程的基本使用和管理,可以说已经比较了解了,甚至深入阅读了部分的 std::thread 源码。所以如果你好好学习了上一章,本章也完全不用担心。

    \\n

    我们本章,就要开始聊共享数据的那些事。

    \\n

    条件竞争

    \\n

    在多线程的情况下,每个线程都抢着完成自己的任务。在大多数情况下,即使会改变执行顺序,也是良性竞争,这是无所谓的。比如两个线程都要往标准输出输出一段字符,谁先谁后并不会有什么太大影响。

    "}');export{ot as comp,pt as data}; diff --git "a/assets/04\345\220\214\346\255\245\346\223\215\344\275\234.html-DuqBIYTo.js" "b/assets/04\345\220\214\346\255\245\346\223\215\344\275\234.html-DuqBIYTo.js" new file mode 100644 index 00000000..ec7cb070 --- /dev/null +++ "b/assets/04\345\220\214\346\255\245\346\223\215\344\275\234.html-DuqBIYTo.js" @@ -0,0 +1,382 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as c,c as l,a as n,b as s,d as t,e as p}from"./app-Oub5ASTw.js";const u={},i=p('

    同步操作

    "同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在同步操作中,各个任务之间通常需要相互协调和等待,以确保数据的一致性和正确性

    本章的主要内容有:

    • 条件变量

    • std::future 等待异步任务

    • 在规定时间内等待

    本章将讨论如何使用条件变量等待事件,介绍 future 等标准库设施用作同步操作。

    等待事件或条件

    假设你正在一辆夜间运行的地铁上,那么你要如何在正确的站点下车呢?

    1. 一直不休息,每一站都能知道,这样就不会错过你要下车的站点,但是这会很疲惫。

    2. 可以看一下时间,估算一下地铁到达目的地的时间,然后设置一个稍早的闹钟,就休息。这个方法听起来还行,但是你可能被过早的叫醒,甚至估算错误导致坐过站,又或者闹钟没电了睡过站。

    3. 事实上最简单的方式是,到站的时候有人或者其它东西能将你叫醒(比如手机的地图,到达设置的位置就提醒)。

    ',8),d={href:"https://zh.wikipedia.org/wiki/%E5%BF%99%E7%A2%8C%E7%AD%89%E5%BE%85",target:"_blank",rel:"noopener noreferrer"},r=n("strong",null,"自旋",-1),k=p(`
    bool flag = false;
    +std::mutex m;
    +
    +void wait_for_flag(){
    +    std::unique_lock<std::mutex>lk{ m };
    +    while (!flag){
    +        lk.unlock();    // 1 解锁互斥量
    +        lk.lock();      // 2 上锁互斥量
    +    }
    +}
    +

    第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。循环中,休眠②前函数对互斥量解锁①,再休眠结束后再对互斥量上锁,让另外的线程有机会获取锁并设置标识(因为修改函数和等待函数共用一个互斥量)。

    void wait_for_flag(){
    +    std::unique_lock<std::mutex>lk{ m };
    +    while (!flag){
    +        lk.unlock();    // 1 解锁互斥量
    +        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 2 休眠
    +        lk.lock();      // 3 上锁互斥量
    +    }
    +}
    +

    第三种方式(也是最好的)实际上就是使用条件变量了。通过另一线程触发等待事件的机制是最基本的唤醒方式,这种机制就称为“条件变量”。

    `,4),v={href:"https://zh.cppreference.com/w/cpp/thread/condition_variable",target:"_blank",rel:"noopener noreferrer"},m=n("code",null,"std::condition_variable",-1),b={href:"https://zh.cppreference.com/w/cpp/thread/condition_variable_any",target:"_blank",rel:"noopener noreferrer"},_=n("code",null,"std::condition_variable_any",-1),h={href:"https://zh.cppreference.com/w/cpp/header/condition_variable",target:"_blank",rel:"noopener noreferrer"},f=n("code",null,"",-1),g=n("code",null,"condition_variable_any",-1),y=n("code",null,"std::condition_variable",-1),w=n("code",null,"std::unique_lock",-1),x=n("code",null,"std::condition_variable",-1),q=n("code",null,"condition_variable_any",-1),z={href:"https://zh.cppreference.com/w/cpp/named_req/BasicLockable",target:"_blank",rel:"noopener noreferrer"},C=n("em",null,"可基本锁定(BasicLockable)",-1),E=n("code",null,"_any",-1),A=n("code",null,"any",-1),T=n("strong",null,"更加通用但是却又更多的性能开销",-1),P=n("strong",null,"首选",-1),M=n("code",null,"std::condition_variable",-1),j=n("code",null,"std::condition_variable_any",-1),B=p(`
    std::mutex mtx;
    +std::condition_variable cv;
    +bool arrived = false;
    +
    +void waitForArrival() {
    +    std::unique_lock<std::mutex> lck(mtx);
    +    cv.wait(lck, []{ return arrived; }); // 等待 arrived 变为 true
    +    std::cout << "到达目的地,可以下车了!" << std::endl;
    +}
    +
    +void simulateArrival() {
    +    std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟地铁到站,假设5秒后到达目的地
    +    {
    +        std::lock_guard<std::mutex> lck(mtx);
    +        arrived = true; // 设置条件变量为 true,表示到达目的地
    +    }
    +    cv.notify_one(); // 通知等待的线程
    +}
    +
    `,1),D={href:"https://godbolt.org/z/eEaMhEanx",target:"_blank",rel:"noopener noreferrer"},F=n("code",null,"std::condition_variable_any",-1),I={href:"https://godbolt.org/z/8dcPsKd5q",target:"_blank",rel:"noopener noreferrer"},N=p("
    • std::mutex mtx: 创建了一个互斥量,用于保护共享数据的访问,确保在多线程环境下的数据同步。

    • std::condition_variable cv: 创建了一个条件变量,用于线程间的同步,当条件不满足时,线程可以等待,直到条件满足时被唤醒。

    • bool arrived = false: 设置了一个标志位,表示是否到达目的地。

    waitForArrival 函数中:

    1. std::unique_lock<std::mutex> lck(mtx): 使用互斥量创建了一个独占锁。

    2. cv.wait(lck, []{ return arrived; }): 阻塞当前线程,释放(unlock)锁,直到条件被满足。

    3. 一旦条件满足,即 arrived 变为 true,并且条件变量 cv唤醒(包括虚假唤醒),那么当前线程会重新获取锁(lock),并执行后续的操作。

    simulateArrival 函数中:

    1. std::this_thread::sleep_for(std::chrono::seconds(5)): 模拟地铁到站,暂停当前线程 5 秒。

    2. 设置 arrived 为 true,表示到达目的地。

    3. cv.notify_one(): 唤醒一个等待条件变量的线程。

    这样,当 simulateArrival 函数执行后,arrived 被设置为 true,并且通过 cv.notify_one() 唤醒了等待在条件变量上的线程,从而使得 waitForArrival 函数中的等待结束,可以执行后续的操作,即输出提示信息。


    ",7),Y={href:"https://zh.cppreference.com/w/cpp/thread/condition_variable/wait",target:"_blank",rel:"noopener noreferrer"},K=n("code",null,"wait",-1),L={href:"https://zh.cppreference.com/w/cpp/named_req/Predicate",target:"_blank",rel:"noopener noreferrer"},O=n("em",null,"谓词",-1),R=p(`
    void wait(std::unique_lock<std::mutex>& lock);                 // 1
    +
    +template<class Predicate>
    +void wait(std::unique_lock<std::mutex>& lock, Predicate pred); // 2
    +

    ②等价于:

    while (!pred())
    +    wait(lock);
    +
    `,3),V={href:"https://en.wikipedia.org/wiki/Spurious_wakeup",target:"_blank",rel:"noopener noreferrer"},S=p(`

    条件变量虚假唤醒是指在使用条件变量进行线程同步时,有时候线程可能会在没有收到通知的情况下被唤醒。问题取决于程序和系统的具体实现。解决方法很简单,在循环中等待并判断条件可一并解决。使用 C++ 标准库则没有这个烦恼了。

    线程安全的队列

    在本节中,我们介绍了一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点:

    1. 当执行 push 操作时,需要确保没有其他线程正在执行 pushpop 操作;同样,在执行 pop 操作时,也需要确保没有其他线程正在执行 pushpop 操作。

    2. 当队列为时,不应该执行 pop 操作。因此,我们需要使用条件变量来传递一个谓词,以确保在执行 pop 操作时队列不为空。

    基于以上思考,我们设计了一个名为 threadsafe_queue 的模板类,如下:

    template<typename T>
    +class threadsafe_queue {
    +    mutable std::mutex m;              // 互斥量,用于保护队列操作的独占访问
    +    std::condition_variable data_cond; // 条件变量,用于在队列为空时等待
    +    std::queue<T> data_queue;          // 实际存储数据的队列
    +public:
    +    threadsafe_queue() {}
    +    void push(T new_value) {
    +        {
    +            std::lock_guard<std::mutex>lk(m);
    +            data_queue.push(new_value);
    +        }
    +        data_cond.notify_one();
    +    }
    +    // 从队列中弹出元素(阻塞直到队列不为空)
    +    void pop(T& value) {
    +        std::unique_lock<std::mutex>lk(m);
    +        data_cond.wait(lk, [this] {return !data_queue.empty(); });
    +        value = data_queue.front();
    +        data_queue.pop();
    +    }
    +    // 从队列中弹出元素(阻塞直到队列不为空),并返回一个指向弹出元素的 shared_ptr
    +    std::shared_ptr<T> pop() {
    +        std::unique_lock<std::mutex>lk(m);
    +        data_cond.wait(lk, [this] {return !data_queue.empty(); });
    +        std::shared_ptr<T>res(std::make_shared<T>(data_queue.front()));
    +        data_queue.pop();
    +        return res;
    +    }
    +    bool empty()const {
    +        std::lock_guard<std::mutex>lk(m);
    +        return data_queue.empty();
    +    }
    +};
    +

    请无视我们省略的构造、赋值、交换、try_xx 等操作。以上示例已经足够。

    光写好了肯定不够,我们还得测试运行,我们可以写一个经典的:”生产者消费者模型“,也就是一个线程 push生产“,一个线程 pop消费“。

    void producer(threadsafe_queue<int>& q) {
    +    for (int i = 0; i < 5; ++i) {
    +        q.push(i);
    +    }
    +}
    +void consumer(threadsafe_queue<int>& q) {
    +    for (int i = 0; i < 5; ++i) {
    +        int value{};
    +        q.pop(value);
    +    }
    +}
    +

    两个线程分别运行 producerconsumer,为了观测运行我们可以为 pushpop 中增加打印语句:

    std::cout << "push:" << new_value << std::endl;
    +std::cout << "pop:" << value << std::endl;
    +
    `,11),G=n("strong",null,"可能",-1),J={href:"https://godbolt.org/z/33T44arb8",target:"_blank",rel:"noopener noreferrer"},X=n("strong",null,"运行",-1),U=p(`
    push:0
    +pop:0
    +push:1
    +pop:1
    +push:2
    +push:3
    +push:4
    +pop:2
    +pop:3
    +pop:4
    +

    这很正常,到底哪个线程会抢到 CPU 时间片持续运行,是系统调度决定的,我们只需要保证一开始提到的两点就行了:

    pushpop 都只能单独执行;当队列为时,不执行 pop 操作。

    我们可以给一个简单的示意图帮助你理解这段运行结果:

    初始状态:队列为空
    ++---+---+---+---+---+
    +
    +Producer 线程插入元素 0:
    ++---+---+---+---+---+
    +| 0 |   |   |   |   |
    +
    +Consumer 线程弹出元素 0:
    ++---+---+---+---+---+
    +|   |   |   |   |   |
    +
    +Producer 线程插入元素 1:
    ++---+---+---+---+---+
    +| 1 |   |   |   |   |
    +
    +Consumer 线程弹出元素 1:
    ++---+---+---+---+---+
    +|   |   |   |   |   |
    +
    +Producer 线程插入元素 2:
    ++---+---+---+---+---+
    +|   | 2 |   |   |   |
    +
    +Producer 线程插入元素 3:
    ++---+---+---+---+---+
    +|   | 2 | 3 |   |   |
    +
    +Producer 线程插入元素 4:
    ++---+---+---+---+---+
    +|   | 2 | 3 | 4 |   |
    +
    +Consumer 线程弹出元素 2:
    ++---+---+---+---+---+
    +|   |   | 3 | 4 |   |
    +
    +Consumer 线程弹出元素 3:
    ++---+---+---+---+---+
    +|   |   |   | 4 |   |
    +
    +Consumer 线程弹出元素 4:
    ++---+---+---+---+---+
    +|   |   |   |   |   |
    +
    +队列为空,所有元素已被弹出
    +

    到此,也就可以了。

    使用 future

    其实就是异步

    `,8),H={href:"https://github.com/Mq-b/Modern-Cpp-templates-tutorial",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://space.bilibili.com/1292761396",target:"_blank",rel:"noopener noreferrer"},W=n("em",null,[n("strong",null,"车到站")],-1),Z={href:"https://zh.cppreference.com/w/cpp/thread#.E6.9C.AA.E6.9D.A5.E4.BD.93",target:"_blank",rel:"noopener noreferrer"},$={href:"https://zh.cppreference.com/w/cpp/header/future",target:"_blank",rel:"noopener noreferrer"},nn=n("code",null,"",-1),sn={href:"https://zh.cppreference.com/w/cpp/thread/future",target:"_blank",rel:"noopener noreferrer"},an=n("code",null,"std::future",-1),tn={href:"https://zh.cppreference.com/w/cpp/thread/shared_future",target:"_blank",rel:"noopener noreferrer"},pn=n("code",null,"std::shared_future",-1),on=n("code",null,"std::unique_ptr",-1),en=n("code",null,"std::shared_ptr",-1),cn=n("code",null,"std::future",-1),ln=n("strong",null,"单个",-1),un=n("code",null,"std::shared_future",-1),dn=n("strong",null,"多个",-1),rn=n("code",null,"shared_future",-1),kn=n("p",null,[s("最简单的作用是,我们先前讲的 "),n("code",null,"std::thread"),s(" 执行任务是没有返回值的,这个问题就能使用 future 解决。")],-1),vn=n("h3",{id:"创建异步任务获取返回值",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#创建异步任务获取返回值"},[n("span",null,"创建异步任务获取返回值")])],-1),mn=n("code",null,"std::thread",-1),bn={href:"https://zh.cppreference.com/w/cpp/thread/async",target:"_blank",rel:"noopener noreferrer"},_n=n("code",null,"std::async",-1),hn=n("code",null,"std::async",-1),fn=n("code",null,"std::future",-1),gn={href:"https://zh.cppreference.com/w/cpp/thread/future/get",target:"_blank",rel:"noopener noreferrer"},yn=n("code",null,"get()",-1),wn=n("code",null,"future",-1),xn=p(`
    #include <iostream>
    +#include <thread>
    +#include <future>
    +
    +int task(int n){
    +    std::cout << "异步任务 ID: " << std::this_thread::get_id() << '\\n';
    +    return n * n;
    +}
    +
    +int main(){
    +    std::future<int> future = std::async(task, 10);
    +    std::cout << "main\\n";
    +    std::cout << future.get() << '\\n';
    +}
    +
    `,1),qn={href:"https://godbolt.org/z/5xvT1x86c",target:"_blank",rel:"noopener noreferrer"},zn=n("code",null,"std::thread",-1),Cn=n("code",null,"std::async",-1),En={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},An=n("code",null,"std::ref",-1),Tn=n("code",null,"std::async",-1),Pn=p(`
    struct X{
    +    int operator()(int n)const{
    +        return n * n;
    +    }
    +};
    +struct Y{
    +    int f(int n)const{
    +        return n * n;
    +    }
    +};
    +void f(int& p) { std::cout << &p << '\\n'; }
    +
    +int main(){
    +    Y y;
    +    int n = 0;
    +    auto t1 = std::async(X{}, 10);
    +    auto t2 = std::async(&Y::f,&y,10);
    +    auto t3 = std::async([] {});         
    +    auto t4 = std::async(f, std::ref(n));
    +    std::cout << &n << '\\n';
    +}
    +
    `,1),Mn={href:"https://godbolt.org/z/fEvs3M3vv",target:"_blank",rel:"noopener noreferrer"},jn={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},Bn=n("code",null,"std::ref",-1),Dn=n("code",null,"std::thread",-1),Fn=n("strong",null,"右值表达式进行传递",-1),In=n("strong",null,"只支持移动的类型",-1),Nn=n("code",null,"std::ref",-1),Yn=n("code",null,"void f(int&)",-1),Kn=n("code",null,"void f(const int&)",-1),Ln=p(`
    void f(const int& p) {}
    +void f2(int& p ){}
    +
    +int n = 0;
    +std::async(f, n);   // OK! 可以通过编译,不过引用的并非是局部的n
    +std::async(f2, n);  // Error! 无法通过编译
    +

    我们来展示使用 std::move ,也就移动传递参数:

    struct move_only {
    +    move_only() { std::puts("默认构造"); }
    +    move_only(const move_only&) = delete;
    +    move_only(move_only&&)noexcept {
    +        std::puts("移动构造");
    +    }
    +};
    +
    +void task(move_only x){
    +    std::cout << "异步任务 ID: " << std::this_thread::get_id() << '\\n';
    +}
    +
    +int main(){
    +    move_only x;
    +    std::future<void> future = std::async(task, std::move(x));
    +    std::this_thread::sleep_for(std::chrono::milliseconds(1));
    +    std::cout << "main\\n";
    +    future.wait();  // 等待异步任务执行完毕
    +}
    +
    `,3),On={href:"https://godbolt.org/z/fY9Md3nzz",target:"_blank",rel:"noopener noreferrer"},Rn=p(`

    如你所见,它支持只移动类型,我们将参数使用 std::move 传递。


    接下来我们聊 std::async 的执行策略,我们前面一直没有使用,其实就是在传递可调用对象与参数之前传递枚举值罢了:

    1. std::launch::async 在不同线程上执行异步任务。
    2. std::launch::deferred 惰性求值,不创建线程,等待 future 对象调用 waitget 成员函数的时候执行任务。

    而我们先前没有写明这个参数,实际上是默认std::launch::async | std::launch::deferred ,也就是说由实现选择到底是否创建线程执行异步任务。我们来展示一下:

    void f(){
    +    std::cout << std::this_thread::get_id() << '\\n';
    +}
    +
    +int main(){
    +    std::cout << std::this_thread::get_id() << '\\n';
    +    auto f1 = std::async(std::launch::deferred, f);
    +    f1.wait(); // 在 wait 或 get() 调用时执行,不创建线程
    +    auto f2 = std::async(std::launch::async,f); // 创建线程执行异步任务
    +    auto f3 = std::async(std::launch::deferred | std::launch::async, f); // 实现选择的执行方式
    +}
    +
    `,6),Vn={href:"https://godbolt.org/z/abr96xqvM",target:"_blank",rel:"noopener noreferrer"},Sn=n("hr",null,null,-1),Gn=n("p",null,"其实到此基本就差不多了,我们再介绍两个常见问题即可:",-1),Jn=n("code",null,"std::async",-1),Xn={href:"https://zh.cppreference.com/w/cpp/thread/future",target:"_blank",rel:"noopener noreferrer"},Un={href:"https://zh.cppreference.com/w/cpp/thread/future",target:"_blank",rel:"noopener noreferrer"},Hn=n("strong",null,"析构函数将阻塞到异步计算完成",-1),Qn=p(`
    std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f()
    +std::async(std::launch::async, []{ g(); }); // f() 完成前不开始
    +

    如你所见,这并不能创建异步任务,会堵塞,然后逐个执行。

    `,2),Wn=p(`
  • 被移动的 std::future 没有所有权,失去共享状态,不能调用 getwait 成员函数。

    auto t = std::async([] {});
    +std::future<void> future{ std::move(t) };
    +t.wait();   // Error! 抛出异常
    +

    如同没有线程资源所有权的 std::thread 对象调用 join() 一样错误,这是移动语义的基本语义逻辑。

  • `,1),Zn=n("h3",{id:"future-与-std-packaged-task",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#future-与-std-packaged-task"},[n("span",null,[n("code",null,"future"),s(" 与 "),n("code",null,"std::packaged_task")])])],-1),$n={href:"https://zh.cppreference.com/w/cpp/thread/packaged_task",target:"_blank",rel:"noopener noreferrer"},ns=n("code",null,"std::packaged_task",-1),ss={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},as=n("em",null,"可调用(Callable)",-1),ts=n("strong",null,"异步",-1),ps={href:"https://zh.cppreference.com/w/cpp/thread/future",target:"_blank",rel:"noopener noreferrer"},os=p(`

    通常它会和 std::future 一起使用,不过也可以单独使用,我们一步一步来:

    std::packaged_task<double(int, int)> task([](int a, int b){
    +    return std::pow(a, b);
    +});
    +task(10, 2); // 执行传递的 lambda,但无法获取返回值
    +
    `,2),es={href:"https://zh.cppreference.com/w/cpp/thread/packaged_task/operator()",target:"_blank",rel:"noopener noreferrer"},cs=n("code",null,"operator()",-1),ls={href:"https://zh.cppreference.com/w/cpp/named_req/Callable",target:"_blank",rel:"noopener noreferrer"},us=n("em",null,"可调用(Callable)",-1),is=n("code",null,"void",-1),ds=n("strong",null,"没办法获取返回值",-1),rs=p(`

    如果想要异步的获取返回值,我们需要在调用 operator() 之前,让它和 future 关联,然后使用 future.get(),也就是:

    std::packaged_task<double(int, int)> task([](int a, int b){
    +    return std::pow(a, b);
    +});
    +std::future<double>future = task.get_future();
    +task(10, 2); // 此处执行任务
    +std::cout << future.get() << '\\n'; // 不堵塞,此处获取返回值
    +
    `,2),ks={href:"https://godbolt.org/z/799Khvadc",target:"_blank",rel:"noopener noreferrer"},vs=p(`

    先关联任务,再执行任务,当我们想要获取任务的返回值的时候,就 future.get() 即可。值得注意的是,任务并不会在线程中执行,想要在线程中执行异步任务,然后再获取返回值,我们可以这么做:

    std::packaged_task<double(int, int)> task([](int a, int b){
    +    return std::pow(a, b);
    +});
    +std::future<double>future = task.get_future();
    +std::thread t{ std::move(task),10,2 }; // 任务在线程中执行
    +t.join();
    +
    +std::cout << future.get() << '\\n'; // 并不堵塞,获取任务返回值罢了
    +
    `,2),ms={href:"https://godbolt.org/z/85r9db49z",target:"_blank",rel:"noopener noreferrer"},bs=p(`

    因为 task 本身是重载了 operator() 的,是可调用对象,自然可以传递给 std::thread 执行,以及传递调用参数。唯一需要注意的是我们使用了 std::move ,这是因为 std::packaged_task 只能移动,不能复制。


    简而言之,其实 std::packaged_task 也就是一个“包装”类而已,它本身并没什么特殊的,老老实实执行我们传递的任务,且方便我们获取返回值罢了,明确这一点,那么一切都不成问题。

    std::packaged_task 也可以在线程中传递,在需要的时候获取返回值,而非像上面那样将它自己作为可调用对象:

    template<typename R, typename...Ts, typename...Args>
    +    requires std::invocable<std::packaged_task<R(Ts...)>&, Args...> 
    +void async_task(std::packaged_task<R(Ts...)>& task, Args&&...args) {
    +    // todo..
    +    task(std::forward<Args>(args)...);
    +}
    +
    +int main() {
    +    std::packaged_task<int(int,int)> task([](int a,int b){
    +        return a + b;
    +    });
    +    
    +    int value = 50;
    +    std::future<int> future = task.get_future();
    +    // 创建一个线程来执行异步任务
    +    std::thread t{ [&] {async_task(task, value, value); } };
    +    std::cout << future.get() << '\\n';
    +    t.join();
    +}
    +
    `,5),_s={href:"https://godbolt.org/z/qdK8GMaGE",target:"_blank",rel:"noopener noreferrer"},hs=p(`

    我们套了一个 lambda,这是因为函数模板不是函数,它并非具体类型,没办法直接被那样传递使用,只能包一层了。这只是一个简单的示例,展示可以使用 std::packaged_task 作函数形参,然后我们来传递任务进行异步调用等操作。

    我们再将第二章实现的并行 sum 改成 std::package_task + std::future 的形式:

    template<typename ForwardIt>
    +auto sum(ForwardIt first, ForwardIt last) {
    +    using value_type = std::iter_value_t<ForwardIt>;
    +    std::size_t num_threads = std::thread::hardware_concurrency();
    +    std::ptrdiff_t distance = std::distance(first, last);
    +
    +    if (distance > 1024000) {
    +        // 计算每个线程处理的元素数量
    +        std::size_t chunk_size = distance / num_threads;
    +        std::size_t remainder = distance % num_threads;
    +
    +        // 存储每个线程要执行的任务
    +        std::vector<std::packaged_task<value_type()>>tasks;
    +        // 和每一个任务进行关联的 future 用于获取返回值
    +        std::vector<std::future<value_type>>futures(num_threads);
    +
    +        // 存储关联线程的线程对象
    +        std::vector<std::thread> threads;
    +
    +        // 制作任务、与 future 关联、启动线程执行
    +        auto start = first;
    +        for (std::size_t i = 0; i < num_threads; ++i) {
    +            auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0));
    +            tasks.emplace_back(std::packaged_task<value_type()>{[start, end, i] {
    +                return std::accumulate(start, end, value_type{});
    +            }});
    +            start = end; // 开始迭代器不断向前
    +            futures[i] = tasks[i].get_future(); // 任务与 std::future 关联
    +            threads.emplace_back(std::move(tasks[i]));
    +        }
    +
    +        // 等待所有线程执行完毕
    +        for (auto& thread : threads)
    +            thread.join();
    +
    +        // 汇总线程的计算结果
    +        value_type total_sum {};
    +        for (std::size_t i = 0; i < num_threads; ++i) {
    +            total_sum += futures[i].get();
    +        }
    +        return total_sum;
    +    }
    +
    +    value_type total_sum = std::accumulate(first, last, value_type{});
    +    return total_sum;
    +}
    +
    `,3),fs={href:"https://godbolt.org/z/r19MYcv6e",target:"_blank",rel:"noopener noreferrer"},gs=n("p",null,[s("相比于之前,其实不同无非是定义了 "),n("code",null,"std::vector> tasks"),s(" 与 "),n("code",null,"std::vector> futures"),s(" ,然后在循环中制造任务插入容器,关联 tuple,再放到线程中执行。最后汇总的时候写一个循环,"),n("code",null,"futures[i].get()"),s(" 获取任务的返回值加起来即可。")],-1),ys=n("p",null,"到此,也就可以了。",-1),ws=n("h3",{id:"使用-std-promise",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#使用-std-promise"},[n("span",null,[s("使用 "),n("code",null,"std::promise")])])],-1),xs={href:"https://zh.cppreference.com/w/cpp/thread/promise",target:"_blank",rel:"noopener noreferrer"},qs=n("code",null,"std::promise",-1),zs=n("code",null,"std::promise",-1),Cs={href:"https://zh.cppreference.com/w/cpp/thread/future",target:"_blank",rel:"noopener noreferrer"},Es=p(`
    // 计算函数,接受一个整数并返回它的平方
    +void calculate_square(std::promise<int> promiseObj, int num) {
    +    // 模拟一些计算
    +    std::this_thread::sleep_for(std::chrono::seconds(1));
    +
    +    // 计算平方并设置值到 promise 中
    +    promiseObj.set_value(num * num);
    +}
    +
    +// 创建一个 promise 对象,用于存储计算结果
    +std::promise<int> promise;
    +
    +// 从 promise 获取 future 对象进行关联
    +std::future<int> future = promise.get_future();
    +
    +// 启动一个线程进行计算
    +int num = 5;
    +std::thread t(calculate_square, std::move(promise), num);
    +
    +// 阻塞,直到结果可用
    +int result = future.get();
    +std::cout << num << " 的平方是:" << result << std::endl;
    +
    +t.join();
    +
    `,1),As={href:"https://godbolt.org/z/qrnq16Mxh",target:"_blank",rel:"noopener noreferrer"},Ts={href:"https://zh.cppreference.com/w/cpp/thread/promise/set_value",target:"_blank",rel:"noopener noreferrer"},Ps=n("code",null,"set_value()",-1),Ms=n("code",null,"promise",-1),js=n("code",null,"get()",-1),Bs=n("code",null,"promise",-1),Ds=n("code",null,"std::promise",-1),Fs=n("strong",null,"只能移动",-1),Is=n("code",null,"std::move",-1),Ns=n("hr",null,null,-1),Ys=n("code",null,"set_value()",-1),Ks=n("code",null,"std::promise",-1),Ls={href:"https://zh.cppreference.com/w/cpp/thread/promise/set_exception",target:"_blank",rel:"noopener noreferrer"},Os=n("code",null,"set_exception()",-1),Rs={href:"https://zh.cppreference.com/w/cpp/error/exception_ptr",target:"_blank",rel:"noopener noreferrer"},Vs=n("code",null,"std::exception_ptr",-1),Ss={href:"https://zh.cppreference.com/w/cpp/error/current_exception",target:"_blank",rel:"noopener noreferrer"},Gs=n("code",null,"std::current_exception()",-1),Js=n("code",null,"std::future",-1),Xs=n("code",null,"get()",-1),Us=n("code",null,"promise",-1),Hs=n("code",null,"std::future",-1),Qs=p(`
    void throw_function(std::promise<int> prom) {
    +    try {
    +        throw std::runtime_error("一个异常");
    +    }
    +    catch (...) {
    +        prom.set_exception(std::current_exception());
    +    }
    +}
    +
    +int main() {
    +    std::promise<int> prom;
    +    std::future<int> fut = prom.get_future();
    +
    +    std::thread t(throw_function, std::move(prom));
    +
    +    try {
    +        std::cout << "等待线程执行,抛出异常并设置\\n";
    +        fut.get();
    +    }
    +    catch (std::exception& e) {
    +        std::cerr << "来自线程的异常: " << e.what() << '\\n';
    +    }
    +    t.join();
    +}
    +
    `,1),Ws={href:"https://godbolt.org/z/MdsaT8rdo",target:"_blank",rel:"noopener noreferrer"},Zs=n("strong",null,"运行结果",-1),$s=p(`
    等待线程执行,抛出异常并设置
    +来自线程的异常: 一个异常
    +

    你可能对这段代码还有一些疑问:我们写的是 promised<int> ,但是却没有使用 set_value 设置值,你可能会想着再写一行 prom.set_value(0)

    `,2),na=n("code",null,"set_value",-1),sa=n("code",null,"set_exception",-1),aa={href:"https://zh.cppreference.com/w/cpp/thread/future_error",target:"_blank",rel:"noopener noreferrer"},ta={href:"https://zh.cppreference.com/w/cpp/thread/future_errc",target:"_blank",rel:"noopener noreferrer"},pa=n("code",null,"promise_already_satisfied",-1),oa=n("code",null,"std::promise",-1),ea=n("strong",null,"无法共存",-1),ca=p(`

    简而言之,set_valueset_exception 二选一,如果先前调用了 set_value ,就不可再次调用 set_exception,反之亦然(不然就会抛出异常),示例如下:

    void throw_function(std::promise<int> prom) {
    +    prom.set_value(100);
    +    try {
    +        throw std::runtime_error("一个异常");
    +    }
    +    catch (...) {
    +        try{
    +            // 共享状态的 promise 已存储值,调用 set_exception 产生异常
    +            prom.set_exception(std::current_exception());
    +        }catch (std::exception& e){
    +            std::cerr << "来自 set_exception 的异常: " << e.what() << '\\n';
    +        }
    +    }
    +}
    +
    +int main() {
    +    std::promise<int> prom;
    +    std::future<int> fut = prom.get_future();
    +
    +    std::thread t(throw_function, std::move(prom));
    +    
    +    std::cout << "等待线程执行,抛出异常并设置\\n";
    +    std::cout << "值:" << fut.get() << '\\n'; // 100
    +
    +    t.join();
    +}
    +
    `,2),la={href:"https://godbolt.org/z/hb9arEsoE",target:"_blank",rel:"noopener noreferrer"},ua=n("strong",null,"运行结果",-1),ia=p(`
    等待线程执行,抛出异常并设置
    +值:100
    +来自 set_exception 的异常: promise already satisfied
    +

    多个线程的等待

    之前的例子中都在用 std::future ,不过 std::future 也有局限性。很多线程在等待的时候,只有一个线程能获取结果。当多个线程等待相同事件的结果时,就需要使用 std::shared_future 来替代 std::future 了。

    `,3);function da(ra,ka){const a=e("ExternalLinkIcon");return c(),l("div",null,[i,n("p",null,[s("这和线程有什么关系呢?其实第一种方法就是在说”"),n("a",d,[s("忙等待"),t(a)]),s("(busy waiting)”也称“"),r,s("“。")]),k,n("p",null,[s("C++ 标准库对条件变量有两套实现:"),n("a",v,[m,t(a)]),s(" 和 "),n("a",b,[_,t(a)]),s(",这两个实现都包含在 "),n("a",h,[f,t(a)]),s(" 头文件中。")]),n("p",null,[g,s(" 类是 "),y,s(" 的泛化。相对于只在 "),w,s(" 上工作的 "),x,s(","),q,s(" 能在任何满足"),n("a",z,[C,t(a)]),s("要求的锁上工作,所以增加了 "),E,s(" 后缀。显而易见,这种区分必然是 "),A,s(" 版"),T,s("。所以通常"),P,s(),M,s("。有特殊需求,才会考虑 "),j,s("。")]),B,n("blockquote",null,[n("p",null,[n("a",D,[s("运行"),t(a)]),s("测试。更换为 "),F,s(" 效果"),n("a",I,[s("相同"),t(a)]),s("。")])]),N,n("p",null,[s("条件变量的 "),n("a",Y,[K,t(a)]),s(" 成员函数有两个版本,以上代码使用的就是第二个版本,传入了一个"),n("a",L,[O,t(a)]),s("。")]),R,n("p",null,[s("这可以避免“"),n("a",V,[s("虚假唤醒(spurious wakeup)"),t(a)]),s("”。")]),S,n("p",null,[G,s("的"),n("a",J,[X,t(a)]),s("结果是:")]),U,n("p",null,[s("举个例子:我们在车站等车,你可能会做一些别的事情打发时间,比如学习"),n("a",H,[s("现代 C++ 模板教程"),t(a)]),s("、观看 "),n("a",Q,[s("mq白"),t(a)]),s(" 的视频教程、玩手机等。不过,你始终在等待一件事情:"),W,s("。")]),n("p",null,[s("C++ 标准库将这种事件称为 "),n("a",Z,[s("future"),t(a)]),s("。它用于处理线程中需要等待某个事件的情况,线程知道预期结果。等待的同时也可以执行其它的任务。")]),n("p",null,[s("C++ 标准库有两种 future,都声明在 "),n("a",$,[nn,t(a)]),s(" 头文件中:独占的 "),n("a",sn,[an,t(a)]),s(" 、共享的 "),n("a",tn,[pn,t(a)]),s("。它们的区别与 "),on,s(" 和 "),en,s(" 类似。"),cn,s(" 只能与"),ln,s("指定事件关联,而 "),un,s(" 能关联"),dn,s("事件。它们都是模板,它们的模板类型参数,就是其关联的事件(函数)的返回类型。当多个线程需要访问一个独立 future 对象时, 必须使用互斥量或类似同步机制进行保护。而多个线程访问同一共享状态,若每个线程都是通过其自身的 "),rn,s(" 对象副本进行访问,则是安全的。")]),kn,vn,n("p",null,[s("假设需要执行一个耗时任务并获取其返回值,但是并不急切的需要它。那么就可以启动新线程计算,然而 "),mn,s(" 没提供直接接收返回值的机制。所以我们可以使用 "),n("a",bn,[_n,t(a)]),s(" 函数模板。")]),n("p",null,[s("使用 "),hn,s(" 启动一个异步任务,它会返回一个 "),fn,s(" 对象,这个对象和任务关联,将持有最终计算出来的结果。当需要这个值的时候,只需要调用 "),n("a",gn,[yn,t(a)]),s(" 成员函数,就会阻塞直到 "),wn,s(" 为就绪为止(即任务执行完毕),返回执行结果。")]),xn,n("blockquote",null,[n("p",null,[n("a",qn,[s("运行"),t(a)]),s("测试。")])]),n("p",null,[s("与 "),zn,s(" 一样,"),Cn,s(" 支持任意"),n("a",En,[s("可调用(Callable)"),t(a)]),s("对象,以及传递调用参数。包括支持使用 "),An,s(" ,以及移动的问题。我们下面详细聊一下 "),Tn,s(" 参数传递的事。")]),Pn,n("blockquote",null,[n("p",null,[n("a",Mn,[s("运行"),t(a)]),s("测试。")])]),n("p",null,[s("如你所见,它支持所有"),n("a",jn,[s("可调用(Callable)"),t(a)]),s("对象,并且也是默认拷贝,必须使用 "),Bn,s(" 才能传递引用。并且它和 "),Dn,s(" 一样,内部会将保有的参数副本转换为"),Fn,s(",这是为了那些"),In,s(",左值引用没办法引用右值表达式,所以如果不使用 "),Nn,s(",这里 "),Yn,s(" 就会导致编译错误,如果是 "),Kn,s(" 则可以通过编译,不过引用的不是我们传递的局部对象。")]),Ln,n("blockquote",null,[n("p",null,[n("a",On,[s("运行"),t(a)]),s("测试。")])]),Rn,n("blockquote",null,[n("p",null,[n("a",Vn,[s("运行"),t(a)]),s("测试。")])]),Sn,Gn,n("ol",null,[n("li",null,[n("p",null,[s("如果从 "),Jn,s(" 获得的 "),n("a",Xn,[s("std::future"),t(a)]),s(" 没有被移动或绑定到引用,那么在完整表达式结尾, "),n("a",Un,[s("std::future"),t(a)]),s(" 的"),Hn,s("。")]),Qn]),Wn]),Zn,n("p",null,[s("类模板 "),n("a",$n,[ns,t(a)]),s(" 包装任何"),n("a",ss,[as,t(a)]),s("目标(函数、lambda 表达式、bind 表达式或其它函数对象),使得能"),ts,s("调用它。其返回值或所抛异常被存储于能通过 "),n("a",ps,[s("std::future"),t(a)]),s(" 对象访问的共享状态中。")]),os,n("p",null,[s("它有 "),n("a",es,[cs,t(a)]),s(" 的重载,它会执行我们传递的"),n("a",ls,[us,t(a)]),s("对象,不过这个重载的返回类型是 "),is,s(),ds,s("。")]),rs,n("blockquote",null,[n("p",null,[n("a",ks,[s("运行"),t(a)]),s("测试。")])]),vs,n("blockquote",null,[n("p",null,[n("a",ms,[s("运行"),t(a)]),s("测试。")])]),bs,n("blockquote",null,[n("p",null,[n("a",_s,[s("运行"),t(a)]),s("测试。")])]),hs,n("blockquote",null,[n("p",null,[n("a",fs,[s("运行"),t(a)]),s("测试。")])]),gs,ys,ws,n("p",null,[s("类模板 "),n("a",xs,[qs,t(a)]),s(" 用于存储一个值或一个异常,之后通过 "),zs,s(" 对象所创建的 "),n("a",Cs,[s("std::future"),t(a)]),s(" 对象异步获得。")]),Es,n("blockquote",null,[n("p",null,[n("a",As,[s("运行"),t(a)]),s("测试。")])]),n("p",null,[s("我们在新线程中通过调用 "),n("a",Ts,[Ps,t(a)]),s(" 函数设置 "),Ms,s(" 的值,并在主线程中通过与其关联的 future 对象的 "),js,s(" 成员函数获取这个值,如果"),Bs,s("的值还没有被设置,那么将阻塞当前线程,直到被设置为止。同样的 "),Ds,s(),Fs,s(",不可复制,所以我们使用了 "),Is,s(" 进行传递。")]),Ns,n("p",null,[s("除了 "),Ys,s(" 函数外,"),Ks,s(" 还有一个 "),n("a",Ls,[Os,t(a)]),s(" 成员函数,它接受一个 "),n("a",Rs,[Vs,t(a)]),s(" 类型的参数,这个参数通常通过 "),n("a",Ss,[Gs,t(a)]),s(" 获取,用于指示当前线程中抛出的异常。然后,"),Js,s(" 对象通过 "),Xs,s(" 函数获取这个异常,如果 "),Us,s(" 所在的函数有异常被抛出,则 "),Hs,s(" 对象会重新抛出这个异常,从而允许主线程捕获并处理它。")]),Qs,n("p",null,[n("a",Ws,[Zs,t(a)]),s(":")]),$s,n("p",null,[s("共享状态的 promise 已经存储值或者异常,再次调用 "),na,s("("),sa,s(") 会抛出 "),n("a",aa,[s("std::future_error"),t(a)]),s(" 异常,将错误码设置为 "),n("a",ta,[pa,t(a)]),s("。这是因为 "),oa,s(" 对象只能是存储值或者异常其中一种,而"),ea,s("。")]),ca,n("p",null,[n("a",la,[ua,t(a)]),s(":")]),ia])}const ba=o(u,[["render",da],["__file","04同步操作.html.vue"]]),_a=JSON.parse('{"path":"/md/04%E5%90%8C%E6%AD%A5%E6%93%8D%E4%BD%9C.html","title":"同步操作","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"等待事件或条件","slug":"等待事件或条件","link":"#等待事件或条件","children":[]},{"level":2,"title":"线程安全的队列","slug":"线程安全的队列","link":"#线程安全的队列","children":[]},{"level":2,"title":"使用 future","slug":"使用-future","link":"#使用-future","children":[{"level":3,"title":"创建异步任务获取返回值","slug":"创建异步任务获取返回值","link":"#创建异步任务获取返回值","children":[]},{"level":3,"title":"future 与 std::packaged_task","slug":"future-与-std-packaged-task","link":"#future-与-std-packaged-task","children":[]},{"level":3,"title":"使用 std::promise","slug":"使用-std-promise","link":"#使用-std-promise","children":[]},{"level":3,"title":"多个线程的等待","slug":"多个线程的等待","link":"#多个线程的等待","children":[]}]}],"git":{"createdTime":1713323227000,"updatedTime":1714799003000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":14},{"name":"A. Jiang","email":"de34@live.cn","commits":1}]},"readingTime":{"minutes":18.82,"words":5645},"filePathRelative":"md/04同步操作.md","localizedDate":"2024年4月17日","excerpt":"\\n

    \\"同步操作\\"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在同步操作中,各个任务之间通常需要相互协调和等待,以确保数据的一致性和正确性

    \\n

    本章的主要内容有:

    \\n
      \\n
    • \\n

      条件变量

      \\n
    • \\n
    • \\n

      std::future 等待异步任务

      \\n
    • \\n
    • \\n

      在规定时间内等待

      \\n
    • \\n
    \\n

    本章将讨论如何使用条件变量等待事件,介绍 future 等标准库设施用作同步操作。

    "}');export{ba as comp,_a as data}; diff --git a/assets/404.html-PJlRDmY_.js b/assets/404.html-PJlRDmY_.js new file mode 100644 index 00000000..66b185c1 --- /dev/null +++ b/assets/404.html-PJlRDmY_.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,a as n}from"./app-Oub5ASTw.js";const a={},r=n("p",null,"404 Not Found",-1),s=[r];function c(l,_){return e(),o("div",null,s)}const m=t(a,[["render",c],["__file","404.html.vue"]]),p=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound"},"headers":[],"git":{},"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"

    404 Not Found

    \\n"}');export{m as comp,p as data}; diff --git a/assets/SUMMARY.html-D8dQ4wSd.js b/assets/SUMMARY.html-D8dQ4wSd.js new file mode 100644 index 00000000..07bc726d --- /dev/null +++ b/assets/SUMMARY.html-D8dQ4wSd.js @@ -0,0 +1 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o,c as d,a as t,d as E,w as r,b as e}from"./app-Oub5ASTw.js";const m={},i=t("h1",{id:"summary",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#summary"},[t("span",null,"Summary")])],-1),u=t("code",null,"std::thread",-1),A=t("code",null,"std::scoped_lock",-1),c=t("code",null,"std::async",-1),s=t("code",null,"std::future",-1);function B(_,h){const l=n("RouteLink");return o(),d("div",null,[i,t("ul",null,[t("li",null,[E(l,{to:"/"},{default:r(()=>[e("介绍")]),_:1})]),t("li",null,[E(l,{to:"/md/01%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5.html"},{default:r(()=>[e("基本概念")]),_:1})]),t("li",null,[E(l,{to:"/md/02%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B.html"},{default:r(()=>[e("使用线程")]),_:1})]),t("li",null,[E(l,{to:"/md/03%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE.html"},{default:r(()=>[e("共享数据")]),_:1})]),t("li",null,[E(l,{to:"/md/04%E5%90%8C%E6%AD%A5%E6%93%8D%E4%BD%9C.html"},{default:r(()=>[e("同步操作")]),_:1})]),t("li",null,[E(l,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/"},{default:r(()=>[e("详细分析")]),_:1}),t("ul",null,[t("li",null,[E(l,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html"},{default:r(()=>[u,e(" 的构造-源码解析")]),_:1})]),t("li",null,[E(l,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/02scoped_lock%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html"},{default:r(()=>[A,e(" 的源码实现与解析")]),_:1})]),t("li",null,[E(l,{to:"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/03async%E4%B8%8Efuture%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html"},{default:r(()=>[c,e(" 与 "),s,e(" 源码解析")]),_:1})])])])])])}const g=a(m,[["render",B],["__file","SUMMARY.html.vue"]]),C=JSON.parse('{"path":"/SUMMARY.html","title":"Summary","lang":"zh-CN","frontmatter":{},"headers":[],"git":{"createdTime":1710500650000,"updatedTime":1713323227000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":7}]},"readingTime":{"minutes":0.4,"words":121},"filePathRelative":"SUMMARY.md","localizedDate":"2024年3月15日","excerpt":"\\n"}');export{g as comp,C as data}; diff --git a/assets/SearchResult-4y_nt7Ph.js b/assets/SearchResult-4y_nt7Ph.js new file mode 100644 index 00000000..83a29988 --- /dev/null +++ b/assets/SearchResult-4y_nt7Ph.js @@ -0,0 +1 @@ +import{u as U,f as se,g as te,h as M,i as ae,P as le,t as re,j as ue,k as L,l as k,m as ie,n as Y,p as t,q as oe,R as _,s as ne,v as ce,x as ve,C as de,y as pe,z as he,A as ye,B as Ee,D as me,E as Ae,F as ge,G as $,H as j,I as Be,J as Q,K as fe}from"./app-Oub5ASTw.js";const He=["/","/SUMMARY.html","/md/01%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5.html","/md/02%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B.html","/md/03%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE.html","/md/04%E5%90%8C%E6%AD%A5%E6%93%8D%E4%BD%9C.html","/image/%E6%8D%90%E8%B5%A0/","/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html","/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/02scoped_lock%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html","/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/03async%E4%B8%8Efuture%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html","/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/","/404.html","/md/","/image/"],Re="SEARCH_PRO_QUERY_HISTORY",m=U(Re,[]),ke=()=>{const{queryHistoryCount:a}=Q,l=a>0;return{enabled:l,queryHistory:m,addQueryHistory:r=>{l&&(m.value=Array.from(new Set([r,...m.value.slice(0,a-1)])))},removeQueryHistory:r=>{m.value=[...m.value.slice(0,r),...m.value.slice(r+1)]}}},b=a=>He[a.id]+("anchor"in a?`#${a.anchor}`:""),Qe="SEARCH_PRO_RESULT_HISTORY",{resultHistoryCount:I}=Q,A=U(Qe,[]),we=()=>{const a=I>0;return{enabled:a,resultHistory:A,addResultHistory:l=>{if(a){const r={link:b(l),display:l.display};"header"in l&&(r.header=l.header),A.value=[r,...A.value.slice(0,I-1)]}},removeResultHistory:l=>{A.value=[...A.value.slice(0,l),...A.value.slice(l+1)]}}},Ce=a=>{const l=de(),r=M(),w=pe(),i=L(0),f=k(()=>i.value>0),h=he([]);return ye(()=>{const{search:y,terminate:C}=Ee(),g=Be(c=>{const B=c.join(" "),{searchFilter:D=p=>p,splitWord:q,suggestionsFilter:P,...E}=l.value;B?(i.value+=1,y(c.join(" "),r.value,E).then(p=>D(p,B,r.value,w.value)).then(p=>{i.value-=1,h.value=p}).catch(p=>{console.warn(p),i.value-=1,i.value||(h.value=[])})):h.value=[]},Q.searchDelay-Q.suggestDelay);Y([a,r],([c])=>g(c),{immediate:!0}),me(()=>{C()})}),{isSearching:f,results:h}};var qe=se({name:"SearchResult",props:{queries:{type:Array,required:!0},isFocusing:Boolean},emits:["close","updateQuery"],setup(a,{emit:l}){const r=te(),w=M(),i=ae(le),{enabled:f,addQueryHistory:h,queryHistory:y,removeQueryHistory:C}=ke(),{enabled:g,resultHistory:c,addResultHistory:B,removeResultHistory:D}=we(),q=f||g,P=re(a,"queries"),{results:E,isSearching:p}=Ce(P),u=ue({isQuery:!0,index:0}),v=L(0),d=L(0),O=k(()=>q&&(y.value.length>0||c.value.length>0)),x=k(()=>E.value.length>0),F=k(()=>E.value[v.value]||null),z=()=>{const{isQuery:e,index:s}=u;s===0?(u.isQuery=!e,u.index=e?c.value.length-1:y.value.length-1):u.index=s-1},G=()=>{const{isQuery:e,index:s}=u;s===(e?y.value.length-1:c.value.length-1)?(u.isQuery=!e,u.index=0):u.index=s+1},J=()=>{v.value=v.value>0?v.value-1:E.value.length-1,d.value=F.value.contents.length-1},K=()=>{v.value=v.value{d.value{d.value>0?d.value-=1:J()},S=e=>e.map(s=>fe(s)?s:t(s[0],s[1])),W=e=>{if(e.type==="customField"){const s=Ae[e.index]||"$content",[o,R=""]=ge(s)?s[w.value].split("$content"):s.split("$content");return e.display.map(n=>t("div",S([o,...n,R])))}return e.display.map(s=>t("div",S(s)))},H=()=>{v.value=0,d.value=0,l("updateQuery",""),l("close")},X=()=>f?t("ul",{class:"search-pro-result-list"},t("li",{class:"search-pro-result-list-item"},[t("div",{class:"search-pro-result-title"},i.value.queryHistory),y.value.map((e,s)=>t("div",{class:["search-pro-result-item",{active:u.isQuery&&u.index===s}],onClick:()=>{l("updateQuery",e)}},[t($,{class:"search-pro-result-type"}),t("div",{class:"search-pro-result-content"},e),t("button",{class:"search-pro-remove-icon",innerHTML:j,onClick:o=>{o.preventDefault(),o.stopPropagation(),C(s)}})]))])):null,Z=()=>g?t("ul",{class:"search-pro-result-list"},t("li",{class:"search-pro-result-list-item"},[t("div",{class:"search-pro-result-title"},i.value.resultHistory),c.value.map((e,s)=>t(_,{to:e.link,class:["search-pro-result-item",{active:!u.isQuery&&u.index===s}],onClick:()=>{H()}},()=>[t($,{class:"search-pro-result-type"}),t("div",{class:"search-pro-result-content"},[e.header?t("div",{class:"content-header"},e.header):null,t("div",e.display.map(o=>S(o)).flat())]),t("button",{class:"search-pro-remove-icon",innerHTML:j,onClick:o=>{o.preventDefault(),o.stopPropagation(),D(s)}})]))])):null;return ie("keydown",e=>{if(a.isFocusing){if(x.value){if(e.key==="ArrowUp")N();else if(e.key==="ArrowDown")V();else if(e.key==="Enter"){const s=F.value.contents[d.value];h(a.queries.join(" ")),B(s),r.push(b(s)),H()}}else if(g){if(e.key==="ArrowUp")z();else if(e.key==="ArrowDown")G();else if(e.key==="Enter"){const{index:s}=u;u.isQuery?(l("updateQuery",y.value[s]),e.preventDefault()):(r.push(c.value[s].link),H())}}}}),Y([v,d],()=>{var e;(e=document.querySelector(".search-pro-result-list-item.active .search-pro-result-item.active"))==null||e.scrollIntoView(!1)},{flush:"post"}),()=>t("div",{class:["search-pro-result-wrapper",{empty:a.queries.length?!x.value:!O.value}],id:"search-pro-results"},a.queries.length?p.value?t(oe,{hint:i.value.searching}):x.value?t("ul",{class:"search-pro-result-list"},E.value.map(({title:e,contents:s},o)=>{const R=v.value===o;return t("li",{class:["search-pro-result-list-item",{active:R}]},[t("div",{class:"search-pro-result-title"},e||i.value.defaultTitle),s.map((n,ee)=>{const T=R&&d.value===ee;return t(_,{to:b(n),class:["search-pro-result-item",{active:T,"aria-selected":T}],onClick:()=>{h(a.queries.join(" ")),B(n),H()}},()=>[n.type==="text"?null:t(n.type==="title"?ne:n.type==="heading"?ce:ve,{class:"search-pro-result-type"}),t("div",{class:"search-pro-result-content"},[n.type==="text"&&n.header?t("div",{class:"content-header"},n.header):null,t("div",W(n))])])})])})):i.value.emptyResult:q?O.value?[X(),Z()]:i.value.emptyHistory:i.value.emptyResult)}});export{qe as default}; diff --git a/assets/app-Oub5ASTw.js b/assets/app-Oub5ASTw.js new file mode 100644 index 00000000..24aea19d --- /dev/null +++ b/assets/app-Oub5ASTw.js @@ -0,0 +1,25 @@ +const __vite__fileDeps=["assets/index.html-Cr_1xWLb.js","assets/plugin-vue_export-helper-DlAUqK2U.js","assets/SUMMARY.html-D8dQ4wSd.js","assets/01基本概念.html-OWekP6KD.js","assets/02使用线程.html-Cw8uz5dR.js","assets/03共享数据.html-C-aV2-mo.js","assets/04同步操作.html-DuqBIYTo.js","assets/index.html-aGXxoBnY.js","assets/01thread的构造与源码解析.html-D9zIyv03.js","assets/02scoped_lock源码解析.html-14rPQ6Fa.js","assets/03async与future源码解析.html-CgLju1xM.js","assets/index.html-BM0ukr18.js","assets/404.html-PJlRDmY_.js","assets/index.html-BTzaSACv.js","assets/index.html-BLSw2nVP.js"],__vite__mapDeps=i=>i.map(i=>__vite__fileDeps[i]); +/** +* @vue/shared v3.4.26 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function qo(e,t){const n=new Set(e.split(","));return r=>n.has(r)}const he={},un=[],Je=()=>{},Dc=()=>!1,Xn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Ko=e=>e.startsWith("onUpdate:"),Ee=Object.assign,Go=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Bc=Object.prototype.hasOwnProperty,le=(e,t)=>Bc.call(e,t),X=Array.isArray,Ln=e=>Hr(e)==="[object Map]",Fc=e=>Hr(e)==="[object Set]",te=e=>typeof e=="function",Le=e=>typeof e=="string",Zn=e=>typeof e=="symbol",me=e=>e!==null&&typeof e=="object",ea=e=>(me(e)||te(e))&&te(e.then)&&te(e.catch),jc=Object.prototype.toString,Hr=e=>jc.call(e),Vc=e=>Hr(e).slice(8,-1),zc=e=>Hr(e)==="[object Object]",Yo=e=>Le(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,fn=qo(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Dr=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Wc=/-(\w)/g,Ze=Dr(e=>e.replace(Wc,(t,n)=>n?n.toUpperCase():"")),Uc=/\B([A-Z])/g,gn=Dr(e=>e.replace(Uc,"-$1").toLowerCase()),er=Dr(e=>e.charAt(0).toUpperCase()+e.slice(1)),Zr=Dr(e=>e?`on${er(e)}`:""),Ht=(e,t)=>!Object.is(e,t),eo=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},qc=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Kc=e=>{const t=Le(e)?Number(e):NaN;return isNaN(t)?e:t};let Ns;const na=()=>Ns||(Ns=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Jo(e){if(X(e)){const t={};for(let n=0;n{if(n){const r=n.split(Yc);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function Qo(e){let t="";if(Le(e))t=e;else if(X(e))for(let n=0;n=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),Vt()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=$t,n=Jt;try{return $t=!0,Jt=this,this._runnings++,Hs(this),this.fn()}finally{Ds(this),this._runnings--,Jt=n,$t=t}}stop(){this.active&&(Hs(this),Ds(this),this.onStop&&this.onStop(),this.active=!1)}}function ru(e){return e.value}function Hs(e){e._trackId++,e._depsLength=0}function Ds(e){if(e.deps.length>e._depsLength){for(let t=e._depsLength;t{const n=new Map;return n.cleanup=e,n.computed=t,n},Tr=new WeakMap,Qt=Symbol(""),ko=Symbol("");function ze(e,t,n){if($t&&Jt){let r=Tr.get(e);r||Tr.set(e,r=new Map);let o=r.get(n);o||r.set(n,o=ca(()=>r.delete(n))),aa(Jt,o)}}function yt(e,t,n,r,o,s){const l=Tr.get(e);if(!l)return;let a=[];if(t==="clear")a=[...l.values()];else if(n==="length"&&X(e)){const i=Number(r);l.forEach((u,f)=>{(f==="length"||!Zn(f)&&f>=i)&&a.push(u)})}else switch(n!==void 0&&a.push(l.get(n)),t){case"add":X(e)?Yo(n)&&a.push(l.get("length")):(a.push(l.get(Qt)),Ln(e)&&a.push(l.get(ko)));break;case"delete":X(e)||(a.push(l.get(Qt)),Ln(e)&&a.push(l.get(ko)));break;case"set":Ln(e)&&a.push(l.get(Qt));break}Zo();for(const i of a)i&&ia(i,4);es()}function ou(e,t){const n=Tr.get(e);return n&&n.get(t)}const su=qo("__proto__,__v_isRef,__isVue"),ua=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Zn)),Bs=lu();function lu(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const r=ae(this);for(let s=0,l=this.length;s{e[t]=function(...n){jt(),Zo();const r=ae(this)[t].apply(this,n);return es(),Vt(),r}}),e}function au(e){Zn(e)||(e=String(e));const t=ae(this);return ze(t,"has",e),t.hasOwnProperty(e)}class fa{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,r){const o=this._isReadonly,s=this._isShallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return s;if(n==="__v_raw")return r===(o?s?wu:va:s?ha:pa).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(r)?t:void 0;const l=X(t);if(!o){if(l&&le(Bs,n))return Reflect.get(Bs,n,r);if(n==="hasOwnProperty")return au}const a=Reflect.get(t,n,r);return(Zn(n)?ua.has(n):su(n))||(o||ze(t,"get",n),s)?a:Re(a)?l&&Yo(n)?a:a.value:me(a)?o?nr(a):tr(a):a}}class da extends fa{constructor(t=!1){super(!1,t)}set(t,n,r,o){let s=t[n];if(!this._isShallow){const i=Bn(s);if(!Lr(r)&&!Bn(r)&&(s=ae(s),r=ae(r)),!X(t)&&Re(s)&&!Re(r))return i?!1:(s.value=r,!0)}const l=X(t)&&Yo(n)?Number(n)e,Br=e=>Reflect.getPrototypeOf(e);function dr(e,t,n=!1,r=!1){e=e.__v_raw;const o=ae(e),s=ae(t);n||(Ht(t,s)&&ze(o,"get",t),ze(o,"get",s));const{has:l}=Br(o),a=r?ts:n?os:Fn;if(l.call(o,t))return a(e.get(t));if(l.call(o,s))return a(e.get(s));e!==o&&e.get(t)}function pr(e,t=!1){const n=this.__v_raw,r=ae(n),o=ae(e);return t||(Ht(e,o)&&ze(r,"has",e),ze(r,"has",o)),e===o?n.has(e):n.has(e)||n.has(o)}function hr(e,t=!1){return e=e.__v_raw,!t&&ze(ae(e),"iterate",Qt),Reflect.get(e,"size",e)}function Fs(e){e=ae(e);const t=ae(this);return Br(t).has.call(t,e)||(t.add(e),yt(t,"add",e,e)),this}function js(e,t){t=ae(t);const n=ae(this),{has:r,get:o}=Br(n);let s=r.call(n,e);s||(e=ae(e),s=r.call(n,e));const l=o.call(n,e);return n.set(e,t),s?Ht(t,l)&&yt(n,"set",e,t):yt(n,"add",e,t),this}function Vs(e){const t=ae(this),{has:n,get:r}=Br(t);let o=n.call(t,e);o||(e=ae(e),o=n.call(t,e)),r&&r.call(t,e);const s=t.delete(e);return o&&yt(t,"delete",e,void 0),s}function zs(){const e=ae(this),t=e.size!==0,n=e.clear();return t&&yt(e,"clear",void 0,void 0),n}function vr(e,t){return function(r,o){const s=this,l=s.__v_raw,a=ae(l),i=t?ts:e?os:Fn;return!e&&ze(a,"iterate",Qt),l.forEach((u,f)=>r.call(o,i(u),i(f),s))}}function mr(e,t,n){return function(...r){const o=this.__v_raw,s=ae(o),l=Ln(s),a=e==="entries"||e===Symbol.iterator&&l,i=e==="keys"&&l,u=o[e](...r),f=n?ts:t?os:Fn;return!t&&ze(s,"iterate",i?ko:Qt),{next(){const{value:p,done:d}=u.next();return d?{value:p,done:d}:{value:a?[f(p[0]),f(p[1])]:f(p),done:d}},[Symbol.iterator](){return this}}}}function xt(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function du(){const e={get(s){return dr(this,s)},get size(){return hr(this)},has:pr,add:Fs,set:js,delete:Vs,clear:zs,forEach:vr(!1,!1)},t={get(s){return dr(this,s,!1,!0)},get size(){return hr(this)},has:pr,add:Fs,set:js,delete:Vs,clear:zs,forEach:vr(!1,!0)},n={get(s){return dr(this,s,!0)},get size(){return hr(this,!0)},has(s){return pr.call(this,s,!0)},add:xt("add"),set:xt("set"),delete:xt("delete"),clear:xt("clear"),forEach:vr(!0,!1)},r={get(s){return dr(this,s,!0,!0)},get size(){return hr(this,!0)},has(s){return pr.call(this,s,!0)},add:xt("add"),set:xt("set"),delete:xt("delete"),clear:xt("clear"),forEach:vr(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(s=>{e[s]=mr(s,!1,!1),n[s]=mr(s,!0,!1),t[s]=mr(s,!1,!0),r[s]=mr(s,!0,!0)}),[e,n,t,r]}const[pu,hu,vu,mu]=du();function ns(e,t){const n=t?e?mu:vu:e?hu:pu;return(r,o,s)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?r:Reflect.get(le(n,o)&&o in r?n:r,o,s)}const gu={get:ns(!1,!1)},yu={get:ns(!1,!0)},bu={get:ns(!0,!1)};const pa=new WeakMap,ha=new WeakMap,va=new WeakMap,wu=new WeakMap;function _u(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Eu(e){return e.__v_skip||!Object.isExtensible(e)?0:_u(Vc(e))}function tr(e){return Bn(e)?e:rs(e,!1,cu,gu,pa)}function ma(e){return rs(e,!1,fu,yu,ha)}function nr(e){return rs(e,!0,uu,bu,va)}function rs(e,t,n,r,o){if(!me(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=o.get(e);if(s)return s;const l=Eu(e);if(l===0)return e;const a=new Proxy(e,l===2?r:n);return o.set(e,a),a}function Rn(e){return Bn(e)?Rn(e.__v_raw):!!(e&&e.__v_isReactive)}function Bn(e){return!!(e&&e.__v_isReadonly)}function Lr(e){return!!(e&&e.__v_isShallow)}function ga(e){return e?!!e.__v_raw:!1}function ae(e){const t=e&&e.__v_raw;return t?ae(t):e}function Cu(e){return Object.isExtensible(e)&&ta(e,"__v_skip",!0),e}const Fn=e=>me(e)?tr(e):e,os=e=>me(e)?nr(e):e;class ya{constructor(t,n,r,o){this.getter=t,this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new Xo(()=>t(this._value),()=>Pn(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=r}get value(){const t=ae(this);return(!t._cacheable||t.effect.dirty)&&Ht(t._value,t._value=t.effect.run())&&Pn(t,4),ss(t),t.effect._dirtyLevel>=2&&Pn(t,2),t._value}set value(t){this._setter(t)}get _dirty(){return this.effect.dirty}set _dirty(t){this.effect.dirty=t}}function ku(e,t,n=!1){let r,o;const s=te(e);return s?(r=e,o=Je):(r=e.get,o=e.set),new ya(r,o,s||!o,n)}function ss(e){var t;$t&&Jt&&(e=ae(e),aa(Jt,(t=e.dep)!=null?t:e.dep=ca(()=>e.dep=void 0,e instanceof ya?e:void 0)))}function Pn(e,t=4,n){e=ae(e);const r=e.dep;r&&ia(r,t)}function Re(e){return!!(e&&e.__v_isRef===!0)}function Q(e){return ba(e,!1)}function Ne(e){return ba(e,!0)}function ba(e,t){return Re(e)?e:new Su(e,t)}class Su{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:ae(t),this._value=n?t:Fn(t)}get value(){return ss(this),this._value}set value(t){const n=this.__v_isShallow||Lr(t)||Bn(t);t=n?t:ae(t),Ht(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Fn(t),Pn(this,4))}}function Xt(e){return Re(e)?e.value:e}const xu={get:(e,t,n)=>Xt(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return Re(o)&&!Re(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function wa(e){return Rn(e)?e:new Proxy(e,xu)}class Au{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:r}=t(()=>ss(this),()=>Pn(this));this._get=n,this._set=r}get value(){return this._get()}set value(t){this._set(t)}}function ls(e){return new Au(e)}class Tu{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return ou(ae(this._object),this._key)}}class Lu{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function Fr(e,t,n){return Re(e)?e:te(e)?new Lu(e):me(e)&&arguments.length>1?Ru(e,t,n):Q(e)}function Ru(e,t,n){const r=e[t];return Re(r)?r:new Tu(e,t,n)}/** +* @vue/runtime-core v3.4.26 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Nt(e,t,n,r){try{return r?e(...r):e()}catch(o){rr(o,t,n)}}function Xe(e,t,n,r){if(te(e)){const o=Nt(e,t,n,r);return o&&ea(o)&&o.catch(s=>{rr(s,t,n)}),o}if(X(e)){const o=[];for(let s=0;s>>1,o=Oe[r],s=Vn(o);sft&&Oe.splice(t,1)}function Iu(e){X(e)?dn.push(...e):(!Rt||!Rt.includes(e,e.allowRecurse?Kt+1:Kt))&&dn.push(e),Ea()}function Ws(e,t,n=jn?ft+1:0){for(;nVn(n)-Vn(r));if(dn.length=0,Rt){Rt.push(...t);return}for(Rt=t,Kt=0;Kte.id==null?1/0:e.id,$u=(e,t)=>{const n=Vn(e)-Vn(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Ca(e){So=!1,jn=!0,Oe.sort($u);try{for(ft=0;ftLe(v)?v.trim():v)),p&&(o=n.map(qc))}let a,i=r[a=Zr(t)]||r[a=Zr(Ze(t))];!i&&s&&(i=r[a=Zr(gn(t))]),i&&Xe(i,e,6,o);const u=r[a+"Once"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[a])return;e.emitted[a]=!0,Xe(u,e,6,o)}}function ka(e,t,n=!1){const r=t.emitsCache,o=r.get(e);if(o!==void 0)return o;const s=e.emits;let l={},a=!1;if(!te(e)){const i=u=>{const f=ka(u,t,!0);f&&(a=!0,Ee(l,f))};!n&&t.mixins.length&&t.mixins.forEach(i),e.extends&&i(e.extends),e.mixins&&e.mixins.forEach(i)}return!s&&!a?(me(e)&&r.set(e,null),null):(X(s)?s.forEach(i=>l[i]=null):Ee(l,s),me(e)&&r.set(e,l),l)}function Vr(e,t){return!e||!Xn(t)?!1:(t=t.slice(2).replace(/Once$/,""),le(e,t[0].toLowerCase()+t.slice(1))||le(e,gn(t))||le(e,t))}let Qe=null,Sa=null;function Pr(e){const t=Qe;return Qe=e,Sa=e&&e.type.__scopeId||null,t}function Hu(e,t=Qe,n){if(!t||e._n)return e;const r=(...o)=>{r._d&&nl(-1);const s=Pr(t);let l;try{l=e(...o)}finally{Pr(s),r._d&&nl(1)}return l};return r._n=!0,r._c=!0,r._d=!0,r}function to(e){const{type:t,vnode:n,proxy:r,withProxy:o,propsOptions:[s],slots:l,attrs:a,emit:i,render:u,renderCache:f,props:p,data:d,setupState:v,ctx:g,inheritAttrs:E}=e,b=Pr(e);let y,C;try{if(n.shapeFlag&4){const x=o||r,H=x;y=rt(u.call(H,x,f,p,v,d,g)),C=a}else{const x=t;y=rt(x.length>1?x(p,{attrs:a,slots:l,emit:i}):x(p,null)),C=t.props?a:Du(a)}}catch(x){Nn.length=0,rr(x,e,1),y=xe(ot)}let w=y;if(C&&E!==!1){const x=Object.keys(C),{shapeFlag:H}=w;x.length&&H&7&&(s&&x.some(Ko)&&(C=Bu(C,s)),w=Dt(w,C,!1,!0))}return n.dirs&&(w=Dt(w,null,!1,!0),w.dirs=w.dirs?w.dirs.concat(n.dirs):n.dirs),n.transition&&(w.transition=n.transition),y=w,Pr(b),y}const Du=e=>{let t;for(const n in e)(n==="class"||n==="style"||Xn(n))&&((t||(t={}))[n]=e[n]);return t},Bu=(e,t)=>{const n={};for(const r in e)(!Ko(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function Fu(e,t,n){const{props:r,children:o,component:s}=e,{props:l,children:a,patchFlag:i}=t,u=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&i>=0){if(i&1024)return!0;if(i&16)return r?Us(r,l,u):!!l;if(i&8){const f=t.dynamicProps;for(let p=0;pe.__isSuspense;function Aa(e,t){t&&t.pendingBranch?X(e)?t.effects.push(...e):t.effects.push(e):Iu(e)}const Uu=Symbol.for("v-scx"),qu=()=>ke(Uu);function Ta(e,t){return is(e,null,t)}const gr={};function oe(e,t,n){return is(e,t,n)}function is(e,t,{immediate:n,deep:r,flush:o,once:s,onTrack:l,onTrigger:a}=he){if(t&&s){const k=t;t=(...V)=>{k(...V),H()}}const i=Te,u=k=>r===!0?k:an(k,r===!1?1:void 0);let f,p=!1,d=!1;if(Re(e)?(f=()=>e.value,p=Lr(e)):Rn(e)?(f=()=>u(e),p=!0):X(e)?(d=!0,p=e.some(k=>Rn(k)||Lr(k)),f=()=>e.map(k=>{if(Re(k))return k.value;if(Rn(k))return u(k);if(te(k))return Nt(k,i,2)})):te(e)?t?f=()=>Nt(e,i,2):f=()=>(v&&v(),Xe(e,i,3,[g])):f=Je,t&&r){const k=f;f=()=>an(k())}let v,g=k=>{v=w.onStop=()=>{Nt(k,i,4),v=w.onStop=void 0}},E;if(lr)if(g=Je,t?n&&Xe(t,i,3,[f(),d?[]:void 0,g]):f(),o==="sync"){const k=qu();E=k.__watcherHandles||(k.__watcherHandles=[])}else return Je;let b=d?new Array(e.length).fill(gr):gr;const y=()=>{if(!(!w.active||!w.dirty))if(t){const k=w.run();(r||p||(d?k.some((V,N)=>Ht(V,b[N])):Ht(k,b)))&&(v&&v(),Xe(t,i,3,[k,b===gr?void 0:d&&b[0]===gr?[]:b,g]),b=k)}else w.run()};y.allowRecurse=!!t;let C;o==="sync"?C=y:o==="post"?C=()=>Be(y,i&&i.suspense):(y.pre=!0,i&&(y.id=i.uid),C=()=>jr(y));const w=new Xo(f,Je,C),x=oa(),H=()=>{w.stop(),x&&Go(x.effects,w)};return t?n?y():b=w.run():o==="post"?Be(w.run.bind(w),i&&i.suspense):w.run(),E&&E.push(H),H}function Ku(e,t,n){const r=this.proxy,o=Le(e)?e.includes(".")?La(r,e):()=>r[e]:e.bind(r,r);let s;te(t)?s=t:(s=t.handler,n=t);const l=sr(this),a=is(o,s.bind(r),n);return l(),a}function La(e,t){const n=t.split(".");return()=>{let r=e;for(let o=0;o{an(r,t,n)});else if(zc(e))for(const r in e)an(e[r],t,n);return e}function ut(e,t,n,r){const o=e.dirs,s=t&&t.dirs;for(let l=0;l{e.isMounted=!0}),Na(()=>{e.isUnmounting=!0}),e}const Ge=[Function,Array],Pa={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Ge,onEnter:Ge,onAfterEnter:Ge,onEnterCancelled:Ge,onBeforeLeave:Ge,onLeave:Ge,onAfterLeave:Ge,onLeaveCancelled:Ge,onBeforeAppear:Ge,onAppear:Ge,onAfterAppear:Ge,onAppearCancelled:Ge},Gu={name:"BaseTransition",props:Pa,setup(e,{slots:t}){const n=yn(),r=Ra();return()=>{const o=t.default&&cs(t.default(),!0);if(!o||!o.length)return;let s=o[0];if(o.length>1){for(const d of o)if(d.type!==ot){s=d;break}}const l=ae(e),{mode:a}=l;if(r.isLeaving)return no(s);const i=Ks(s);if(!i)return no(s);const u=zn(i,l,r,n);Wn(i,u);const f=n.subTree,p=f&&Ks(f);if(p&&p.type!==ot&&!Gt(i,p)){const d=zn(p,l,r,n);if(Wn(p,d),a==="out-in"&&i.type!==ot)return r.isLeaving=!0,d.afterLeave=()=>{r.isLeaving=!1,n.update.active!==!1&&(n.effect.dirty=!0,n.update())},no(s);a==="in-out"&&i.type!==ot&&(d.delayLeave=(v,g,E)=>{const b=Ma(r,p);b[String(p.key)]=p,v[Pt]=()=>{g(),v[Pt]=void 0,delete u.delayedLeave},u.delayedLeave=E})}return s}}},Yu=Gu;function Ma(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function zn(e,t,n,r){const{appear:o,mode:s,persisted:l=!1,onBeforeEnter:a,onEnter:i,onAfterEnter:u,onEnterCancelled:f,onBeforeLeave:p,onLeave:d,onAfterLeave:v,onLeaveCancelled:g,onBeforeAppear:E,onAppear:b,onAfterAppear:y,onAppearCancelled:C}=t,w=String(e.key),x=Ma(n,e),H=(N,G)=>{N&&Xe(N,r,9,G)},k=(N,G)=>{const P=G[1];H(N,G),X(N)?N.every(Y=>Y.length<=1)&&P():N.length<=1&&P()},V={mode:s,persisted:l,beforeEnter(N){let G=a;if(!n.isMounted)if(o)G=E||a;else return;N[Pt]&&N[Pt](!0);const P=x[w];P&&Gt(e,P)&&P.el[Pt]&&P.el[Pt](),H(G,[N])},enter(N){let G=i,P=u,Y=f;if(!n.isMounted)if(o)G=b||i,P=y||u,Y=C||f;else return;let I=!1;const Z=N[yr]=we=>{I||(I=!0,we?H(Y,[N]):H(P,[N]),V.delayedLeave&&V.delayedLeave(),N[yr]=void 0)};G?k(G,[N,Z]):Z()},leave(N,G){const P=String(e.key);if(N[yr]&&N[yr](!0),n.isUnmounting)return G();H(p,[N]);let Y=!1;const I=N[Pt]=Z=>{Y||(Y=!0,G(),Z?H(g,[N]):H(v,[N]),N[Pt]=void 0,x[P]===e&&delete x[P])};x[P]=e,d?k(d,[N,I]):I()},clone(N){return zn(N,t,n,r)}};return V}function no(e){if(or(e))return e=Dt(e),e.children=null,e}function Ks(e){if(!or(e))return e;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&te(n.default))return n.default()}}function Wn(e,t){e.shapeFlag&6&&e.component?Wn(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function cs(e,t=!1,n){let r=[],o=0;for(let s=0;s1)for(let s=0;s!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function Oa(e){te(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:r,delay:o=200,timeout:s,suspensible:l=!0,onError:a}=e;let i=null,u,f=0;const p=()=>(f++,i=null,d()),d=()=>{let v;return i||(v=i=t().catch(g=>{if(g=g instanceof Error?g:new Error(String(g)),a)return new Promise((E,b)=>{a(g,()=>E(p()),()=>b(g),f+1)});throw g}).then(g=>v!==i&&i?i:(g&&(g.__esModule||g[Symbol.toStringTag]==="Module")&&(g=g.default),u=g,g)))};return K({name:"AsyncComponentWrapper",__asyncLoader:d,get __asyncResolved(){return u},setup(){const v=Te;if(u)return()=>ro(u,v);const g=C=>{i=null,rr(C,v,13,!r)};if(l&&v.suspense||lr)return d().then(C=>()=>ro(C,v)).catch(C=>(g(C),()=>r?xe(r,{error:C}):null));const E=Q(!1),b=Q(),y=Q(!!o);return o&&setTimeout(()=>{y.value=!1},o),s!=null&&setTimeout(()=>{if(!E.value&&!b.value){const C=new Error(`Async component timed out after ${s}ms.`);g(C),b.value=C}},s),d().then(()=>{E.value=!0,v.parent&&or(v.parent.vnode)&&(v.parent.effect.dirty=!0,jr(v.parent.update))}).catch(C=>{g(C),b.value=C}),()=>{if(E.value&&u)return ro(u,v);if(b.value&&r)return xe(r,{error:b.value});if(n&&!y.value)return xe(n)}}})}function ro(e,t){const{ref:n,props:r,children:o,ce:s}=t.vnode,l=xe(e,r,o);return l.ref=n,l.ce=s,delete t.vnode.ce,l}const or=e=>e.type.__isKeepAlive;function Ju(e,t){Ia(e,"a",t)}function Qu(e,t){Ia(e,"da",t)}function Ia(e,t,n=Te){const r=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(zr(t,r,n),n){let o=n.parent;for(;o&&o.parent;)or(o.parent.vnode)&&Xu(r,t,n,o),o=o.parent}}function Xu(e,t,n,r){const o=zr(t,e,r,!0);en(()=>{Go(r[t],o)},n)}function zr(e,t,n=Te,r=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...l)=>{if(n.isUnmounted)return;jt();const a=sr(n),i=Xe(t,n,e,l);return a(),Vt(),i});return r?o.unshift(s):o.push(s),s}}const _t=e=>(t,n=Te)=>(!lr||e==="sp")&&zr(e,(...r)=>t(...r),n),Zu=_t("bm"),ye=_t("m"),ef=_t("bu"),$a=_t("u"),Na=_t("bum"),en=_t("um"),tf=_t("sp"),nf=_t("rtg"),rf=_t("rtc");function of(e,t=Te){zr("ec",e,t)}const xo=e=>e?Za(e)?ps(e)||e.proxy:xo(e.parent):null,On=Ee(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>xo(e.parent),$root:e=>xo(e.root),$emit:e=>e.emit,$options:e=>us(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,jr(e.update)}),$nextTick:e=>e.n||(e.n=zt.bind(e.proxy)),$watch:e=>Ku.bind(e)}),oo=(e,t)=>e!==he&&!e.__isScriptSetup&&le(e,t),sf={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:r,data:o,props:s,accessCache:l,type:a,appContext:i}=e;let u;if(t[0]!=="$"){const v=l[t];if(v!==void 0)switch(v){case 1:return r[t];case 2:return o[t];case 4:return n[t];case 3:return s[t]}else{if(oo(r,t))return l[t]=1,r[t];if(o!==he&&le(o,t))return l[t]=2,o[t];if((u=e.propsOptions[0])&&le(u,t))return l[t]=3,s[t];if(n!==he&&le(n,t))return l[t]=4,n[t];Ao&&(l[t]=0)}}const f=On[t];let p,d;if(f)return t==="$attrs"&&ze(e.attrs,"get",""),f(e);if((p=a.__cssModules)&&(p=p[t]))return p;if(n!==he&&le(n,t))return l[t]=4,n[t];if(d=i.config.globalProperties,le(d,t))return d[t]},set({_:e},t,n){const{data:r,setupState:o,ctx:s}=e;return oo(o,t)?(o[t]=n,!0):r!==he&&le(r,t)?(r[t]=n,!0):le(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,propsOptions:s}},l){let a;return!!n[l]||e!==he&&le(e,l)||oo(t,l)||(a=s[0])&&le(a,l)||le(r,l)||le(On,l)||le(o.config.globalProperties,l)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:le(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Gs(e){return X(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Ao=!0;function lf(e){const t=us(e),n=e.proxy,r=e.ctx;Ao=!1,t.beforeCreate&&Ys(t.beforeCreate,e,"bc");const{data:o,computed:s,methods:l,watch:a,provide:i,inject:u,created:f,beforeMount:p,mounted:d,beforeUpdate:v,updated:g,activated:E,deactivated:b,beforeDestroy:y,beforeUnmount:C,destroyed:w,unmounted:x,render:H,renderTracked:k,renderTriggered:V,errorCaptured:N,serverPrefetch:G,expose:P,inheritAttrs:Y,components:I,directives:Z,filters:we}=t;if(u&&af(u,r,null),l)for(const ne in l){const W=l[ne];te(W)&&(r[ne]=W.bind(n))}if(o){const ne=o.call(n,n);me(ne)&&(e.data=tr(ne))}if(Ao=!0,s)for(const ne in s){const W=s[ne],tt=te(W)?W.bind(n,n):te(W.get)?W.get.bind(n,n):Je,St=!te(W)&&te(W.set)?W.set.bind(n):Je,it=S({get:tt,set:St});Object.defineProperty(r,ne,{enumerable:!0,configurable:!0,get:()=>it.value,set:De=>it.value=De})}if(a)for(const ne in a)Ha(a[ne],r,n,ne);if(i){const ne=te(i)?i.call(n):i;Reflect.ownKeys(ne).forEach(W=>{Zt(W,ne[W])})}f&&Ys(f,e,"c");function U(ne,W){X(W)?W.forEach(tt=>ne(tt.bind(n))):W&&ne(W.bind(n))}if(U(Zu,p),U(ye,d),U(ef,v),U($a,g),U(Ju,E),U(Qu,b),U(of,N),U(rf,k),U(nf,V),U(Na,C),U(en,x),U(tf,G),X(P))if(P.length){const ne=e.exposed||(e.exposed={});P.forEach(W=>{Object.defineProperty(ne,W,{get:()=>n[W],set:tt=>n[W]=tt})})}else e.exposed||(e.exposed={});H&&e.render===Je&&(e.render=H),Y!=null&&(e.inheritAttrs=Y),I&&(e.components=I),Z&&(e.directives=Z)}function af(e,t,n=Je){X(e)&&(e=To(e));for(const r in e){const o=e[r];let s;me(o)?"default"in o?s=ke(o.from||r,o.default,!0):s=ke(o.from||r):s=ke(o),Re(s)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>s.value,set:l=>s.value=l}):t[r]=s}}function Ys(e,t,n){Xe(X(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function Ha(e,t,n,r){const o=r.includes(".")?La(n,r):()=>n[r];if(Le(e)){const s=t[e];te(s)&&oe(o,s)}else if(te(e))oe(o,e.bind(n));else if(me(e))if(X(e))e.forEach(s=>Ha(s,t,n,r));else{const s=te(e.handler)?e.handler.bind(n):t[e.handler];te(s)&&oe(o,s,e)}}function us(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:o,optionsCache:s,config:{optionMergeStrategies:l}}=e.appContext,a=s.get(t);let i;return a?i=a:!o.length&&!n&&!r?i=t:(i={},o.length&&o.forEach(u=>Mr(i,u,l,!0)),Mr(i,t,l)),me(t)&&s.set(t,i),i}function Mr(e,t,n,r=!1){const{mixins:o,extends:s}=t;s&&Mr(e,s,n,!0),o&&o.forEach(l=>Mr(e,l,n,!0));for(const l in t)if(!(r&&l==="expose")){const a=cf[l]||n&&n[l];e[l]=a?a(e[l],t[l]):t[l]}return e}const cf={data:Js,props:Qs,emits:Qs,methods:An,computed:An,beforeCreate:Ie,created:Ie,beforeMount:Ie,mounted:Ie,beforeUpdate:Ie,updated:Ie,beforeDestroy:Ie,beforeUnmount:Ie,destroyed:Ie,unmounted:Ie,activated:Ie,deactivated:Ie,errorCaptured:Ie,serverPrefetch:Ie,components:An,directives:An,watch:ff,provide:Js,inject:uf};function Js(e,t){return t?e?function(){return Ee(te(e)?e.call(this,this):e,te(t)?t.call(this,this):t)}:t:e}function uf(e,t){return An(To(e),To(t))}function To(e){if(X(e)){const t={};for(let n=0;n1)return n&&te(t)?t.call(r&&r.proxy):t}}const Ba={},Fa=()=>Object.create(Ba),ja=e=>Object.getPrototypeOf(e)===Ba;function hf(e,t,n,r=!1){const o={},s=Fa();e.propsDefaults=Object.create(null),Va(e,t,o,s);for(const l in e.propsOptions[0])l in o||(o[l]=void 0);n?e.props=r?o:ma(o):e.type.props?e.props=o:e.props=s,e.attrs=s}function vf(e,t,n,r){const{props:o,attrs:s,vnode:{patchFlag:l}}=e,a=ae(o),[i]=e.propsOptions;let u=!1;if((r||l>0)&&!(l&16)){if(l&8){const f=e.vnode.dynamicProps;for(let p=0;p{i=!0;const[d,v]=za(p,t,!0);Ee(l,d),v&&a.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(f),e.extends&&f(e.extends),e.mixins&&e.mixins.forEach(f)}if(!s&&!i)return me(e)&&r.set(e,un),un;if(X(s))for(let f=0;f-1,v[1]=E<0||g-1||le(v,"default"))&&a.push(p)}}}const u=[l,a];return me(e)&&r.set(e,u),u}function Xs(e){return e[0]!=="$"&&!fn(e)}function Zs(e){return e===null?"null":typeof e=="function"?e.name||"":typeof e=="object"&&e.constructor&&e.constructor.name||""}function el(e,t){return Zs(e)===Zs(t)}function tl(e,t){return X(t)?t.findIndex(n=>el(n,e)):te(t)&&el(t,e)?0:-1}const Wa=e=>e[0]==="_"||e==="$stable",fs=e=>X(e)?e.map(rt):[rt(e)],mf=(e,t,n)=>{if(t._n)return t;const r=Hu((...o)=>fs(t(...o)),n);return r._c=!1,r},Ua=(e,t,n)=>{const r=e._ctx;for(const o in e){if(Wa(o))continue;const s=e[o];if(te(s))t[o]=mf(o,s,r);else if(s!=null){const l=fs(s);t[o]=()=>l}}},qa=(e,t)=>{const n=fs(t);e.slots.default=()=>n},gf=(e,t)=>{const n=e.slots=Fa();if(e.vnode.shapeFlag&32){const r=t._;r?(Ee(n,t),ta(n,"_",r,!0)):Ua(t,n)}else t&&qa(e,t)},yf=(e,t,n)=>{const{vnode:r,slots:o}=e;let s=!0,l=he;if(r.shapeFlag&32){const a=t._;a?n&&a===1?s=!1:(Ee(o,t),!n&&a===1&&delete o._):(s=!t.$stable,Ua(t,o)),l=t}else t&&(qa(e,t),l={default:1});if(s)for(const a in o)!Wa(a)&&l[a]==null&&delete o[a]};function Or(e,t,n,r,o=!1){if(X(e)){e.forEach((d,v)=>Or(d,t&&(X(t)?t[v]:t),n,r,o));return}if(Mn(r)&&!o)return;const s=r.shapeFlag&4?ps(r.component)||r.component.proxy:r.el,l=o?null:s,{i:a,r:i}=e,u=t&&t.r,f=a.refs===he?a.refs={}:a.refs,p=a.setupState;if(u!=null&&u!==i&&(Le(u)?(f[u]=null,le(p,u)&&(p[u]=null)):Re(u)&&(u.value=null)),te(i))Nt(i,a,12,[l,f]);else{const d=Le(i),v=Re(i);if(d||v){const g=()=>{if(e.f){const E=d?le(p,i)?p[i]:f[i]:i.value;o?X(E)&&Go(E,s):X(E)?E.includes(s)||E.push(s):d?(f[i]=[s],le(p,i)&&(p[i]=f[i])):(i.value=[s],e.k&&(f[e.k]=i.value))}else d?(f[i]=l,le(p,i)&&(p[i]=l)):v&&(i.value=l,e.k&&(f[e.k]=l))};l?(g.id=-1,Be(g,n)):g()}}}let At=!1;const bf=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",wf=e=>e.namespaceURI.includes("MathML"),br=e=>{if(bf(e))return"svg";if(wf(e))return"mathml"},wr=e=>e.nodeType===8;function _f(e){const{mt:t,p:n,o:{patchProp:r,createText:o,nextSibling:s,parentNode:l,remove:a,insert:i,createComment:u}}=e,f=(w,x)=>{if(!x.hasChildNodes()){n(null,w,x),Rr(),x._vnode=w;return}At=!1,p(x.firstChild,w,null,null,null),Rr(),x._vnode=w,At&&console.error("Hydration completed but contains mismatches.")},p=(w,x,H,k,V,N=!1)=>{N=N||!!x.dynamicChildren;const G=wr(w)&&w.data==="[",P=()=>E(w,x,H,k,V,G),{type:Y,ref:I,shapeFlag:Z,patchFlag:we}=x;let _e=w.nodeType;x.el=w,we===-2&&(N=!1,x.dynamicChildren=null);let U=null;switch(Y){case pn:_e!==3?x.children===""?(i(x.el=o(""),l(w),w),U=w):U=P():(w.data!==x.children&&(At=!0,w.data=x.children),U=s(w));break;case ot:C(w)?(U=s(w),y(x.el=w.content.firstChild,w,H)):_e!==8||G?U=P():U=s(w);break;case $n:if(G&&(w=s(w),_e=w.nodeType),_e===1||_e===3){U=w;const ne=!x.children.length;for(let W=0;W{N=N||!!x.dynamicChildren;const{type:G,props:P,patchFlag:Y,shapeFlag:I,dirs:Z,transition:we}=x,_e=G==="input"||G==="option";if(_e||Y!==-1){Z&&ut(x,null,H,"created");let U=!1;if(C(w)){U=Ka(k,we)&&H&&H.vnode.props&&H.vnode.props.appear;const W=w.content.firstChild;U&&we.beforeEnter(W),y(W,w,H),x.el=w=W}if(I&16&&!(P&&(P.innerHTML||P.textContent))){let W=v(w.firstChild,x,w,H,k,V,N);for(;W;){At=!0;const tt=W;W=W.nextSibling,a(tt)}}else I&8&&w.textContent!==x.children&&(At=!0,w.textContent=x.children);if(P)if(_e||!N||Y&48)for(const W in P)(_e&&(W.endsWith("value")||W==="indeterminate")||Xn(W)&&!fn(W)||W[0]===".")&&r(w,W,null,P[W],void 0,void 0,H);else P.onClick&&r(w,"onClick",null,P.onClick,void 0,void 0,H);let ne;(ne=P&&P.onVnodeBeforeMount)&&Ye(ne,H,x),Z&&ut(x,null,H,"beforeMount"),((ne=P&&P.onVnodeMounted)||Z||U)&&Aa(()=>{ne&&Ye(ne,H,x),U&&we.enter(w),Z&&ut(x,null,H,"mounted")},k)}return w.nextSibling},v=(w,x,H,k,V,N,G)=>{G=G||!!x.dynamicChildren;const P=x.children,Y=P.length;for(let I=0;I{const{slotScopeIds:G}=x;G&&(V=V?V.concat(G):G);const P=l(w),Y=v(s(w),x,P,H,k,V,N);return Y&&wr(Y)&&Y.data==="]"?s(x.anchor=Y):(At=!0,i(x.anchor=u("]"),P,Y),Y)},E=(w,x,H,k,V,N)=>{if(At=!0,x.el=null,N){const Y=b(w);for(;;){const I=s(w);if(I&&I!==Y)a(I);else break}}const G=s(w),P=l(w);return a(w),n(null,x,P,G,H,k,br(P),V),G},b=(w,x="[",H="]")=>{let k=0;for(;w;)if(w=s(w),w&&wr(w)&&(w.data===x&&k++,w.data===H)){if(k===0)return s(w);k--}return w},y=(w,x,H)=>{const k=x.parentNode;k&&k.replaceChild(w,x);let V=H;for(;V;)V.vnode.el===x&&(V.vnode.el=V.subTree.el=w),V=V.parent},C=w=>w.nodeType===1&&w.tagName.toLowerCase()==="template";return[f,p]}const Be=Aa;function Ef(e){return Cf(e,_f)}function Cf(e,t){const n=na();n.__VUE__=!0;const{insert:r,remove:o,patchProp:s,createElement:l,createText:a,createComment:i,setText:u,setElementText:f,parentNode:p,nextSibling:d,setScopeId:v=Je,insertStaticContent:g}=e,E=(h,m,_,L=null,A=null,O=null,B=void 0,M=null,$=!!m.dynamicChildren)=>{if(h===m)return;h&&!Gt(h,m)&&(L=T(h),De(h,A,O,!0),h=null),m.patchFlag===-2&&($=!1,m.dynamicChildren=null);const{type:R,ref:j,shapeFlag:J}=m;switch(R){case pn:b(h,m,_,L);break;case ot:y(h,m,_,L);break;case $n:h==null&&C(m,_,L,B);break;case qe:I(h,m,_,L,A,O,B,M,$);break;default:J&1?H(h,m,_,L,A,O,B,M,$):J&6?Z(h,m,_,L,A,O,B,M,$):(J&64||J&128)&&R.process(h,m,_,L,A,O,B,M,$,z)}j!=null&&A&&Or(j,h&&h.ref,O,m||h,!m)},b=(h,m,_,L)=>{if(h==null)r(m.el=a(m.children),_,L);else{const A=m.el=h.el;m.children!==h.children&&u(A,m.children)}},y=(h,m,_,L)=>{h==null?r(m.el=i(m.children||""),_,L):m.el=h.el},C=(h,m,_,L)=>{[h.el,h.anchor]=g(h.children,m,_,L,h.el,h.anchor)},w=({el:h,anchor:m},_,L)=>{let A;for(;h&&h!==m;)A=d(h),r(h,_,L),h=A;r(m,_,L)},x=({el:h,anchor:m})=>{let _;for(;h&&h!==m;)_=d(h),o(h),h=_;o(m)},H=(h,m,_,L,A,O,B,M,$)=>{m.type==="svg"?B="svg":m.type==="math"&&(B="mathml"),h==null?k(m,_,L,A,O,B,M,$):G(h,m,A,O,B,M,$)},k=(h,m,_,L,A,O,B,M)=>{let $,R;const{props:j,shapeFlag:J,transition:q,dirs:ee}=h;if($=h.el=l(h.type,O,j&&j.is,j),J&8?f($,h.children):J&16&&N(h.children,$,null,L,A,so(h,O),B,M),ee&&ut(h,null,L,"created"),V($,h,h.scopeId,B,L),j){for(const ue in j)ue!=="value"&&!fn(ue)&&s($,ue,null,j[ue],O,h.children,L,A,Pe);"value"in j&&s($,"value",null,j.value,O),(R=j.onVnodeBeforeMount)&&Ye(R,L,h)}ee&&ut(h,null,L,"beforeMount");const re=Ka(A,q);re&&q.beforeEnter($),r($,m,_),((R=j&&j.onVnodeMounted)||re||ee)&&Be(()=>{R&&Ye(R,L,h),re&&q.enter($),ee&&ut(h,null,L,"mounted")},A)},V=(h,m,_,L,A)=>{if(_&&v(h,_),L)for(let O=0;O{for(let R=$;R{const M=m.el=h.el;let{patchFlag:$,dynamicChildren:R,dirs:j}=m;$|=h.patchFlag&16;const J=h.props||he,q=m.props||he;let ee;if(_&&Wt(_,!1),(ee=q.onVnodeBeforeUpdate)&&Ye(ee,_,m,h),j&&ut(m,h,_,"beforeUpdate"),_&&Wt(_,!0),R?P(h.dynamicChildren,R,M,_,L,so(m,A),O):B||W(h,m,M,null,_,L,so(m,A),O,!1),$>0){if($&16)Y(M,m,J,q,_,L,A);else if($&2&&J.class!==q.class&&s(M,"class",null,q.class,A),$&4&&s(M,"style",J.style,q.style,A),$&8){const re=m.dynamicProps;for(let ue=0;ue{ee&&Ye(ee,_,m,h),j&&ut(m,h,_,"updated")},L)},P=(h,m,_,L,A,O,B)=>{for(let M=0;M{if(_!==L){if(_!==he)for(const M in _)!fn(M)&&!(M in L)&&s(h,M,_[M],null,B,m.children,A,O,Pe);for(const M in L){if(fn(M))continue;const $=L[M],R=_[M];$!==R&&M!=="value"&&s(h,M,R,$,B,m.children,A,O,Pe)}"value"in L&&s(h,"value",_.value,L.value,B)}},I=(h,m,_,L,A,O,B,M,$)=>{const R=m.el=h?h.el:a(""),j=m.anchor=h?h.anchor:a("");let{patchFlag:J,dynamicChildren:q,slotScopeIds:ee}=m;ee&&(M=M?M.concat(ee):ee),h==null?(r(R,_,L),r(j,_,L),N(m.children||[],_,j,A,O,B,M,$)):J>0&&J&64&&q&&h.dynamicChildren?(P(h.dynamicChildren,q,_,A,O,B,M),(m.key!=null||A&&m===A.subTree)&&Ga(h,m,!0)):W(h,m,_,j,A,O,B,M,$)},Z=(h,m,_,L,A,O,B,M,$)=>{m.slotScopeIds=M,h==null?m.shapeFlag&512?A.ctx.activate(m,_,L,B,$):we(m,_,L,A,O,B,$):_e(h,m,$)},we=(h,m,_,L,A,O,B)=>{const M=h.component=Of(h,L,A);if(or(h)&&(M.ctx.renderer=z),If(M),M.asyncDep){if(A&&A.registerDep(M,U),!h.el){const $=M.subTree=xe(ot);y(null,$,m,_)}}else U(M,h,m,_,A,O,B)},_e=(h,m,_)=>{const L=m.component=h.component;if(Fu(h,m,_))if(L.asyncDep&&!L.asyncResolved){ne(L,m,_);return}else L.next=m,Ou(L.update),L.effect.dirty=!0,L.update();else m.el=h.el,L.vnode=m},U=(h,m,_,L,A,O,B)=>{const M=()=>{if(h.isMounted){let{next:j,bu:J,u:q,parent:ee,vnode:re}=h;{const sn=Ya(h);if(sn){j&&(j.el=re.el,ne(h,j,B)),sn.asyncDep.then(()=>{h.isUnmounted||M()});return}}let ue=j,pe;Wt(h,!1),j?(j.el=re.el,ne(h,j,B)):j=re,J&&eo(J),(pe=j.props&&j.props.onVnodeBeforeUpdate)&&Ye(pe,ee,j,re),Wt(h,!0);const Ae=to(h),nt=h.subTree;h.subTree=Ae,E(nt,Ae,p(nt.el),T(nt),h,A,O),j.el=Ae.el,ue===null&&ju(h,Ae.el),q&&Be(q,A),(pe=j.props&&j.props.onVnodeUpdated)&&Be(()=>Ye(pe,ee,j,re),A)}else{let j;const{el:J,props:q}=m,{bm:ee,m:re,parent:ue}=h,pe=Mn(m);if(Wt(h,!1),ee&&eo(ee),!pe&&(j=q&&q.onVnodeBeforeMount)&&Ye(j,ue,m),Wt(h,!0),J&&de){const Ae=()=>{h.subTree=to(h),de(J,h.subTree,h,A,null)};pe?m.type.__asyncLoader().then(()=>!h.isUnmounted&&Ae()):Ae()}else{const Ae=h.subTree=to(h);E(null,Ae,_,L,h,A,O),m.el=Ae.el}if(re&&Be(re,A),!pe&&(j=q&&q.onVnodeMounted)){const Ae=m;Be(()=>Ye(j,ue,Ae),A)}(m.shapeFlag&256||ue&&Mn(ue.vnode)&&ue.vnode.shapeFlag&256)&&h.a&&Be(h.a,A),h.isMounted=!0,m=_=L=null}},$=h.effect=new Xo(M,Je,()=>jr(R),h.scope),R=h.update=()=>{$.dirty&&$.run()};R.id=h.uid,Wt(h,!0),R()},ne=(h,m,_)=>{m.component=h;const L=h.vnode.props;h.vnode=m,h.next=null,vf(h,m.props,L,_),yf(h,m.children,_),jt(),Ws(h),Vt()},W=(h,m,_,L,A,O,B,M,$=!1)=>{const R=h&&h.children,j=h?h.shapeFlag:0,J=m.children,{patchFlag:q,shapeFlag:ee}=m;if(q>0){if(q&128){St(R,J,_,L,A,O,B,M,$);return}else if(q&256){tt(R,J,_,L,A,O,B,M,$);return}}ee&8?(j&16&&Pe(R,A,O),J!==R&&f(_,J)):j&16?ee&16?St(R,J,_,L,A,O,B,M,$):Pe(R,A,O,!0):(j&8&&f(_,""),ee&16&&N(J,_,L,A,O,B,M,$))},tt=(h,m,_,L,A,O,B,M,$)=>{h=h||un,m=m||un;const R=h.length,j=m.length,J=Math.min(R,j);let q;for(q=0;qj?Pe(h,A,O,!0,!1,J):N(m,_,L,A,O,B,M,$,J)},St=(h,m,_,L,A,O,B,M,$)=>{let R=0;const j=m.length;let J=h.length-1,q=j-1;for(;R<=J&&R<=q;){const ee=h[R],re=m[R]=$?Mt(m[R]):rt(m[R]);if(Gt(ee,re))E(ee,re,_,null,A,O,B,M,$);else break;R++}for(;R<=J&&R<=q;){const ee=h[J],re=m[q]=$?Mt(m[q]):rt(m[q]);if(Gt(ee,re))E(ee,re,_,null,A,O,B,M,$);else break;J--,q--}if(R>J){if(R<=q){const ee=q+1,re=eeq)for(;R<=J;)De(h[R],A,O,!0),R++;else{const ee=R,re=R,ue=new Map;for(R=re;R<=q;R++){const We=m[R]=$?Mt(m[R]):rt(m[R]);We.key!=null&&ue.set(We.key,R)}let pe,Ae=0;const nt=q-re+1;let sn=!1,Os=0;const Cn=new Array(nt);for(R=0;R=nt){De(We,A,O,!0);continue}let ct;if(We.key!=null)ct=ue.get(We.key);else for(pe=re;pe<=q;pe++)if(Cn[pe-re]===0&&Gt(We,m[pe])){ct=pe;break}ct===void 0?De(We,A,O,!0):(Cn[ct-re]=R+1,ct>=Os?Os=ct:sn=!0,E(We,m[ct],_,null,A,O,B,M,$),Ae++)}const Is=sn?kf(Cn):un;for(pe=Is.length-1,R=nt-1;R>=0;R--){const We=re+R,ct=m[We],$s=We+1{const{el:O,type:B,transition:M,children:$,shapeFlag:R}=h;if(R&6){it(h.component.subTree,m,_,L);return}if(R&128){h.suspense.move(m,_,L);return}if(R&64){B.move(h,m,_,z);return}if(B===qe){r(O,m,_);for(let J=0;J<$.length;J++)it($[J],m,_,L);r(h.anchor,m,_);return}if(B===$n){w(h,m,_);return}if(L!==2&&R&1&&M)if(L===0)M.beforeEnter(O),r(O,m,_),Be(()=>M.enter(O),A);else{const{leave:J,delayLeave:q,afterLeave:ee}=M,re=()=>r(O,m,_),ue=()=>{J(O,()=>{re(),ee&&ee()})};q?q(O,re,ue):ue()}else r(O,m,_)},De=(h,m,_,L=!1,A=!1)=>{const{type:O,props:B,ref:M,children:$,dynamicChildren:R,shapeFlag:j,patchFlag:J,dirs:q}=h;if(M!=null&&Or(M,null,_,h,!0),j&256){m.ctx.deactivate(h);return}const ee=j&1&&q,re=!Mn(h);let ue;if(re&&(ue=B&&B.onVnodeBeforeUnmount)&&Ye(ue,m,h),j&6)fr(h.component,_,L);else{if(j&128){h.suspense.unmount(_,L);return}ee&&ut(h,null,m,"beforeUnmount"),j&64?h.type.remove(h,m,_,A,z,L):R&&(O!==qe||J>0&&J&64)?Pe(R,m,_,!1,!0):(O===qe&&J&384||!A&&j&16)&&Pe($,m,_),L&&rn(h)}(re&&(ue=B&&B.onVnodeUnmounted)||ee)&&Be(()=>{ue&&Ye(ue,m,h),ee&&ut(h,null,m,"unmounted")},_)},rn=h=>{const{type:m,el:_,anchor:L,transition:A}=h;if(m===qe){on(_,L);return}if(m===$n){x(h);return}const O=()=>{o(_),A&&!A.persisted&&A.afterLeave&&A.afterLeave()};if(h.shapeFlag&1&&A&&!A.persisted){const{leave:B,delayLeave:M}=A,$=()=>B(_,O);M?M(h.el,O,$):$()}else O()},on=(h,m)=>{let _;for(;h!==m;)_=d(h),o(h),h=_;o(m)},fr=(h,m,_)=>{const{bum:L,scope:A,update:O,subTree:B,um:M}=h;L&&eo(L),A.stop(),O&&(O.active=!1,De(B,h,m,_)),M&&Be(M,m),Be(()=>{h.isUnmounted=!0},m),m&&m.pendingBranch&&!m.isUnmounted&&h.asyncDep&&!h.asyncResolved&&h.suspenseId===m.pendingId&&(m.deps--,m.deps===0&&m.resolve())},Pe=(h,m,_,L=!1,A=!1,O=0)=>{for(let B=O;Bh.shapeFlag&6?T(h.component.subTree):h.shapeFlag&128?h.suspense.next():d(h.anchor||h.el);let F=!1;const D=(h,m,_)=>{h==null?m._vnode&&De(m._vnode,null,null,!0):E(m._vnode||null,h,m,null,null,null,_),F||(F=!0,Ws(),Rr(),F=!1),m._vnode=h},z={p:E,um:De,m:it,r:rn,mt:we,mc:N,pc:W,pbc:P,n:T,o:e};let ie,de;return t&&([ie,de]=t(z)),{render:D,hydrate:ie,createApp:pf(D,ie)}}function so({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function Wt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Ka(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Ga(e,t,n=!1){const r=e.children,o=t.children;if(X(r)&&X(o))for(let s=0;s>1,e[n[a]]0&&(t[r]=n[s-1]),n[s]=r)}}for(s=n.length,l=n[s-1];s-- >0;)n[s]=l,l=t[l];return n}function Ya(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Ya(t)}const Sf=e=>e.__isTeleport,qe=Symbol.for("v-fgt"),pn=Symbol.for("v-txt"),ot=Symbol.for("v-cmt"),$n=Symbol.for("v-stc"),Nn=[];let st=null;function Bv(e=!1){Nn.push(st=e?null:[])}function xf(){Nn.pop(),st=Nn[Nn.length-1]||null}let Un=1;function nl(e){Un+=e}function Af(e){return e.dynamicChildren=Un>0?st||un:null,xf(),Un>0&&st&&st.push(e),e}function Fv(e,t,n,r,o,s){return Af(Qa(e,t,n,r,o,s,!0))}function Ro(e){return e?e.__v_isVNode===!0:!1}function Gt(e,t){return e.type===t.type&&e.key===t.key}const Ja=({key:e})=>e??null,xr=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Le(e)||Re(e)||te(e)?{i:Qe,r:e,k:t,f:!!n}:e:null);function Qa(e,t=null,n=null,r=0,o=null,s=e===qe?0:1,l=!1,a=!1){const i={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ja(t),ref:t&&xr(t),scopeId:Sa,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:r,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Qe};return a?(ds(i,n),s&128&&e.normalize(i)):n&&(i.shapeFlag|=Le(n)?8:16),Un>0&&!l&&st&&(i.patchFlag>0||s&6)&&i.patchFlag!==32&&st.push(i),i}const xe=Tf;function Tf(e,t=null,n=null,r=0,o=null,s=!1){if((!e||e===Vu)&&(e=ot),Ro(e)){const a=Dt(e,t,!0);return n&&ds(a,n),Un>0&&!s&&st&&(a.shapeFlag&6?st[st.indexOf(e)]=a:st.push(a)),a.patchFlag|=-2,a}if(Bf(e)&&(e=e.__vccOpts),t){t=Lf(t);let{class:a,style:i}=t;a&&!Le(a)&&(t.class=Qo(a)),me(i)&&(ga(i)&&!X(i)&&(i=Ee({},i)),t.style=Jo(i))}const l=Le(e)?1:Wu(e)?128:Sf(e)?64:me(e)?4:te(e)?2:0;return Qa(e,t,n,r,o,l,s,!0)}function Lf(e){return e?ga(e)||ja(e)?Ee({},e):e:null}function Dt(e,t,n=!1,r=!1){const{props:o,ref:s,patchFlag:l,children:a,transition:i}=e,u=t?Rf(o||{},t):o,f={__v_isVNode:!0,__v_skip:!0,type:e.type,props:u,key:u&&Ja(u),ref:t&&t.ref?n&&s?X(s)?s.concat(xr(t)):[s,xr(t)]:xr(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:a,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==qe?l===-1?16:l|16:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:i,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Dt(e.ssContent),ssFallback:e.ssFallback&&Dt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return i&&r&&(f.transition=i.clone(f)),f}function Xa(e=" ",t=0){return xe(pn,null,e,t)}function jv(e,t){const n=xe($n,null,e);return n.staticCount=t,n}function rt(e){return e==null||typeof e=="boolean"?xe(ot):X(e)?xe(qe,null,e.slice()):typeof e=="object"?Mt(e):xe(pn,null,String(e))}function Mt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Dt(e)}function ds(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(X(t))n=16;else if(typeof t=="object")if(r&65){const o=t.default;o&&(o._c&&(o._d=!1),ds(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!ja(t)?t._ctx=Qe:o===3&&Qe&&(Qe.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else te(t)?(t={default:t,_ctx:Qe},n=32):(t=String(t),r&64?(n=16,t=[Xa(t)]):n=8);e.children=t,e.shapeFlag|=n}function Rf(...e){const t={};for(let n=0;nTe||Qe;let Ir,Po;{const e=na(),t=(n,r)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(r),s=>{o.length>1?o.forEach(l=>l(s)):o[0](s)}};Ir=t("__VUE_INSTANCE_SETTERS__",n=>Te=n),Po=t("__VUE_SSR_SETTERS__",n=>lr=n)}const sr=e=>{const t=Te;return Ir(e),e.scope.on(),()=>{e.scope.off(),Ir(t)}},rl=()=>{Te&&Te.scope.off(),Ir(null)};function Za(e){return e.vnode.shapeFlag&4}let lr=!1;function If(e,t=!1){t&&Po(t);const{props:n,children:r}=e.vnode,o=Za(e);hf(e,n,o,t),gf(e,r);const s=o?$f(e,t):void 0;return t&&Po(!1),s}function $f(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,sf);const{setup:r}=n;if(r){const o=e.setupContext=r.length>1?Hf(e):null,s=sr(e);jt();const l=Nt(r,e,0,[e.props,o]);if(Vt(),s(),ea(l)){if(l.then(rl,rl),t)return l.then(a=>{ol(e,a,t)}).catch(a=>{rr(a,e,0)});e.asyncDep=l}else ol(e,l,t)}else ei(e,t)}function ol(e,t,n){te(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:me(t)&&(e.setupState=wa(t)),ei(e,n)}let sl;function ei(e,t,n){const r=e.type;if(!e.render){if(!t&&sl&&!r.render){const o=r.template||us(e).template;if(o){const{isCustomElement:s,compilerOptions:l}=e.appContext.config,{delimiters:a,compilerOptions:i}=r,u=Ee(Ee({isCustomElement:s,delimiters:a},l),i);r.render=sl(o,u)}}e.render=r.render||Je}{const o=sr(e);jt();try{lf(e)}finally{Vt(),o()}}}const Nf={get(e,t){return ze(e,"get",""),e[t]}};function Hf(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Nf),slots:e.slots,emit:e.emit,expose:t}}function ps(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(wa(Cu(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in On)return On[n](e)},has(t,n){return n in t||n in On}}))}function Df(e,t=!0){return te(e)?e.displayName||e.name:e.name||t&&e.__name}function Bf(e){return te(e)&&"__vccOpts"in e}const S=(e,t)=>ku(e,t,lr);function c(e,t,n){const r=arguments.length;return r===2?me(t)&&!X(t)?Ro(t)?xe(e,null,[t]):xe(e,t):xe(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&Ro(n)&&(n=[n]),xe(e,t,n))}const Ff="3.4.26";/** +* @vue/runtime-dom v3.4.26 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/const jf="http://www.w3.org/2000/svg",Vf="http://www.w3.org/1998/Math/MathML",Ot=typeof document<"u"?document:null,ll=Ot&&Ot.createElement("template"),zf={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t==="svg"?Ot.createElementNS(jf,e):t==="mathml"?Ot.createElementNS(Vf,e):Ot.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&o.setAttribute("multiple",r.multiple),o},createText:e=>Ot.createTextNode(e),createComment:e=>Ot.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ot.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,o,s){const l=n?n.previousSibling:t.lastChild;if(o&&(o===s||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===s||!(o=o.nextSibling)););else{ll.innerHTML=r==="svg"?`${e}`:r==="mathml"?`${e}`:e;const a=ll.content;if(r==="svg"||r==="mathml"){const i=a.firstChild;for(;i.firstChild;)a.appendChild(i.firstChild);a.removeChild(i)}t.insertBefore(a,n)}return[l?l.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Tt="transition",kn="animation",hn=Symbol("_vtc"),Bt=(e,{slots:t})=>c(Yu,ni(e),t);Bt.displayName="Transition";const ti={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Wf=Bt.props=Ee({},Pa,ti),Ut=(e,t=[])=>{X(e)?e.forEach(n=>n(...t)):e&&e(...t)},al=e=>e?X(e)?e.some(t=>t.length>1):e.length>1:!1;function ni(e){const t={};for(const I in e)I in ti||(t[I]=e[I]);if(e.css===!1)return t;const{name:n="v",type:r,duration:o,enterFromClass:s=`${n}-enter-from`,enterActiveClass:l=`${n}-enter-active`,enterToClass:a=`${n}-enter-to`,appearFromClass:i=s,appearActiveClass:u=l,appearToClass:f=a,leaveFromClass:p=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,g=Uf(o),E=g&&g[0],b=g&&g[1],{onBeforeEnter:y,onEnter:C,onEnterCancelled:w,onLeave:x,onLeaveCancelled:H,onBeforeAppear:k=y,onAppear:V=C,onAppearCancelled:N=w}=t,G=(I,Z,we)=>{Lt(I,Z?f:a),Lt(I,Z?u:l),we&&we()},P=(I,Z)=>{I._isLeaving=!1,Lt(I,p),Lt(I,v),Lt(I,d),Z&&Z()},Y=I=>(Z,we)=>{const _e=I?V:C,U=()=>G(Z,I,we);Ut(_e,[Z,U]),il(()=>{Lt(Z,I?i:s),mt(Z,I?f:a),al(_e)||cl(Z,r,E,U)})};return Ee(t,{onBeforeEnter(I){Ut(y,[I]),mt(I,s),mt(I,l)},onBeforeAppear(I){Ut(k,[I]),mt(I,i),mt(I,u)},onEnter:Y(!1),onAppear:Y(!0),onLeave(I,Z){I._isLeaving=!0;const we=()=>P(I,Z);mt(I,p),mt(I,d),oi(),il(()=>{I._isLeaving&&(Lt(I,p),mt(I,v),al(x)||cl(I,r,b,we))}),Ut(x,[I,we])},onEnterCancelled(I){G(I,!1),Ut(w,[I])},onAppearCancelled(I){G(I,!0),Ut(N,[I])},onLeaveCancelled(I){P(I),Ut(H,[I])}})}function Uf(e){if(e==null)return null;if(me(e))return[lo(e.enter),lo(e.leave)];{const t=lo(e);return[t,t]}}function lo(e){return Kc(e)}function mt(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[hn]||(e[hn]=new Set)).add(t)}function Lt(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[hn];n&&(n.delete(t),n.size||(e[hn]=void 0))}function il(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let qf=0;function cl(e,t,n,r){const o=e._endId=++qf,s=()=>{o===e._endId&&r()};if(n)return setTimeout(s,n);const{type:l,timeout:a,propCount:i}=ri(e,t);if(!l)return r();const u=l+"end";let f=0;const p=()=>{e.removeEventListener(u,d),s()},d=v=>{v.target===e&&++f>=i&&p()};setTimeout(()=>{f(n[g]||"").split(", "),o=r(`${Tt}Delay`),s=r(`${Tt}Duration`),l=ul(o,s),a=r(`${kn}Delay`),i=r(`${kn}Duration`),u=ul(a,i);let f=null,p=0,d=0;t===Tt?l>0&&(f=Tt,p=l,d=s.length):t===kn?u>0&&(f=kn,p=u,d=i.length):(p=Math.max(l,u),f=p>0?l>u?Tt:kn:null,d=f?f===Tt?s.length:i.length:0);const v=f===Tt&&/\b(transform|all)(,|$)/.test(r(`${Tt}Property`).toString());return{type:f,timeout:p,propCount:d,hasTransform:v}}function ul(e,t){for(;e.lengthfl(n)+fl(e[r])))}function fl(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function oi(){return document.body.offsetHeight}function Kf(e,t,n){const r=e[hn];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const dl=Symbol("_vod"),Gf=Symbol("_vsh"),Yf=Symbol(""),Jf=/(^|;)\s*display\s*:/;function Qf(e,t,n){const r=e.style,o=Le(n);let s=!1;if(n&&!o){if(t)if(Le(t))for(const l of t.split(";")){const a=l.slice(0,l.indexOf(":")).trim();n[a]==null&&Ar(r,a,"")}else for(const l in t)n[l]==null&&Ar(r,l,"");for(const l in n)l==="display"&&(s=!0),Ar(r,l,n[l])}else if(o){if(t!==n){const l=r[Yf];l&&(n+=";"+l),r.cssText=n,s=Jf.test(n)}}else t&&e.removeAttribute("style");dl in e&&(e[dl]=s?r.display:"",e[Gf]&&(r.display="none"))}const pl=/\s*!important$/;function Ar(e,t,n){if(X(n))n.forEach(r=>Ar(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=Xf(e,t);pl.test(n)?e.setProperty(gn(r),n.replace(pl,""),"important"):e[r]=n}}const hl=["Webkit","Moz","ms"],ao={};function Xf(e,t){const n=ao[t];if(n)return n;let r=Ze(t);if(r!=="filter"&&r in e)return ao[t]=r;r=er(r);for(let o=0;oio||(sd.then(()=>io=0),io=Date.now());function ad(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;Xe(id(r,n.value),t,5,[r])};return n.value=e,n.attached=ld(),n}function id(e,t){if(X(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>o=>!o._stopped&&r&&r(o))}else return t}const yl=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,cd=(e,t,n,r,o,s,l,a,i)=>{const u=o==="svg";t==="class"?Kf(e,r,u):t==="style"?Qf(e,n,r):Xn(t)?Ko(t)||rd(e,t,n,r,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ud(e,t,r,u))?ed(e,t,r,s,l,a,i):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),Zf(e,t,r,u))};function ud(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&yl(t)&&te(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return yl(t)&&Le(n)?!1:t in e}const si=new WeakMap,li=new WeakMap,$r=Symbol("_moveCb"),bl=Symbol("_enterCb"),ai={name:"TransitionGroup",props:Ee({},Wf,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=yn(),r=Ra();let o,s;return $a(()=>{if(!o.length)return;const l=e.moveClass||`${e.name||"v"}-move`;if(!md(o[0].el,n.vnode.el,l))return;o.forEach(pd),o.forEach(hd);const a=o.filter(vd);oi(),a.forEach(i=>{const u=i.el,f=u.style;mt(u,l),f.transform=f.webkitTransform=f.transitionDuration="";const p=u[$r]=d=>{d&&d.target!==u||(!d||/transform$/.test(d.propertyName))&&(u.removeEventListener("transitionend",p),u[$r]=null,Lt(u,l))};u.addEventListener("transitionend",p)})}),()=>{const l=ae(e),a=ni(l);let i=l.tag||qe;if(o=[],s)for(let u=0;udelete e.mode;ai.props;const dd=ai;function pd(e){const t=e.el;t[$r]&&t[$r](),t[bl]&&t[bl]()}function hd(e){li.set(e,e.el.getBoundingClientRect())}function vd(e){const t=si.get(e),n=li.get(e),r=t.left-n.left,o=t.top-n.top;if(r||o){const s=e.el.style;return s.transform=s.webkitTransform=`translate(${r}px,${o}px)`,s.transitionDuration="0s",e}}function md(e,t,n){const r=e.cloneNode(),o=e[hn];o&&o.forEach(a=>{a.split(/\s+/).forEach(i=>i&&r.classList.remove(i))}),n.split(/\s+/).forEach(a=>a&&r.classList.add(a)),r.style.display="none";const s=t.nodeType===1?t:t.parentNode;s.appendChild(r);const{hasTransform:l}=ri(r);return s.removeChild(r),l}const gd=Ee({patchProp:cd},zf);let co,wl=!1;function yd(){return co=wl?co:Ef(gd),wl=!0,co}const bd=(...e)=>{const t=yd().createApp(...e),{mount:n}=t;return t.mount=r=>{const o=_d(r);if(o)return n(o,!0,wd(o))},t};function wd(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function _d(e){return Le(e)?document.querySelector(e):e}var Ed=["link","meta","script","style","noscript","template"],Cd=["title","base"],kd=([e,t,n])=>Cd.includes(e)?e:Ed.includes(e)?e==="meta"&&t.name?`${e}.${t.name}`:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,Object.entries(t).map(([r,o])=>typeof o=="boolean"?o?[r,""]:null:[r,o]).filter(r=>r!=null).sort(([r],[o])=>r.localeCompare(o)),n]):null,Sd=e=>{const t=new Set,n=[];return e.forEach(r=>{const o=kd(r);o&&!t.has(o)&&(t.add(o),n.push(r))}),n},xd=e=>e[0]==="/"?e:`/${e}`,ii=e=>e[e.length-1]==="/"||e.endsWith(".html")?e:`${e}/`,tn=e=>/^(https?:)?\/\//.test(e),Ad=/.md((\?|#).*)?$/,hs=(e,t="/")=>!!(tn(e)||e.startsWith("/")&&!e.startsWith(t)&&!Ad.test(e)),Wr=e=>/^[a-z][a-z0-9+.-]*:/.test(e),Ur=e=>Object.prototype.toString.call(e)==="[object Object]",Td=e=>{const[t,...n]=e.split(/(\?|#)/);if(!t||t.endsWith("/"))return e;let r=t.replace(/(^|\/)README.md$/i,"$1index.html");return r.endsWith(".md")?r=r.substring(0,r.length-3)+".html":r.endsWith(".html")||(r=r+".html"),r.endsWith("/index.html")&&(r=r.substring(0,r.length-10)),r+n.join("")},vs=e=>e[e.length-1]==="/"?e.slice(0,-1):e,ci=e=>e[0]==="/"?e.slice(1):e,Ld=(e,t)=>{const n=Object.keys(e).sort((r,o)=>{const s=o.split("/").length-r.split("/").length;return s!==0?s:o.length-r.length});for(const r of n)if(t.startsWith(r))return r;return"/"},ve=e=>typeof e=="string";const Rd="modulepreload",Pd=function(e){return"/ModernCpp-ConcurrentProgramming-Tutorial/"+e},_l={},Me=function(t,n,r){let o=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const s=document.querySelector("meta[property=csp-nonce]"),l=(s==null?void 0:s.nonce)||(s==null?void 0:s.getAttribute("nonce"));o=Promise.all(n.map(a=>{if(a=Pd(a),a in _l)return;_l[a]=!0;const i=a.endsWith(".css"),u=i?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${a}"]${u}`))return;const f=document.createElement("link");if(f.rel=i?"stylesheet":Rd,i||(f.as="script",f.crossOrigin=""),f.href=a,l&&f.setAttribute("nonce",l),document.head.appendChild(f),i)return new Promise((p,d)=>{f.addEventListener("load",p),f.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${a}`)))})}))}return o.then(()=>t()).catch(s=>{const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=s,window.dispatchEvent(l),!l.defaultPrevented)throw s})},Md=JSON.parse("{}"),Od=Object.fromEntries([["/",{loader:()=>Me(()=>import("./index.html-Cr_1xWLb.js"),__vite__mapDeps([0,1])),meta:{t:"现代C++并发编程教程"}}],["/SUMMARY.html",{loader:()=>Me(()=>import("./SUMMARY.html-D8dQ4wSd.js"),__vite__mapDeps([2,1])),meta:{t:"Summary"}}],["/md/01%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5.html",{loader:()=>Me(()=>import("./01基本概念.html-OWekP6KD.js"),__vite__mapDeps([3,1])),meta:{t:"基本概念"}}],["/md/02%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B.html",{loader:()=>Me(()=>import("./02使用线程.html-Cw8uz5dR.js"),__vite__mapDeps([4,1])),meta:{t:"使用线程"}}],["/md/03%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE.html",{loader:()=>Me(()=>import("./03共享数据.html-C-aV2-mo.js"),__vite__mapDeps([5,1])),meta:{t:"共享数据"}}],["/md/04%E5%90%8C%E6%AD%A5%E6%93%8D%E4%BD%9C.html",{loader:()=>Me(()=>import("./04同步操作.html-DuqBIYTo.js"),__vite__mapDeps([6,1])),meta:{t:"同步操作"}}],["/image/%E6%8D%90%E8%B5%A0/",{loader:()=>Me(()=>import("./index.html-aGXxoBnY.js"),__vite__mapDeps([7,1])),meta:{t:""}}],["/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/01thread%E7%9A%84%E6%9E%84%E9%80%A0%E4%B8%8E%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html",{loader:()=>Me(()=>import("./01thread的构造与源码解析.html-D9zIyv03.js"),__vite__mapDeps([8,1])),meta:{t:"std::thread 的构造-源码解析"}}],["/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/02scoped_lock%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html",{loader:()=>Me(()=>import("./02scoped_lock源码解析.html-14rPQ6Fa.js"),__vite__mapDeps([9,1])),meta:{t:"std::scoped_lock 的源码实现与解析"}}],["/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/03async%E4%B8%8Efuture%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html",{loader:()=>Me(()=>import("./03async与future源码解析.html-CgLju1xM.js"),__vite__mapDeps([10,1])),meta:{t:"st::async 与 std::future 源码解析"}}],["/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/",{loader:()=>Me(()=>import("./index.html-BM0ukr18.js"),__vite__mapDeps([11,1])),meta:{t:"详细分析"}}],["/404.html",{loader:()=>Me(()=>import("./404.html-PJlRDmY_.js"),__vite__mapDeps([12,1])),meta:{t:""}}],["/md/",{loader:()=>Me(()=>import("./index.html-BTzaSACv.js"),__vite__mapDeps([13,1])),meta:{t:"Md"}}],["/image/",{loader:()=>Me(()=>import("./index.html-BLSw2nVP.js"),__vite__mapDeps([14,1])),meta:{t:"Image"}}]]);/*! + * vue-router v4.3.2 + * (c) 2024 Eduardo San Martin Morote + * @license MIT + */const ln=typeof document<"u";function Id(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const ce=Object.assign;function uo(e,t){const n={};for(const r in t){const o=t[r];n[r]=lt(o)?o.map(e):e(o)}return n}const Hn=()=>{},lt=Array.isArray,ui=/#/g,$d=/&/g,Nd=/\//g,Hd=/=/g,Dd=/\?/g,fi=/\+/g,Bd=/%5B/g,Fd=/%5D/g,di=/%5E/g,jd=/%60/g,pi=/%7B/g,Vd=/%7C/g,hi=/%7D/g,zd=/%20/g;function ms(e){return encodeURI(""+e).replace(Vd,"|").replace(Bd,"[").replace(Fd,"]")}function Wd(e){return ms(e).replace(pi,"{").replace(hi,"}").replace(di,"^")}function Mo(e){return ms(e).replace(fi,"%2B").replace(zd,"+").replace(ui,"%23").replace($d,"%26").replace(jd,"`").replace(pi,"{").replace(hi,"}").replace(di,"^")}function Ud(e){return Mo(e).replace(Hd,"%3D")}function qd(e){return ms(e).replace(ui,"%23").replace(Dd,"%3F")}function Kd(e){return e==null?"":qd(e).replace(Nd,"%2F")}function qn(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const Gd=/\/$/,Yd=e=>e.replace(Gd,"");function fo(e,t,n="/"){let r,o={},s="",l="";const a=t.indexOf("#");let i=t.indexOf("?");return a=0&&(i=-1),i>-1&&(r=t.slice(0,i),s=t.slice(i+1,a>-1?a:t.length),o=e(s)),a>-1&&(r=r||t.slice(0,a),l=t.slice(a,t.length)),r=Zd(r??t,n),{fullPath:r+(s&&"?")+s+l,path:r,query:o,hash:qn(l)}}function Jd(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function El(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function Qd(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&vn(t.matched[r],n.matched[o])&&vi(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function vn(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function vi(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!Xd(e[n],t[n]))return!1;return!0}function Xd(e,t){return lt(e)?Cl(e,t):lt(t)?Cl(t,e):e===t}function Cl(e,t){return lt(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function Zd(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/"),o=r[r.length-1];(o===".."||o===".")&&r.push("");let s=n.length-1,l,a;for(l=0;l1&&s--;else break;return n.slice(0,s).join("/")+"/"+r.slice(l).join("/")}var Kn;(function(e){e.pop="pop",e.push="push"})(Kn||(Kn={}));var Dn;(function(e){e.back="back",e.forward="forward",e.unknown=""})(Dn||(Dn={}));function ep(e){if(!e)if(ln){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),Yd(e)}const tp=/^[^#]+#/;function np(e,t){return e.replace(tp,"#")+t}function rp(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const qr=()=>({left:window.scrollX,top:window.scrollY});function op(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.startsWith("#"),o=typeof n=="string"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=rp(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function kl(e,t){return(history.state?history.state.position-t:-1)+e}const Oo=new Map;function sp(e,t){Oo.set(e,t)}function lp(e){const t=Oo.get(e);return Oo.delete(e),t}let ap=()=>location.protocol+"//"+location.host;function mi(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf("#");if(s>-1){let a=o.includes(e.slice(s))?e.slice(s).length:1,i=o.slice(a);return i[0]!=="/"&&(i="/"+i),El(i,"")}return El(n,e)+r+o}function ip(e,t,n,r){let o=[],s=[],l=null;const a=({state:d})=>{const v=mi(e,location),g=n.value,E=t.value;let b=0;if(d){if(n.value=v,t.value=d,l&&l===g){l=null;return}b=E?d.position-E.position:0}else r(v);o.forEach(y=>{y(n.value,g,{delta:b,type:Kn.pop,direction:b?b>0?Dn.forward:Dn.back:Dn.unknown})})};function i(){l=n.value}function u(d){o.push(d);const v=()=>{const g=o.indexOf(d);g>-1&&o.splice(g,1)};return s.push(v),v}function f(){const{history:d}=window;d.state&&d.replaceState(ce({},d.state,{scroll:qr()}),"")}function p(){for(const d of s)d();s=[],window.removeEventListener("popstate",a),window.removeEventListener("beforeunload",f)}return window.addEventListener("popstate",a),window.addEventListener("beforeunload",f,{passive:!0}),{pauseListeners:i,listen:u,destroy:p}}function Sl(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?qr():null}}function cp(e){const{history:t,location:n}=window,r={value:mi(e,n)},o={value:t.state};o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function s(i,u,f){const p=e.indexOf("#"),d=p>-1?(n.host&&document.querySelector("base")?e:e.slice(p))+i:ap()+e+i;try{t[f?"replaceState":"pushState"](u,"",d),o.value=u}catch(v){console.error(v),n[f?"replace":"assign"](d)}}function l(i,u){const f=ce({},t.state,Sl(o.value.back,i,o.value.forward,!0),u,{position:o.value.position});s(i,f,!0),r.value=i}function a(i,u){const f=ce({},o.value,t.state,{forward:i,scroll:qr()});s(f.current,f,!0);const p=ce({},Sl(r.value,i,null),{position:f.position+1},u);s(i,p,!1),r.value=i}return{location:r,state:o,push:a,replace:l}}function up(e){e=ep(e);const t=cp(e),n=ip(e,t.state,t.location,t.replace);function r(s,l=!0){l||n.pauseListeners(),history.go(s)}const o=ce({location:"",base:e,go:r,createHref:np.bind(null,e)},t,n);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}function fp(e){return typeof e=="string"||e&&typeof e=="object"}function gi(e){return typeof e=="string"||typeof e=="symbol"}const gt={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},yi=Symbol("");var xl;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(xl||(xl={}));function mn(e,t){return ce(new Error,{type:e,[yi]:!0},t)}function vt(e,t){return e instanceof Error&&yi in e&&(t==null||!!(e.type&t))}const Al="[^/]+?",dp={sensitive:!1,strict:!1,start:!0,end:!0},pp=/[.+*?^${}()[\]/\\]/g;function hp(e,t){const n=ce({},dp,t),r=[];let o=n.start?"^":"";const s=[];for(const u of e){const f=u.length?[]:[90];n.strict&&!u.length&&(o+="/");for(let p=0;pt.length?t.length===1&&t[0]===80?1:-1:0}function mp(e,t){let n=0;const r=e.score,o=t.score;for(;n0&&t[t.length-1]<0}const gp={type:0,value:""},yp=/[a-zA-Z0-9_]/;function bp(e){if(!e)return[[]];if(e==="/")return[[gp]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(v){throw new Error(`ERR (${n})/"${u}": ${v}`)}let n=0,r=n;const o=[];let s;function l(){s&&o.push(s),s=[]}let a=0,i,u="",f="";function p(){u&&(n===0?s.push({type:0,value:u}):n===1||n===2||n===3?(s.length>1&&(i==="*"||i==="+")&&t(`A repeatable param (${u}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:u,regexp:f,repeatable:i==="*"||i==="+",optional:i==="*"||i==="?"})):t("Invalid state to consume buffer"),u="")}function d(){u+=i}for(;a{l(C)}:Hn}function l(f){if(gi(f)){const p=r.get(f);p&&(r.delete(f),n.splice(n.indexOf(p),1),p.children.forEach(l),p.alias.forEach(l))}else{const p=n.indexOf(f);p>-1&&(n.splice(p,1),f.record.name&&r.delete(f.record.name),f.children.forEach(l),f.alias.forEach(l))}}function a(){return n}function i(f){let p=0;for(;p=0&&(f.record.path!==n[p].record.path||!bi(f,n[p]));)p++;n.splice(p,0,f),f.record.name&&!Rl(f)&&r.set(f.record.name,f)}function u(f,p){let d,v={},g,E;if("name"in f&&f.name){if(d=r.get(f.name),!d)throw mn(1,{location:f});E=d.record.name,v=ce(Ll(p.params,d.keys.filter(C=>!C.optional).concat(d.parent?d.parent.keys.filter(C=>C.optional):[]).map(C=>C.name)),f.params&&Ll(f.params,d.keys.map(C=>C.name))),g=d.stringify(v)}else if(f.path!=null)g=f.path,d=n.find(C=>C.re.test(g)),d&&(v=d.parse(g),E=d.record.name);else{if(d=p.name?r.get(p.name):n.find(C=>C.re.test(p.path)),!d)throw mn(1,{location:f,currentLocation:p});E=d.record.name,v=ce({},p.params,f.params),g=d.stringify(v)}const b=[];let y=d;for(;y;)b.unshift(y.record),y=y.parent;return{name:E,path:g,params:v,matched:b,meta:kp(b)}}return e.forEach(f=>s(f)),{addRoute:s,resolve:u,removeRoute:l,getRoutes:a,getRecordMatcher:o}}function Ll(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function Ep(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:Cp(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function Cp(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]=typeof n=="object"?n[r]:n;return t}function Rl(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function kp(e){return e.reduce((t,n)=>ce(t,n.meta),{})}function Pl(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}function bi(e,t){return t.children.some(n=>n===e||bi(e,n))}function Sp(e){const t={};if(e===""||e==="?")return t;const r=(e[0]==="?"?e.slice(1):e).split("&");for(let o=0;os&&Mo(s)):[r&&Mo(r)]).forEach(s=>{s!==void 0&&(t+=(t.length?"&":"")+n,s!=null&&(t+="="+s))})}return t}function xp(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=lt(r)?r.map(o=>o==null?null:""+o):r==null?r:""+r)}return t}const Ap=Symbol(""),Ol=Symbol(""),Kr=Symbol(""),gs=Symbol(""),Io=Symbol("");function Sn(){let e=[];function t(r){return e.push(r),()=>{const o=e.indexOf(r);o>-1&&e.splice(o,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function It(e,t,n,r,o,s=l=>l()){const l=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise((a,i)=>{const u=d=>{d===!1?i(mn(4,{from:n,to:t})):d instanceof Error?i(d):fp(d)?i(mn(2,{from:t,to:d})):(l&&r.enterCallbacks[o]===l&&typeof d=="function"&&l.push(d),a())},f=s(()=>e.call(r&&r.instances[o],t,n,u));let p=Promise.resolve(f);e.length<3&&(p=p.then(u)),p.catch(d=>i(d))})}function po(e,t,n,r,o=s=>s()){const s=[];for(const l of e)for(const a in l.components){let i=l.components[a];if(!(t!=="beforeRouteEnter"&&!l.instances[a]))if(Tp(i)){const f=(i.__vccOpts||i)[t];f&&s.push(It(f,n,r,l,a,o))}else{let u=i();s.push(()=>u.then(f=>{if(!f)return Promise.reject(new Error(`Couldn't resolve component "${a}" at "${l.path}"`));const p=Id(f)?f.default:f;l.components[a]=p;const v=(p.__vccOpts||p)[t];return v&&It(v,n,r,l,a,o)()}))}}return s}function Tp(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Il(e){const t=ke(Kr),n=ke(gs),r=S(()=>{const i=Xt(e.to);return t.resolve(i)}),o=S(()=>{const{matched:i}=r.value,{length:u}=i,f=i[u-1],p=n.matched;if(!f||!p.length)return-1;const d=p.findIndex(vn.bind(null,f));if(d>-1)return d;const v=$l(i[u-2]);return u>1&&$l(f)===v&&p[p.length-1].path!==v?p.findIndex(vn.bind(null,i[u-2])):d}),s=S(()=>o.value>-1&&Mp(n.params,r.value.params)),l=S(()=>o.value>-1&&o.value===n.matched.length-1&&vi(n.params,r.value.params));function a(i={}){return Pp(i)?t[Xt(e.replace)?"replace":"push"](Xt(e.to)).catch(Hn):Promise.resolve()}return{route:r,href:S(()=>r.value.href),isActive:s,isExactActive:l,navigate:a}}const Lp=K({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:Il,setup(e,{slots:t}){const n=tr(Il(e)),{options:r}=ke(Kr),o=S(()=>({[Nl(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[Nl(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const s=t.default&&t.default(n);return e.custom?s:c("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},s)}}}),Rp=Lp;function Pp(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Mp(e,t){for(const n in t){const r=t[n],o=e[n];if(typeof r=="string"){if(r!==o)return!1}else if(!lt(o)||o.length!==r.length||r.some((s,l)=>s!==o[l]))return!1}return!0}function $l(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Nl=(e,t,n)=>e??t??n,Op=K({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const r=ke(Io),o=S(()=>e.route||r.value),s=ke(Ol,0),l=S(()=>{let u=Xt(s);const{matched:f}=o.value;let p;for(;(p=f[u])&&!p.components;)u++;return u}),a=S(()=>o.value.matched[l.value]);Zt(Ol,S(()=>l.value+1)),Zt(Ap,a),Zt(Io,o);const i=Q();return oe(()=>[i.value,a.value,e.name],([u,f,p],[d,v,g])=>{f&&(f.instances[p]=u,v&&v!==f&&u&&u===d&&(f.leaveGuards.size||(f.leaveGuards=v.leaveGuards),f.updateGuards.size||(f.updateGuards=v.updateGuards))),u&&f&&(!v||!vn(f,v)||!d)&&(f.enterCallbacks[p]||[]).forEach(E=>E(u))},{flush:"post"}),()=>{const u=o.value,f=e.name,p=a.value,d=p&&p.components[f];if(!d)return Hl(n.default,{Component:d,route:u});const v=p.props[f],g=v?v===!0?u.params:typeof v=="function"?v(u):v:null,b=c(d,ce({},g,t,{onVnodeUnmounted:y=>{y.component.isUnmounted&&(p.instances[f]=null)},ref:i}));return Hl(n.default,{Component:b,route:u})||b}}});function Hl(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Ip=Op;function $p(e){const t=_p(e.routes,e),n=e.parseQuery||Sp,r=e.stringifyQuery||Ml,o=e.history,s=Sn(),l=Sn(),a=Sn(),i=Ne(gt);let u=gt;ln&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const f=uo.bind(null,T=>""+T),p=uo.bind(null,Kd),d=uo.bind(null,qn);function v(T,F){let D,z;return gi(T)?(D=t.getRecordMatcher(T),z=F):z=T,t.addRoute(z,D)}function g(T){const F=t.getRecordMatcher(T);F&&t.removeRoute(F)}function E(){return t.getRoutes().map(T=>T.record)}function b(T){return!!t.getRecordMatcher(T)}function y(T,F){if(F=ce({},F||i.value),typeof T=="string"){const m=fo(n,T,F.path),_=t.resolve({path:m.path},F),L=o.createHref(m.fullPath);return ce(m,_,{params:d(_.params),hash:qn(m.hash),redirectedFrom:void 0,href:L})}let D;if(T.path!=null)D=ce({},T,{path:fo(n,T.path,F.path).path});else{const m=ce({},T.params);for(const _ in m)m[_]==null&&delete m[_];D=ce({},T,{params:p(m)}),F.params=p(F.params)}const z=t.resolve(D,F),ie=T.hash||"";z.params=f(d(z.params));const de=Jd(r,ce({},T,{hash:Wd(ie),path:z.path})),h=o.createHref(de);return ce({fullPath:de,hash:ie,query:r===Ml?xp(T.query):T.query||{}},z,{redirectedFrom:void 0,href:h})}function C(T){return typeof T=="string"?fo(n,T,i.value.path):ce({},T)}function w(T,F){if(u!==T)return mn(8,{from:F,to:T})}function x(T){return V(T)}function H(T){return x(ce(C(T),{replace:!0}))}function k(T){const F=T.matched[T.matched.length-1];if(F&&F.redirect){const{redirect:D}=F;let z=typeof D=="function"?D(T):D;return typeof z=="string"&&(z=z.includes("?")||z.includes("#")?z=C(z):{path:z},z.params={}),ce({query:T.query,hash:T.hash,params:z.path!=null?{}:T.params},z)}}function V(T,F){const D=u=y(T),z=i.value,ie=T.state,de=T.force,h=T.replace===!0,m=k(D);if(m)return V(ce(C(m),{state:typeof m=="object"?ce({},ie,m.state):ie,force:de,replace:h}),F||D);const _=D;_.redirectedFrom=F;let L;return!de&&Qd(r,z,D)&&(L=mn(16,{to:_,from:z}),it(z,z,!0,!1)),(L?Promise.resolve(L):P(_,z)).catch(A=>vt(A)?vt(A,2)?A:St(A):W(A,_,z)).then(A=>{if(A){if(vt(A,2))return V(ce({replace:h},C(A.to),{state:typeof A.to=="object"?ce({},ie,A.to.state):ie,force:de}),F||_)}else A=I(_,z,!0,h,ie);return Y(_,z,A),A})}function N(T,F){const D=w(T,F);return D?Promise.reject(D):Promise.resolve()}function G(T){const F=on.values().next().value;return F&&typeof F.runWithContext=="function"?F.runWithContext(T):T()}function P(T,F){let D;const[z,ie,de]=Np(T,F);D=po(z.reverse(),"beforeRouteLeave",T,F);for(const m of z)m.leaveGuards.forEach(_=>{D.push(It(_,T,F))});const h=N.bind(null,T,F);return D.push(h),Pe(D).then(()=>{D=[];for(const m of s.list())D.push(It(m,T,F));return D.push(h),Pe(D)}).then(()=>{D=po(ie,"beforeRouteUpdate",T,F);for(const m of ie)m.updateGuards.forEach(_=>{D.push(It(_,T,F))});return D.push(h),Pe(D)}).then(()=>{D=[];for(const m of de)if(m.beforeEnter)if(lt(m.beforeEnter))for(const _ of m.beforeEnter)D.push(It(_,T,F));else D.push(It(m.beforeEnter,T,F));return D.push(h),Pe(D)}).then(()=>(T.matched.forEach(m=>m.enterCallbacks={}),D=po(de,"beforeRouteEnter",T,F,G),D.push(h),Pe(D))).then(()=>{D=[];for(const m of l.list())D.push(It(m,T,F));return D.push(h),Pe(D)}).catch(m=>vt(m,8)?m:Promise.reject(m))}function Y(T,F,D){a.list().forEach(z=>G(()=>z(T,F,D)))}function I(T,F,D,z,ie){const de=w(T,F);if(de)return de;const h=F===gt,m=ln?history.state:{};D&&(z||h?o.replace(T.fullPath,ce({scroll:h&&m&&m.scroll},ie)):o.push(T.fullPath,ie)),i.value=T,it(T,F,D,h),St()}let Z;function we(){Z||(Z=o.listen((T,F,D)=>{if(!fr.listening)return;const z=y(T),ie=k(z);if(ie){V(ce(ie,{replace:!0}),z).catch(Hn);return}u=z;const de=i.value;ln&&sp(kl(de.fullPath,D.delta),qr()),P(z,de).catch(h=>vt(h,12)?h:vt(h,2)?(V(h.to,z).then(m=>{vt(m,20)&&!D.delta&&D.type===Kn.pop&&o.go(-1,!1)}).catch(Hn),Promise.reject()):(D.delta&&o.go(-D.delta,!1),W(h,z,de))).then(h=>{h=h||I(z,de,!1),h&&(D.delta&&!vt(h,8)?o.go(-D.delta,!1):D.type===Kn.pop&&vt(h,20)&&o.go(-1,!1)),Y(z,de,h)}).catch(Hn)}))}let _e=Sn(),U=Sn(),ne;function W(T,F,D){St(T);const z=U.list();return z.length?z.forEach(ie=>ie(T,F,D)):console.error(T),Promise.reject(T)}function tt(){return ne&&i.value!==gt?Promise.resolve():new Promise((T,F)=>{_e.add([T,F])})}function St(T){return ne||(ne=!T,we(),_e.list().forEach(([F,D])=>T?D(T):F()),_e.reset()),T}function it(T,F,D,z){const{scrollBehavior:ie}=e;if(!ln||!ie)return Promise.resolve();const de=!D&&lp(kl(T.fullPath,0))||(z||!D)&&history.state&&history.state.scroll||null;return zt().then(()=>ie(T,F,de)).then(h=>h&&op(h)).catch(h=>W(h,T,F))}const De=T=>o.go(T);let rn;const on=new Set,fr={currentRoute:i,listening:!0,addRoute:v,removeRoute:g,hasRoute:b,getRoutes:E,resolve:y,options:e,push:x,replace:H,go:De,back:()=>De(-1),forward:()=>De(1),beforeEach:s.add,beforeResolve:l.add,afterEach:a.add,onError:U.add,isReady:tt,install(T){const F=this;T.component("RouterLink",Rp),T.component("RouterView",Ip),T.config.globalProperties.$router=F,Object.defineProperty(T.config.globalProperties,"$route",{enumerable:!0,get:()=>Xt(i)}),ln&&!rn&&i.value===gt&&(rn=!0,x(o.location).catch(ie=>{}));const D={};for(const ie in gt)Object.defineProperty(D,ie,{get:()=>i.value[ie],enumerable:!0});T.provide(Kr,F),T.provide(gs,ma(D)),T.provide(Io,i);const z=T.unmount;on.add(T),T.unmount=function(){on.delete(T),on.size<1&&(u=gt,Z&&Z(),Z=null,i.value=gt,rn=!1,ne=!1),z()}}};function Pe(T){return T.reduce((F,D)=>F.then(()=>G(D)),Promise.resolve())}return fr}function Np(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let l=0;lvn(u,a))?r.push(a):n.push(a));const i=e.matched[l];i&&(t.matched.find(u=>vn(u,i))||o.push(i))}return[n,r,o]}function bn(){return ke(Kr)}function Et(){return ke(gs)}var ys=Symbol(""),ht=()=>{const e=ke(ys);if(!e)throw new Error("useClientData() is called without provider.");return e},Hp=()=>ht().pageComponent,be=()=>ht().pageData,Se=()=>ht().pageFrontmatter,Dp=()=>ht().pageHead,wi=()=>ht().pageLang,Bp=()=>ht().pageLayout,Ct=()=>ht().routeLocale,Fp=()=>ht().routes,_i=()=>ht().siteData,Gr=()=>ht().siteLocaleData,jp=Symbol(""),$o=Ne(Md),Gn=Ne(Od),Ei=e=>{const t=Td(e);if(Gn.value[t])return t;const n=encodeURI(t);return Gn.value[n]?n:$o.value[t]||$o.value[n]||t},Ft=e=>{const t=Ei(e),n=Gn.value[t]??{...Gn.value["/404.html"],notFound:!0};return{path:t,notFound:!1,...n}},Yr=K({name:"ClientOnly",setup(e,t){const n=Q(!1);return ye(()=>{n.value=!0}),()=>{var r,o;return n.value?(o=(r=t.slots).default)==null?void 0:o.call(r):null}}}),Ci=K({name:"Content",props:{path:{type:String,required:!1,default:""}},setup(e){const t=Hp(),n=S(()=>{if(!e.path)return t.value;const r=Ft(e.path);return Oa(()=>r.loader().then(({comp:o})=>o))});return()=>c(n.value)}}),et=(e={})=>e,Fe=e=>tn(e)?e:`/ModernCpp-ConcurrentProgramming-Tutorial/${ci(e)}`,Vp=e=>{if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget){const t=e.currentTarget.getAttribute("target");if(t!=null&&t.match(/\b_blank\b/i))return}return e.preventDefault(),!0}},Ke=({active:e=!1,activeClass:t="route-link-active",to:n,...r},{slots:o})=>{var i;const s=bn(),l=Ei(n),a=l.startsWith("#")||l.startsWith("?")?l:Fe(l);return c("a",{...r,class:["route-link",{[t]:e}],href:a,onClick:(u={})=>{Vp(u)?s.push(n).catch():Promise.resolve()}},(i=o.default)==null?void 0:i.call(o))};Ke.displayName="RouteLink";Ke.props={active:Boolean,activeClass:String,to:String};var zp="Layout",Wp="en-US",qt=tr({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageHead:(e,t,n)=>{const r=ve(t.description)?t.description:n.description,o=[...Array.isArray(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:r}]];return Sd(o)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:(e,t)=>e.lang||t.lang||Wp,resolvePageLayout:(e,t)=>{const n=ve(e.frontmatter.layout)?e.frontmatter.layout:zp;if(!t[n])throw new Error(`[vuepress] Cannot resolve layout: ${n}`);return t[n]},resolveRouteLocale:(e,t)=>Ld(e,t),resolveSiteLocaleData:(e,t)=>{var n;return{...e,...e.locales[t],head:[...((n=e.locales[t])==null?void 0:n.head)??[],...e.head??[]]}}});const Up={},ar=e=>{const t=Ct();return S(()=>e[t.value]??{})},bt=(e,t)=>{var r;const n=(r=(t==null?void 0:t._instance)||yn())==null?void 0:r.appContext.components;return n?e in n||Ze(e)in n||er(Ze(e))in n:!1},qp=e=>typeof e<"u",ho=e=>typeof e=="number",No=Array.isArray,Yn=(e,t)=>ve(e)&&e.startsWith(t),Kp=(e,t)=>ve(e)&&e.endsWith(t),Gp=Object.entries,Jr=Object.keys,Yp=e=>{if(e){if(typeof e=="number")return new Date(e);const t=Date.parse(e.toString());if(!Number.isNaN(t))return new Date(t)}return null},Qr=e=>Yn(e,"/"),Jp="http://.",Qp=(e,t)=>{if(Qr(e)||typeof t!="string")return Ft(e);const n=t.slice(0,t.lastIndexOf("/"));return Ft(new URL(`${n}/${encodeURI(e)}`,Jp).pathname)},ki=e=>new Promise(t=>setTimeout(t,e)),Si=({type:e="info",text:t="",vertical:n,color:r},{slots:o})=>{var s;return c("span",{class:["vp-badge",e,{diy:r}],style:{verticalAlign:n??!1,backgroundColor:r??!1}},((s=o.default)==null?void 0:s.call(o))||t)};Si.displayName="Badge";var Xp=K({name:"FontIcon",props:{icon:{type:String,default:""},color:{type:String,default:""},size:{type:[String,Number],default:""}},setup(e){const t=S(()=>{const r=["font-icon icon"],o=`${e.icon}`;return r.push(o),r}),n=S(()=>{const r={};return e.color&&(r.color=e.color),e.size&&(r["font-size"]=Number.isNaN(Number(e.size))?e.size:`${e.size}px`),Jr(r).length?r:null});return()=>e.icon?c("span",{key:e.icon,class:t.value,style:n.value}):null}});const Zp=et({enhance:({app:e})=>{bt("Badge")||e.component("Badge",Si),bt("FontIcon")||e.component("FontIcon",Xp)},setup:()=>{},rootComponents:[]});function xi(e,t){let n,r,o;const s=Q(!0),l=()=>{s.value=!0,o()};oe(e,l,{flush:"sync"});const a=typeof t=="function"?t:t.get,i=typeof t=="function"?void 0:t.set,u=ls((f,p)=>(r=f,o=p,{get(){return s.value&&(n=a(),s.value=!1),r(),n},set(d){i==null||i(d)}}));return Object.isExtensible(u)&&(u.trigger=l),u}function nn(e){return oa()?(nu(e),!0):!1}function $e(e){return typeof e=="function"?e():Xt(e)}const ir=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const eh=e=>e!=null,th=Object.prototype.toString,nh=e=>th.call(e)==="[object Object]",wt=()=>{},Ho=rh();function rh(){var e,t;return ir&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function bs(e,t){function n(...r){return new Promise((o,s)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(o).catch(s)})}return n}const Ai=e=>e();function oh(e,t={}){let n,r,o=wt;const s=a=>{clearTimeout(a),o(),o=wt};return a=>{const i=$e(e),u=$e(t.maxWait);return n&&s(n),i<=0||u!==void 0&&u<=0?(r&&(s(r),r=null),Promise.resolve(a())):new Promise((f,p)=>{o=t.rejectOnCancel?p:f,u&&!r&&(r=setTimeout(()=>{n&&s(n),r=null,f(a())},u)),n=setTimeout(()=>{r&&s(r),r=null,f(a())},i)})}}function sh(...e){let t=0,n,r=!0,o=wt,s,l,a,i,u;!Re(e[0])&&typeof e[0]=="object"?{delay:l,trailing:a=!0,leading:i=!0,rejectOnCancel:u=!1}=e[0]:[l,a=!0,i=!0,u=!1]=e;const f=()=>{n&&(clearTimeout(n),n=void 0,o(),o=wt)};return d=>{const v=$e(l),g=Date.now()-t,E=()=>s=d();return f(),v<=0?(t=Date.now(),E()):(g>v&&(i||!r)?(t=Date.now(),E()):a&&(s=new Promise((b,y)=>{o=u?y:b,n=setTimeout(()=>{t=Date.now(),r=!0,b(E()),f()},Math.max(0,v-g))})),!i&&!n&&(n=setTimeout(()=>r=!0,v)),r=!1,s)}}function lh(e=Ai){const t=Q(!0);function n(){t.value=!1}function r(){t.value=!0}const o=(...s)=>{t.value&&e(...s)};return{isActive:nr(t),pause:n,resume:r,eventFilter:o}}function ah(e){let t;function n(){return t||(t=e()),t}return n.reset=async()=>{const r=t;t=void 0,r&&await r},n}function ih(e){return yn()}function ch(...e){if(e.length!==1)return Fr(...e);const t=e[0];return typeof t=="function"?nr(ls(()=>({get:t,set:wt}))):Q(t)}function Ti(e,t=200,n={}){return bs(oh(t,n),e)}function uh(e,t=200,n=!1,r=!0,o=!1){return bs(sh(t,n,r,o),e)}function fh(e,t,n={}){const{eventFilter:r=Ai,...o}=n;return oe(e,bs(r,t),o)}function dh(e,t,n={}){const{eventFilter:r,...o}=n,{eventFilter:s,pause:l,resume:a,isActive:i}=lh(r);return{stop:fh(e,t,{...o,eventFilter:s}),pause:l,resume:a,isActive:i}}function ws(e,t=!0,n){ih()?ye(e,n):t?e():zt(e)}function ph(e,t,n={}){const{immediate:r=!0}=n,o=Q(!1);let s=null;function l(){s&&(clearTimeout(s),s=null)}function a(){o.value=!1,l()}function i(...u){l(),o.value=!0,s=setTimeout(()=>{o.value=!1,s=null,e(...u)},$e(t))}return r&&(o.value=!0,ir&&i()),nn(a),{isPending:nr(o),start:i,stop:a}}function Do(e=!1,t={}){const{truthyValue:n=!0,falsyValue:r=!1}=t,o=Re(e),s=Q(e);function l(a){if(arguments.length)return s.value=a,s.value;{const i=$e(n);return s.value=s.value===i?$e(r):i,s.value}}return o?l:[s,l]}function je(e){var t;const n=$e(e);return(t=n==null?void 0:n.$el)!=null?t:n}const at=ir?window:void 0,hh=ir?window.document:void 0,Li=ir?window.navigator:void 0;function ge(...e){let t,n,r,o;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,r,o]=e,t=at):[t,n,r,o]=e,!t)return wt;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const s=[],l=()=>{s.forEach(f=>f()),s.length=0},a=(f,p,d,v)=>(f.addEventListener(p,d,v),()=>f.removeEventListener(p,d,v)),i=oe(()=>[je(t),$e(o)],([f,p])=>{if(l(),!f)return;const d=nh(p)?{...p}:p;s.push(...n.flatMap(v=>r.map(g=>a(f,v,g,d))))},{immediate:!0,flush:"post"}),u=()=>{i(),l()};return nn(u),u}let Dl=!1;function vh(e,t,n={}){const{window:r=at,ignore:o=[],capture:s=!0,detectIframe:l=!1}=n;if(!r)return wt;Ho&&!Dl&&(Dl=!0,Array.from(r.document.body.children).forEach(d=>d.addEventListener("click",wt)),r.document.documentElement.addEventListener("click",wt));let a=!0;const i=d=>o.some(v=>{if(typeof v=="string")return Array.from(r.document.querySelectorAll(v)).some(g=>g===d.target||d.composedPath().includes(g));{const g=je(v);return g&&(d.target===g||d.composedPath().includes(g))}}),f=[ge(r,"click",d=>{const v=je(e);if(!(!v||v===d.target||d.composedPath().includes(v))){if(d.detail===0&&(a=!i(d)),!a){a=!0;return}t(d)}},{passive:!0,capture:s}),ge(r,"pointerdown",d=>{const v=je(e);a=!i(d)&&!!(v&&!d.composedPath().includes(v))},{passive:!0}),l&&ge(r,"blur",d=>{setTimeout(()=>{var v;const g=je(e);((v=r.document.activeElement)==null?void 0:v.tagName)==="IFRAME"&&!(g!=null&&g.contains(r.document.activeElement))&&t(d)},0)})].filter(Boolean);return()=>f.forEach(d=>d())}function mh(){const e=Q(!1),t=yn();return t&&ye(()=>{e.value=!0},t),e}function wn(e){const t=mh();return S(()=>(t.value,!!e()))}function Ri(e,t={}){const{window:n=at}=t,r=wn(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let o;const s=Q(!1),l=u=>{s.value=u.matches},a=()=>{o&&("removeEventListener"in o?o.removeEventListener("change",l):o.removeListener(l))},i=Ta(()=>{r.value&&(a(),o=n.matchMedia($e(e)),"addEventListener"in o?o.addEventListener("change",l):o.addListener(l),s.value=o.matches)});return nn(()=>{i(),a(),o=void 0}),s}function Bl(e,t={}){const{controls:n=!1,navigator:r=Li}=t,o=wn(()=>r&&"permissions"in r);let s;const l=typeof e=="string"?{name:e}:e,a=Q(),i=()=>{s&&(a.value=s.state)},u=ah(async()=>{if(o.value){if(!s)try{s=await r.permissions.query(l),ge(s,"change",i),i()}catch{a.value="prompt"}return s}});return u(),n?{state:a,isSupported:o,query:u}:a}function gh(e={}){const{navigator:t=Li,read:n=!1,source:r,copiedDuring:o=1500,legacy:s=!1}=e,l=wn(()=>t&&"clipboard"in t),a=Bl("clipboard-read"),i=Bl("clipboard-write"),u=S(()=>l.value||s),f=Q(""),p=Q(!1),d=ph(()=>p.value=!1,o);function v(){l.value&&y(a.value)?t.clipboard.readText().then(C=>{f.value=C}):f.value=b()}u.value&&n&&ge(["copy","cut"],v);async function g(C=$e(r)){u.value&&C!=null&&(l.value&&y(i.value)?await t.clipboard.writeText(C):E(C),f.value=C,p.value=!0,d.start())}function E(C){const w=document.createElement("textarea");w.value=C??"",w.style.position="absolute",w.style.opacity="0",document.body.appendChild(w),w.select(),document.execCommand("copy"),w.remove()}function b(){var C,w,x;return(x=(w=(C=document==null?void 0:document.getSelection)==null?void 0:C.call(document))==null?void 0:w.toString())!=null?x:""}function y(C){return C==="granted"||C==="prompt"}return{isSupported:u,text:f,copied:p,copy:g}}const _r=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},Er="__vueuse_ssr_handlers__",yh=bh();function bh(){return Er in _r||(_r[Er]=_r[Er]||{}),_r[Er]}function wh(e,t){return yh[e]||t}function _h(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Eh={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Fl="vueuse-storage";function Pi(e,t,n,r={}){var o;const{flush:s="pre",deep:l=!0,listenToStorageChanges:a=!0,writeDefaults:i=!0,mergeDefaults:u=!1,shallow:f,window:p=at,eventFilter:d,onError:v=P=>{console.error(P)},initOnMounted:g}=r,E=(f?Ne:Q)(typeof t=="function"?t():t);if(!n)try{n=wh("getDefaultStorage",()=>{var P;return(P=at)==null?void 0:P.localStorage})()}catch(P){v(P)}if(!n)return E;const b=$e(t),y=_h(b),C=(o=r.serializer)!=null?o:Eh[y],{pause:w,resume:x}=dh(E,()=>k(E.value),{flush:s,deep:l,eventFilter:d});p&&a&&ws(()=>{ge(p,"storage",N),ge(p,Fl,G),g&&N()}),g||N();function H(P,Y){p&&p.dispatchEvent(new CustomEvent(Fl,{detail:{key:e,oldValue:P,newValue:Y,storageArea:n}}))}function k(P){try{const Y=n.getItem(e);if(P==null)H(Y,null),n.removeItem(e);else{const I=C.write(P);Y!==I&&(n.setItem(e,I),H(Y,I))}}catch(Y){v(Y)}}function V(P){const Y=P?P.newValue:n.getItem(e);if(Y==null)return i&&b!=null&&n.setItem(e,C.write(b)),b;if(!P&&u){const I=C.read(Y);return typeof u=="function"?u(I,b):y==="object"&&!Array.isArray(I)?{...b,...I}:I}else return typeof Y!="string"?Y:C.read(Y)}function N(P){if(!(P&&P.storageArea!==n)){if(P&&P.key==null){E.value=b;return}if(!(P&&P.key!==e)){w();try{(P==null?void 0:P.newValue)!==C.write(E.value)&&(E.value=V(P))}catch(Y){v(Y)}finally{P?zt(x):x()}}}}function G(P){N(P.detail)}return E}function Ch(e){return Ri("(prefers-color-scheme: dark)",e)}function kh(e,t,n={}){const{window:r=at,...o}=n;let s;const l=wn(()=>r&&"MutationObserver"in r),a=()=>{s&&(s.disconnect(),s=void 0)},i=S(()=>{const d=$e(e),v=(Array.isArray(d)?d:[d]).map(je).filter(eh);return new Set(v)}),u=oe(()=>i.value,d=>{a(),l.value&&r&&d.size&&(s=new MutationObserver(t),d.forEach(v=>s.observe(v,o)))},{immediate:!0,flush:"post"}),f=()=>s==null?void 0:s.takeRecords(),p=()=>{a(),u()};return nn(p),{isSupported:l,stop:p,takeRecords:f}}function Sh(e,t,n={}){const{window:r=at,...o}=n;let s;const l=wn(()=>r&&"ResizeObserver"in r),a=()=>{s&&(s.disconnect(),s=void 0)},i=S(()=>Array.isArray(e)?e.map(p=>je(p)):[je(e)]),u=oe(i,p=>{if(a(),l.value&&r){s=new ResizeObserver(t);for(const d of p)d&&s.observe(d,o)}},{immediate:!0,flush:"post"}),f=()=>{a(),u()};return nn(f),{isSupported:l,stop:f}}function xh(e,t={width:0,height:0},n={}){const{window:r=at,box:o="content-box"}=n,s=S(()=>{var p,d;return(d=(p=je(e))==null?void 0:p.namespaceURI)==null?void 0:d.includes("svg")}),l=Q(t.width),a=Q(t.height),{stop:i}=Sh(e,([p])=>{const d=o==="border-box"?p.borderBoxSize:o==="content-box"?p.contentBoxSize:p.devicePixelContentBoxSize;if(r&&s.value){const v=je(e);if(v){const g=r.getComputedStyle(v);l.value=Number.parseFloat(g.width),a.value=Number.parseFloat(g.height)}}else if(d){const v=Array.isArray(d)?d:[d];l.value=v.reduce((g,{inlineSize:E})=>g+E,0),a.value=v.reduce((g,{blockSize:E})=>g+E,0)}else l.value=p.contentRect.width,a.value=p.contentRect.height},n);ws(()=>{const p=je(e);p&&(l.value="offsetWidth"in p?p.offsetWidth:t.width,a.value="offsetHeight"in p?p.offsetHeight:t.height)});const u=oe(()=>je(e),p=>{l.value=p?t.width:0,a.value=p?t.height:0});function f(){i(),u()}return{width:l,height:a,stop:f}}const jl=["fullscreenchange","webkitfullscreenchange","webkitendfullscreen","mozfullscreenchange","MSFullscreenChange"];function _s(e,t={}){const{document:n=hh,autoExit:r=!1}=t,o=S(()=>{var y;return(y=je(e))!=null?y:n==null?void 0:n.querySelector("html")}),s=Q(!1),l=S(()=>["requestFullscreen","webkitRequestFullscreen","webkitEnterFullscreen","webkitEnterFullScreen","webkitRequestFullScreen","mozRequestFullScreen","msRequestFullscreen"].find(y=>n&&y in n||o.value&&y in o.value)),a=S(()=>["exitFullscreen","webkitExitFullscreen","webkitExitFullScreen","webkitCancelFullScreen","mozCancelFullScreen","msExitFullscreen"].find(y=>n&&y in n||o.value&&y in o.value)),i=S(()=>["fullScreen","webkitIsFullScreen","webkitDisplayingFullscreen","mozFullScreen","msFullscreenElement"].find(y=>n&&y in n||o.value&&y in o.value)),u=["fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement"].find(y=>n&&y in n),f=wn(()=>o.value&&n&&l.value!==void 0&&a.value!==void 0&&i.value!==void 0),p=()=>u?(n==null?void 0:n[u])===o.value:!1,d=()=>{if(i.value){if(n&&n[i.value]!=null)return n[i.value];{const y=o.value;if((y==null?void 0:y[i.value])!=null)return!!y[i.value]}}return!1};async function v(){if(!(!f.value||!s.value)){if(a.value)if((n==null?void 0:n[a.value])!=null)await n[a.value]();else{const y=o.value;(y==null?void 0:y[a.value])!=null&&await y[a.value]()}s.value=!1}}async function g(){if(!f.value||s.value)return;d()&&await v();const y=o.value;l.value&&(y==null?void 0:y[l.value])!=null&&(await y[l.value](),s.value=!0)}async function E(){await(s.value?v():g())}const b=()=>{const y=d();(!y||y&&p())&&(s.value=y)};return ge(n,jl,b,!1),ge(()=>je(o),jl,b,!1),r&&nn(v),{isSupported:f,isFullscreen:s,enter:g,exit:v,toggle:E}}function vo(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function Vv(e,t,n={}){const{window:r=at}=n;return Pi(e,t,r==null?void 0:r.localStorage,n)}function Mi(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const Cr=new WeakMap;function Es(e,t=!1){const n=Q(t);let r=null;oe(ch(e),l=>{const a=vo($e(l));if(a){const i=a;Cr.get(i)||Cr.set(i,i.style.overflow),n.value&&(i.style.overflow="hidden")}},{immediate:!0});const o=()=>{const l=vo($e(e));!l||n.value||(Ho&&(r=ge(l,"touchmove",a=>{Ah(a)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},s=()=>{var l;const a=vo($e(e));!a||!n.value||(Ho&&(r==null||r()),a.style.overflow=(l=Cr.get(a))!=null?l:"",Cr.delete(a),n.value=!1)};return nn(s),S({get(){return n.value},set(l){l?o():s()}})}function Th(e={}){const{window:t=at,behavior:n="auto"}=e;if(!t)return{x:Q(0),y:Q(0)};const r=Q(t.scrollX),o=Q(t.scrollY),s=S({get(){return r.value},set(a){scrollTo({left:a,behavior:n})}}),l=S({get(){return o.value},set(a){scrollTo({top:a,behavior:n})}});return ge(t,"scroll",()=>{r.value=t.scrollX,o.value=t.scrollY},{capture:!1,passive:!0}),{x:s,y:l}}function Lh(e={}){const{window:t=at,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:r=Number.POSITIVE_INFINITY,listenOrientation:o=!0,includeScrollbar:s=!0}=e,l=Q(n),a=Q(r),i=()=>{t&&(s?(l.value=t.innerWidth,a.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,a.value=t.document.documentElement.clientHeight))};if(i(),ws(i),ge("resize",i,{passive:!0}),o){const u=Ri("(orientation: portrait)");oe(u,()=>i())}return{width:l,height:a}}const Vl=async(e,t)=>{const{path:n,query:r}=e.currentRoute.value,{scrollBehavior:o}=e.options;e.options.scrollBehavior=void 0,await e.replace({path:n,query:r,hash:t}),e.options.scrollBehavior=o},Rh=({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:r=5})=>{const o=bn();ge("scroll",Ti(()=>{var g,E;const l=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(l-0)p.some(y=>y.hash===b.hash));for(let b=0;b=(((g=y.parentElement)==null?void 0:g.offsetTop)??0)-r,x=!C||l<(((E=C.parentElement)==null?void 0:E.offsetTop)??0)-r;if(!(w&&x))continue;const k=decodeURIComponent(o.currentRoute.value.hash),V=decodeURIComponent(y.hash);if(k===V)return;if(f){for(let N=b+1;Nve(e.title)?{title:e.title}:null;const Ii=Symbol(""),Nh=e=>{Oi=e},Hh=()=>ke(Ii),Dh=e=>{e.provide(Ii,Oi)};var Bh={"/":{title:"目录",empty:"暂无目录"}};const Fh=K({name:"Catalog",props:{base:{type:String,default:""},level:{type:Number,default:3},index:Boolean,hideHeading:Boolean},setup(e){const t=Hh(),n=ar(Bh),r=be(),o=Fp(),s=_i(),a=Ne(Gp(o.value).map(([u,{meta:f}])=>{const p=t(f);if(!p)return null;const d=u.split("/").length;return{level:Kp(u,"/")?d-2:d-1,base:u.replace(/\/[^/]+\/?$/,"/"),path:u,...p}}).filter(u=>Ur(u)&&ve(u.title))),i=S(()=>{const u=e.base?xd(ii(e.base)):r.value.path.replace(/\/[^/]+$/,"/"),f=u.split("/").length-2,p=[];return a.value.filter(({level:d,path:v})=>{if(!Yn(v,u)||v===u)return!1;if(u==="/"){const g=Jr(s.value.locales).filter(E=>E!=="/");if(v==="/404.html"||g.some(E=>Yn(v,E)))return!1}return d-f<=e.level}).sort(({title:d,level:v,order:g},{title:E,level:b,order:y})=>{const C=v-b;return C||(ho(g)?ho(y)?g>0?y>0?g-y:-1:y<0?g-y:1:g:ho(y)?y:d.localeCompare(E))}).forEach(d=>{var E;const{base:v,level:g}=d;switch(g-f){case 1:{p.push(d);break}case 2:{const b=p.find(y=>y.path===v);b&&(b.children??(b.children=[])).push(d);break}default:{const b=p.find(y=>y.path===v.replace(/\/[^/]+\/$/,"/"));if(b){const y=(E=b.children)==null?void 0:E.find(C=>C.path===v);y&&(y.children??(y.children=[])).push(d)}}}}),p});return()=>{const u=i.value.some(f=>f.children);return c("div",{class:["vp-catalog-wrapper",{index:e.index}]},[e.hideHeading?null:c("h2",{class:"vp-catalog-main-title"},n.value.title),i.value.length?c(e.index?"ol":"ul",{class:["vp-catalogs",{deep:u}]},i.value.map(({children:f=[],title:p,path:d,content:v})=>{const g=c(Ke,{class:"vp-catalog-title",to:d},()=>v?c(v):p);return c("li",{class:"vp-catalog"},u?[c("h3",{id:p,class:["vp-catalog-child-title",{"has-children":f.length}]},[c("a",{href:`#${p}`,class:"vp-catalog-header-anchor","aria-hidden":!0},"#"),g]),f.length?c(e.index?"ol":"ul",{class:"vp-child-catalogs"},f.map(({children:E=[],content:b,path:y,title:C})=>c("li",{class:"vp-child-catalog"},[c("div",{class:["vp-catalog-sub-title",{"has-children":E.length}]},[c("a",{href:`#${C}`,class:"vp-catalog-header-anchor"},"#"),c(Ke,{class:"vp-catalog-title",to:y},()=>b?c(b):C)]),E.length?c(e.index?"ol":"div",{class:e.index?"vp-sub-catalogs":"vp-sub-catalogs-wrapper"},E.map(({content:w,path:x,title:H})=>e.index?c("li",{class:"vp-sub-catalog"},c(Ke,{to:x},()=>w?c(w):H)):c(Ke,{class:"vp-sub-catalog-link",to:x},()=>w?c(w):H))):null]))):null]:c("div",{class:"vp-catalog-child-title"},g))})):c("p",{class:"vp-empty-catalog"},n.value.empty)])}}}),jh=et({enhance:({app:e})=>{Dh(e),bt("Catalog",e)||e.component("Catalog",Fh)}});var Vh={"/":{backToTop:"返回顶部"}};const zh=K({name:"BackToTop",setup(){const e=Se(),t=ar(Vh),n=Ne(),{height:r}=xh(n),{height:o}=Lh(),{y:s}=Th(),l=S(()=>e.value.backToTop!==!1&&s.value>100),a=S(()=>s.value/(r.value-o.value)*100);return ye(()=>{n.value=document.body}),()=>c(Bt,{name:"back-to-top"},()=>l.value?c("button",{type:"button",class:"vp-back-to-top-button","aria-label":t.value.backToTop,onClick:()=>{window.scrollTo({top:0,behavior:"smooth"})}},[c("span",{class:"vp-scroll-progress",role:"progressbar","aria-labelledby":"loadinglabel","aria-valuenow":a.value},c("svg",c("circle",{cx:"26",cy:"26",r:"24",fill:"none",stroke:"currentColor","stroke-width":"4","stroke-dasharray":`${Math.PI*a.value*.48} ${Math.PI*(100-a.value)*.48}`}))),c("div",{class:"back-to-top-icon"})]):null)}}),Wh=et({rootComponents:[zh]}),Uh=c("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[c("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),c("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),$i=K({name:"ExternalLinkIcon",props:{locales:{type:Object,default:()=>({})}},setup(e){const t=Ct(),n=S(()=>e.locales[t.value]??{openInNewWindow:"open in new window"});return()=>c("span",[Uh,c("span",{class:"external-link-icon-sr-only"},n.value.openInNewWindow)])}});var qh={};const Kh=qh,Gh=et({enhance({app:e}){e.component("ExternalLinkIcon",c($i,{locales:Kh}))}});/** + * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT + */const se={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:e=>{const t=se.isStarted();e=mo(e,se.settings.minimum,1),se.status=e===1?null:e;const n=se.render(!t),r=n.querySelector(se.settings.barSelector),o=se.settings.speed,s=se.settings.easing;return n.offsetWidth,Yh(l=>{kr(r,{transform:"translate3d("+zl(e)+"%,0,0)",transition:"all "+o+"ms "+s}),e===1?(kr(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){kr(n,{transition:"all "+o+"ms linear",opacity:"0"}),setTimeout(function(){se.remove(),l()},o)},o)):setTimeout(()=>l(),o)}),se},isStarted:()=>typeof se.status=="number",start:()=>{se.status||se.set(0);const e=()=>{setTimeout(()=>{se.status&&(se.trickle(),e())},se.settings.trickleSpeed)};return se.settings.trickle&&e(),se},done:e=>!e&&!se.status?se:se.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=se.status;return t?(typeof e!="number"&&(e=(1-t)*mo(Math.random()*t,.1,.95)),t=mo(t+e,0,.994),se.set(t)):se.start()},trickle:()=>se.inc(Math.random()*se.settings.trickleRate),render:e=>{if(se.isRendered())return document.getElementById("nprogress");Wl(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=se.settings.template;const n=t.querySelector(se.settings.barSelector),r=e?"-100":zl(se.status||0),o=document.querySelector(se.settings.parent);return kr(n,{transition:"all 0 linear",transform:"translate3d("+r+"%,0,0)"}),o!==document.body&&Wl(o,"nprogress-custom-parent"),o==null||o.appendChild(t),t},remove:()=>{Ul(document.documentElement,"nprogress-busy"),Ul(document.querySelector(se.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&Jh(e)},isRendered:()=>!!document.getElementById("nprogress")},mo=(e,t,n)=>en?n:e,zl=e=>(-1+e)*100,Yh=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),kr=function(){const e=["Webkit","O","Moz","ms"],t={};function n(l){return l.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(a,i){return i.toUpperCase()})}function r(l){const a=document.body.style;if(l in a)return l;let i=e.length;const u=l.charAt(0).toUpperCase()+l.slice(1);let f;for(;i--;)if(f=e[i]+u,f in a)return f;return l}function o(l){return l=n(l),t[l]??(t[l]=r(l))}function s(l,a,i){a=o(a),l.style[a]=i}return function(l,a){for(const i in a){const u=a[i];u!==void 0&&Object.prototype.hasOwnProperty.call(a,i)&&s(l,i,u)}}}(),Ni=(e,t)=>(typeof e=="string"?e:Cs(e)).indexOf(" "+t+" ")>=0,Wl=(e,t)=>{const n=Cs(e),r=n+t;Ni(n,t)||(e.className=r.substring(1))},Ul=(e,t)=>{const n=Cs(e);if(!Ni(e,t))return;const r=n.replace(" "+t+" "," ");e.className=r.substring(1,r.length-1)},Cs=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),Jh=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)},Qh=()=>{ye(()=>{const e=bn(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||se.start()}),e.afterEach(n=>{t.add(n.path),se.done()})})},Xh=et({setup(){Qh()}}),Zh=JSON.parse('{"encrypt":{},"logo":"/image/现代C++并发编程教程.png","navTitle":"现代C++并发编程教程","repo":"https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/","editLinkPattern":"https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/blob/main/:path","contributors":false,"darkmode":"toggle","pageInfo":["ReadingTime"],"locales":{"/":{"lang":"zh-CN","navbarLocales":{"langName":"简体中文","selectLangAriaLabel":"选择语言"},"metaLocales":{"author":"作者","date":"写作日期","origin":"原创","views":"访问量","category":"分类","tag":"标签","readingTime":"阅读时间","words":"字数","toc":"此页内容","prev":"上一页","next":"下一页","lastUpdated":"上次编辑于","contributors":"贡献者","editLink":"编辑此页","print":"打印"},"outlookLocales":{"themeColor":"主题色","darkmode":"外观","fullscreen":"全屏"},"routeLocales":{"skipToContent":"跳至主要內容","notFoundTitle":"页面不存在","notFoundMsg":["这里什么也没有","我们是怎么来到这儿的?","这 是 四 零 四 !","看起来你访问了一个失效的链接"],"back":"返回上一页","home":"带我回家","openInNewWindow":"Open in new window"},"sidebar":[{"text":"首页","link":"/"},{"text":"基本概念","link":"/md/01基本概念"},{"text":"使用线程","link":"/md/02使用线程"},{"text":"共享数据","link":"/md/03共享数据"},{"text":"同步操作","link":"/md/04同步操作"},{"text":"详细分析","link":"/md/详细分析/","collapsible":true,"children":[{"text":"std::thread 的构造-源码解析","link":"/md/详细分析/01thread的构造与源码解析"},{"text":"std::scoped_lock 的源码实现与解析","link":"/md/详细分析/02scoped_lock源码解析"},{"text":"std::async 与 std::future 源码解析","link":"/md/详细分析/03async与future源码解析"}]}]}}}'),e1=Q(Zh),Hi=()=>e1,Di=Symbol(""),t1=()=>{const e=ke(Di);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},n1=(e,t)=>{const{locales:n,...r}=e;return{...r,...n==null?void 0:n[t]}},r1=et({enhance({app:e}){const t=Hi(),n=e._context.provides[ys],r=S(()=>n1(t.value,n.routeLocale.value));e.provide(Di,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})}}),o1=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,s1=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&o1.test(navigator.userAgent),l1=({delay:e=500,duration:t=2e3,locales:n,selector:r,showInMobile:o})=>{const{copy:s,copied:l}=gh({legacy:!0,copiedDuring:t}),a=ar(n),i=be(),u=d=>{if(!d.hasAttribute("copy-code-registered")){const v=document.createElement("button");v.type="button",v.classList.add("vp-copy-code-button"),v.innerHTML='
    ',v.setAttribute("aria-label",a.value.copy),v.setAttribute("data-copied",a.value.copied),d.parentElement&&d.parentElement.insertBefore(v,d),d.setAttribute("copy-code-registered","")}},f=()=>{zt().then(()=>ki(e)).then(()=>{r.forEach(d=>{document.querySelectorAll(d).forEach(u)})})},p=(d,v,g)=>{let{innerText:E=""}=v;/language-(shellscript|shell|bash|sh|zsh)/.test(d.classList.toString())&&(E=E.replace(/^ *(\$|>) /gm,"")),s(E).then(()=>{g.classList.add("copied"),oe(l,()=>{g.classList.remove("copied"),g.blur()},{once:!0})})};ye(()=>{const d=!s1()||o;d&&f(),ge("click",v=>{const g=v.target;if(g.matches('div[class*="language-"] > button.copy')){const E=g.parentElement,b=g.nextElementSibling;b&&p(E,b,g)}else if(g.matches('div[class*="language-"] div.vp-copy-icon')){const E=g.parentElement,b=E.parentElement,y=E.nextElementSibling;y&&p(b,y,E)}}),oe(()=>i.value.path,()=>{d&&f()})})};var a1={"/":{copy:"复制代码",copied:"已复制"}},i1=['.theme-hope-content div[class*="language-"] pre'];const c1=500,u1=2e3,f1=a1,d1=i1,p1=!1,h1=et({setup:()=>{l1({selector:d1,locales:f1,duration:u1,delay:c1,showInMobile:p1})}}),v1=()=>{ge("beforeprint",()=>{document.querySelectorAll("details").forEach(e=>{e.open=!0})})},m1=et({enhance:({app:e})=>{},setup:()=>{v1()}});let g1={};const Bi=Symbol(""),y1=()=>ke(Bi),b1=e=>{e.provide(Bi,g1)},w1='
    ',_1=e=>ve(e)?Array.from(document.querySelectorAll(e)):e.map(t=>Array.from(document.querySelectorAll(t))).flat(),Fi=e=>new Promise((t,n)=>{e.complete?t({type:"image",element:e,src:e.src,width:e.naturalWidth,height:e.naturalHeight,alt:e.alt,msrc:e.src}):(e.onload=()=>t(Fi(e)),e.onerror=r=>n(r))}),E1=e=>{const{isSupported:t,toggle:n}=_s();e.on("uiRegister",()=>{t.value&&e.ui.registerElement({name:"fullscreen",order:7,isButton:!0,html:'',onClick:()=>{n()}}),e.ui.registerElement({name:"download",order:8,isButton:!0,tagName:"a",html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-download"},onInit:(r,o)=>{r.setAttribute("download",""),r.setAttribute("target","_blank"),r.setAttribute("rel","noopener"),o.on("change",()=>{r.setAttribute("href",o.currSlide.data.src)})}}),e.ui.registerElement({name:"bulletsIndicator",className:"photo-swipe-bullets-indicator",appendTo:"wrapper",onInit:(r,o)=>{const s=[];let l=-1;for(let a=0;a{o.goTo(s.indexOf(u.target))},s.push(i),r.appendChild(i)}o.on("change",()=>{l>=0&&s[l].classList.remove("active"),s[o.currIndex].classList.add("active"),l=o.currIndex})}})})},C1=(e,t,n=!0)=>Me(()=>import("./photoswipe.esm-SzV8tJDW.js"),[]).then(({default:r})=>{let o=null;const s=e.map(l=>({html:w1,element:l,msrc:l.src}));return e.forEach((l,a)=>{const i=()=>{o==null||o.destroy(),o=new r({preloaderDelay:0,showHideAnimationType:"zoom",...t,dataSource:s,index:a,...n?{closeOnVerticalDrag:!0,wheelToZoom:!1}:{}}),E1(o),o.addFilter("thumbEl",()=>l),o.addFilter("placeholderSrc",()=>l.src),o.init()};l.getAttribute("photo-swipe")||(l.style.cursor="zoom-in",l.addEventListener("click",()=>{i()}),l.addEventListener("keypress",({key:u})=>{u==="Enter"&&i()}),l.setAttribute("photo-swipe","")),Fi(l).then(u=>{s.splice(a,1,u),o==null||o.refreshSlideContent(a)})}),n?ge("wheel",()=>{o==null||o.close()}):()=>{}}),k1=({selector:e,locales:t,delay:n=500,scrollToClose:r=!0})=>{const o=y1(),s=ar(t),l=be(),a=Se();let i=null;const u=()=>{const{photoSwipe:f}=a.value;f!==!1&&zt().then(()=>ki(n)).then(async()=>{const p=ve(f)?f:e;i=await C1(_1(p),{...o,...s.value},r)})};ye(()=>{u(),oe(()=>l.value.path,()=>{i==null||i(),u()})}),en(()=>{i==null||i()})};var S1={"/":{closeTitle:"关闭",downloadTitle:"下载图片",fullscreenTitle:"切换全屏",zoomTitle:"缩放",arrowPrevTitle:"上一个 (左箭头)",arrowNextTitle:"下一个 (右箭头)"}};const x1=".theme-hope-content :not(a) > img:not([no-view])",A1=S1,T1=800,L1=!0,R1=et({enhance:({app:e})=>{b1(e)},setup:()=>{k1({selector:x1,delay:T1,locales:A1,scrollToClose:L1})}}),ks=e=>{const t=Ct();return S(()=>e[t.value]??{})},P1=(e,t)=>ve(e)&&e.startsWith(t),M1=Object.values,_n=({name:e="",color:t="currentColor"},{slots:n})=>{var r;return c("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:t,"aria-label":`${e} icon`},(r=n.default)==null?void 0:r.call(n))};_n.displayName="IconBase";function O1(){const e=Q(!1),t=yn();return t&&ye(()=>{e.value=!0},t),e}function I1(e){return O1(),S(()=>!!e())}const $1=()=>I1(()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator),N1=()=>{const e=$1();return S(()=>e.value&&/\b(?:Android|iPhone)/i.test(navigator.userAgent))},H1=e=>[/\((ipad);[-\w),; ]+apple/i,/applecoremedia\/[\w.]+ \((ipad)/i,/\b(ipad)\d\d?,\d\d?[;\]].+ios/i].some(t=>t.test(e)),D1=e=>[/ip[honead]{2,4}\b(?:.*os ([\w]+) like mac|; opera)/i,/cfnetwork\/.+darwin/i].some(t=>t.test(e)),B1=e=>[/(mac os x) ?([\w. ]*)/i,/(macintosh|mac_powerpc\b)(?!.+haiku)/i].some(t=>t.test(e)),F1=()=>c(_n,{name:"heading"},()=>c("path",{d:"M250.4 704.6H64V595.4h202.4l26.2-166.6H94V319.6h214.4L352 64h127.8l-43.6 255.4h211.2L691 64h126.2l-43.6 255.4H960v109.2H756.2l-24.6 166.6H930v109.2H717L672 960H545.8l43.6-255.4H376.6L333 960H206.8l43.6-255.4zm168.4-276L394 595.4h211.2l24.6-166.6h-211z"}));F1.displayName="HeadingIcon";const j1=()=>c(_n,{name:"heart"},()=>c("path",{d:"M1024 358.156C1024 195.698 892.3 64 729.844 64c-86.362 0-164.03 37.218-217.844 96.49C458.186 101.218 380.518 64 294.156 64 131.698 64 0 195.698 0 358.156 0 444.518 37.218 522.186 96.49 576H96l320 320c32 32 64 64 96 64s64-32 96-64l320-320h-.49c59.272-53.814 96.49-131.482 96.49-217.844zM841.468 481.232 517.49 805.49a2981.962 2981.962 0 0 1-5.49 5.48c-1.96-1.95-3.814-3.802-5.49-5.48L182.532 481.234C147.366 449.306 128 405.596 128 358.156 128 266.538 202.538 192 294.156 192c47.44 0 91.15 19.366 123.076 54.532L512 350.912l94.768-104.378C638.696 211.366 682.404 192 729.844 192 821.462 192 896 266.538 896 358.156c0 47.44-19.368 91.15-54.532 123.076z"}));j1.displayName="HeartIcon";const V1=()=>c(_n,{name:"history"},()=>c("path",{d:"M512 1024a512 512 0 1 1 512-512 512 512 0 0 1-512 512zm0-896a384 384 0 1 0 384 384 384 384 0 0 0-384-384zm192 448H512a64 64 0 0 1-64-64V320a64 64 0 0 1 128 0v128h128a64 64 0 0 1 0 128z"}));V1.displayName="HistoryIcon";const z1=()=>c(_n,{name:"title"},()=>c("path",{d:"M512 256c70.656 0 134.656 28.672 180.992 75.008A254.933 254.933 0 0 1 768 512c0 83.968-41.024 157.888-103.488 204.48C688.96 748.736 704 788.48 704 832c0 105.984-86.016 192-192 192-106.048 0-192-86.016-192-192h128a64 64 0 1 0 128 0 64 64 0 0 0-64-64 255.19 255.19 0 0 1-181.056-75.008A255.403 255.403 0 0 1 256 512c0-83.968 41.024-157.824 103.488-204.544C335.04 275.264 320 235.584 320 192A192 192 0 0 1 512 0c105.984 0 192 85.952 192 192H576a64.021 64.021 0 0 0-128 0c0 35.328 28.672 64 64 64zM384 512c0 70.656 57.344 128 128 128s128-57.344 128-128-57.344-128-128-128-128 57.344-128 128z"}));z1.displayName="TitleIcon";const Ss=()=>c(_n,{name:"search"},()=>c("path",{d:"M192 480a256 256 0 1 1 512 0 256 256 0 0 1-512 0m631.776 362.496-143.2-143.168A318.464 318.464 0 0 0 768 480c0-176.736-143.264-320-320-320S128 303.264 128 480s143.264 320 320 320a318.016 318.016 0 0 0 184.16-58.592l146.336 146.368c12.512 12.48 32.768 12.48 45.28 0 12.48-12.512 12.48-32.768 0-45.28"}));Ss.displayName="SearchIcon";const ji=()=>c("svg",{xmlns:"http://www.w3.org/2000/svg",width:"32",height:"32",preserveAspectRatio:"xMidYMid",viewBox:"0 0 100 100"},[c("circle",{cx:"28",cy:"75",r:"11",fill:"currentColor"},c("animate",{attributeName:"fill-opacity",begin:"0s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"})),c("path",{fill:"none",stroke:"#88baf0","stroke-width":"10",d:"M28 47a28 28 0 0 1 28 28"},c("animate",{attributeName:"stroke-opacity",begin:"0.1s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"})),c("path",{fill:"none",stroke:"#88baf0","stroke-width":"10",d:"M28 25a50 50 0 0 1 50 50"},c("animate",{attributeName:"stroke-opacity",begin:"0.2s",dur:"1s",keyTimes:"0;0.2;1",repeatCount:"indefinite",values:"0;1;1"}))]);ji.displayName="LoadingIcon";const Vi=({hint:e})=>c("div",{class:"search-pro-result-wrapper loading"},[c(ji),e]);Vi.displayName="SearchLoading";const W1='';var U1={0:{"/":{0:"分",1:"类",2:":",3:" ",4:"$",5:"c",6:"o",7:"n",8:"t",9:"e",10:"n",11:"t"}},1:{"/":{0:"标",1:"签",2:":",3:" ",4:"$",5:"c",6:"o",7:"n",8:"t",9:"e",10:"n",11:"t"}}},q1={"/":{cancel:"取消",placeholder:"搜索",search:"搜索",searching:"搜索中",defaultTitle:"文档",select:"选择",navigate:"切换",autocomplete:"自动补全",exit:"关闭",queryHistory:"搜索历史",resultHistory:"历史结果",emptyHistory:"无搜索历史",emptyResult:"没有找到结果",loading:"正在加载搜索索引..."}},K1={searchDelay:150,suggestDelay:0,queryHistoryCount:5,resultHistoryCount:5,hotKeys:[{key:"k",ctrl:!0},{key:"/",ctrl:!0}],worker:"search-pro.worker.js"};const Nr=K1,zv=U1,zi=Nr.hotKeys,xs=q1,go="Canceled because of new search request.",G1=()=>{const e=new Worker(`/ModernCpp-ConcurrentProgramming-Tutorial/${Nr.worker}`,{}),t={suggest:null,search:null,all:null};return e.addEventListener("message",({data:n})=>{const[r,o,s]=n,l=t[r];(l==null?void 0:l.id)===o&&l.resolve(s)}),e.addEventListener("error",n=>{console.error("Search Worker error:",n)}),{suggest:(n,r,o)=>new Promise((s,l)=>{var i;(i=t.suggest)==null||i.reject(new Error(go));const a=Date.now();e.postMessage({type:"suggest",id:a,query:n,locale:r,options:o}),t.suggest={id:a,resolve:s,reject:l}}),search:(n,r,o)=>new Promise((s,l)=>{var i;(i=t.search)==null||i.reject(new Error(go));const a=Date.now();e.postMessage({type:"search",id:a,query:n,locale:r,options:o}),t.search={id:a,resolve:s,reject:l}}),all:(n,r,o)=>new Promise((s,l)=>{var i;(i=t.all)==null||i.reject(new Error(go));const a=Date.now();e.postMessage({type:"all",id:a,query:n,locale:r,options:o}),t.all={id:a,resolve:s,reject:l}}),terminate:()=>{e.terminate(),M1(t).forEach(n=>{n==null||n.reject(new Error("Worker has been terminated."))})}}};let Y1={};const Wi=Symbol(""),Ui=()=>{const e=Ct(),{locales:t={},...n}=ke(Wi);return S(()=>({...n,...t[e.value]||{}}))},J1=e=>{e.provide(Wi,Y1)},Q1=(e,t=!1)=>{const n=Q(0),r=S(()=>e.value[n.value]),o=()=>{n.value=n.value>0?n.value-1:e.value.length-1},s=()=>{n.value=n.value{t||(n.value=0)}),{index:n,item:r,prev:o,next:s}},X1=e=>e instanceof Element?document.activeElement===e&&(["TEXTAREA","SELECT","INPUT"].includes(e.tagName)||e.hasAttribute("contenteditable")):!1,Z1=e=>zi.some(t=>{const{key:n,ctrl:r=!1,shift:o=!1,alt:s=!1,meta:l=!1}=t;return n===e.key&&r===e.ctrlKey&&o===e.shiftKey&&s===e.altKey&&l===e.metaKey}),e0='',t0='',n0='',r0='',As=Symbol(""),o0=()=>{const e=Q(!1);Zt(As,e)},s0=e=>{const t=Q([]);{const n=Ui(),r=be(),o=Ct();ye(()=>{const{suggest:s,terminate:l}=G1(),a=i=>{const u=i.join(" "),{searchFilter:f,splitWord:p,suggestionsFilter:d=g=>g,...v}=n.value;u?s(u,o.value,v).then(g=>d(g,u,o.value,r.value)).then(g=>{t.value=g.length?P1(g[0],u)&&!g[0].slice(u.length).includes(" ")?g:[u,...g]:[]}).catch(g=>{console.warn(g)}):t.value=[]};oe([e,o],([i])=>a(i),{immediate:!0}),en(()=>{l()})})}return{suggestions:t}},yo=zi[0];var l0=K({name:"SearchBox",setup(){const e=ks(xs),t=ke(As),n=Q(!1),r=S(()=>yo?[(n.value?["⌃","⇧","⌥","⌘"]:["Ctrl","Shift","Alt","Win"]).filter((o,s)=>yo[["ctrl","shift","alt","meta"][s]]),yo.key.toUpperCase()]:null);return ge("keydown",o=>{!t.value&&Z1(o)&&!X1(o.target)&&(o.preventDefault(),t.value=!0)}),ye(()=>{const{userAgent:o}=navigator;n.value=B1(o)||D1(o)||H1(o)}),()=>[c("button",{type:"button",class:"search-pro-button","aria-label":e.value.search,onClick:()=>{t.value=!0}},[c(Ss),c("div",{class:"search-pro-placeholder"},e.value.search),r.value?c("div",{class:"search-pro-key-hints"},r.value.map(o=>c("kbd",{class:"search-pro-key"},o))):null])]}});const a0=Oa({loader:()=>Me(()=>import("./SearchResult-4y_nt7Ph.js"),[]),loadingComponent:()=>{const e=ks(xs);return c(Vi,{hint:e.value.loading})}});var i0=K({name:"SearchModal",setup(){const e=ke(As),t=Gr(),n=N1(),r=ks(xs),o=Ui(),s=Q(""),l=Q([]),{suggestions:a}=s0(l),i=Q(!1),{index:u,prev:f,next:p}=Q1(a),d=Ne(),v=Ne(),g=(b=u.value)=>{s.value=a.value[b],i.value=!1};ge("keydown",b=>{i.value?b.key==="ArrowUp"?f():b.key==="ArrowDown"?p():b.key==="Enter"?g():b.key==="Escape"&&(i.value=!1):b.key==="Escape"&&(e.value=!1)});const E=Ti(()=>{var b,y;(((y=(b=o.value).splitWord)==null?void 0:y.call(b,s.value))??Promise.resolve(s.value.split(" "))).then(C=>{l.value=C})},Math.min(Nr.searchDelay,Nr.suggestDelay));return oe(s,E,{immediate:!0}),ye(()=>{const b=Es(document.body);oe(e,async y=>{var C;b.value=y,y&&(await zt(),(C=d.value)==null||C.focus())}),vh(v,()=>{i.value=!1}),en(()=>{b.value=!1})}),()=>e.value?c("div",{class:"search-pro-modal-wrapper"},[c("div",{class:"search-pro-mask",onClick:()=>{e.value=!1,s.value=""}}),c("div",{class:"search-pro-modal"},[c("div",{class:"search-pro-box"},[c("form",[c("label",{for:"search-pro","aria-label":r.value.search},c(Ss)),c("input",{ref:d,type:"search",class:"search-pro-input",id:"search-pro",placeholder:r.value.placeholder,spellcheck:"false",autocapitalize:"off",autocomplete:"off",autocorrect:"off",name:`${t.value.title}-search`,value:s.value,"aria-controls":"search-pro-results",onKeydown:b=>{const{key:y}=b;a.value.length&&(y==="Tab"?(g(),b.preventDefault()):(y==="ArrowDown"||y==="ArrowUp"||y==="Escape")&&b.preventDefault())},onInput:({target:b})=>{s.value=b.value,i.value=!0,u.value=0}}),s.value?c("button",{type:"reset",class:"search-pro-clear-button",innerHTML:W1,onClick:()=>{s.value=""}}):null,i.value&&a.value.length?c("ul",{class:"search-pro-suggestions",ref:v},a.value.map((b,y)=>c("li",{class:["search-pro-suggestion",{active:y===u.value}],onClick:()=>{g(y)}},[c("kbd",{class:"search-pro-auto-complete",title:`Tab ${r.value.autocomplete}`},"Tab"),b]))):null]),c("button",{type:"button",class:"search-pro-close-button",onClick:()=>{e.value=!1,s.value=""}},r.value.cancel)]),c(a0,{queries:l.value,isFocusing:!i.value,onClose:()=>{e.value=!1},onUpdateQuery:b=>{s.value=b}}),n.value?null:c("div",{class:"search-pro-hints"},[c("span",{class:"search-pro-hint"},[c("kbd",{innerHTML:e0}),r.value.select]),c("span",{class:"search-pro-hint"},[c("kbd",{innerHTML:n0}),c("kbd",{innerHTML:t0}),r.value.navigate]),c("span",{class:"search-pro-hint"},[c("kbd",{innerHTML:r0}),r.value.exit])])])]):null}}),c0=et({enhance({app:e}){J1(e),e.component("SearchBox",l0)},setup(){o0()},rootComponents:[i0]});const Ce=({name:e="",color:t="currentColor"},{slots:n})=>{var r;return c("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:t,"aria-label":`${e} icon`},(r=n.default)==null?void 0:r.call(n))};Ce.displayName="IconBase";const qi=(e,{slots:t})=>{var n;return(n=t.default)==null?void 0:n.call(t)},u0=e=>tn(e)?e:`https://github.com/${e}`,Ts=(e="")=>!tn(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Ki=()=>c(Ce,{name:"github"},()=>c("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));Ki.displayName="GitHubIcon";const Gi=()=>c(Ce,{name:"gitee"},()=>c("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));Gi.displayName="GiteeIcon";const Yi=()=>c(Ce,{name:"bitbucket"},()=>c("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));Yi.displayName="BitbucketIcon";const Ji=()=>c(Ce,{name:"source"},()=>c("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));Ji.displayName="SourceIcon";const f0=({link:e,type:t=Ts(e??"")})=>{if(!t)return null;const n=t.toLowerCase();return c(n==="bitbucket"?Yi:n==="github"?Ki:n==="gitlab"?"GitLab":n==="gitee"?Gi:Ji)},d0=(e,t=0)=>{let n=3735928559^t,r=1103547991^t;for(let o=0,s;o>>16,2246822507),n^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(n^n>>>13,3266489909),4294967296*(2097151&r)+(n>>>0)},Qi=(e,t)=>d0(e)%t,Xi=/#.*$/u,p0=e=>{const t=Xi.exec(e);return t?t[0]:""},ql=e=>decodeURI(e).replace(Xi,"").replace(/\/index\.html$/iu,"/").replace(/\.html$/iu,"").replace(/(README|index)?\.md$/iu,""),Zi=(e,t)=>{if(!qp(t))return!1;const n=ql(e.path),r=ql(t),o=p0(t);return o?o===e.hash&&(!r||n===r):n===r};var h0=e=>Object.prototype.toString.call(e)==="[object Object]",Jn=e=>typeof e=="string";const ec=Array.isArray,Kl=e=>h0(e)&&Jn(e.name),Gl=(e,t=!1)=>e?ec(e)?e.map(n=>Jn(n)?{name:n}:Kl(n)?n:null).filter(n=>n!==null):Jn(e)?[{name:e}]:Kl(e)?[e]:(console.error(`Expect "author" to be \`AuthorInfo[] | AuthorInfo | string[] | string ${t?"":"| false"} | undefined\`, but got`,e),[]):[],tc=(e,t)=>{if(e){if(ec(e)&&e.every(Jn))return e;if(Jn(e))return[e];console.error(`Expect ${t||"value"} to be \`string[] | string | undefined\`, but got`,e)}return[]},v0=e=>tc(e,"category"),m0=e=>tc(e,"tag"),nc=()=>{const e=be();return S(()=>e.value.readingTime??null)},g0=(e,t)=>{const{minutes:n,words:r}=e,{less1Minute:o,word:s,time:l}=t;return{time:n<1?o:l.replace("$time",Math.round(n).toString()),words:s.replace("$word",r.toString())}};var Yl={"/":{word:"约 $word 字",less1Minute:"小于 1 分钟",time:"大约 $time 分钟"}};const Jl={words:"",time:""},Bo=typeof Yl>"u"?null:Yl,y0=()=>Bo?ar(Bo):S(()=>null),b0=()=>{if(typeof Bo>"u")return S(()=>Jl);const e=nc(),t=y0();return S(()=>e.value&&t.value?g0(e.value,t.value):Jl)},En=()=>Hi(),fe=()=>t1(),bo=()=>null,w0="719px",_0="1440px",E0="false",rc={mobileBreakPoint:w0,pcBreakPoint:_0,enableThemeColor:E0},Ls={},oc=e=>{const{icon:t="",color:n,size:r}=e,o=n||r?{}:null;return n&&(o.color=n),r&&(o.height=Number.isNaN(Number(r))?r:`${r}px`),tn(t)?c("img",{class:"icon",src:t,alt:"","no-view":"",style:o}):Qr(t)?c("img",{class:"icon",src:Fe(t),alt:"","aria-hidden":"","no-view":"",style:o}):c(pt("FontIcon"),e)};oc.displayName="HopeIcon";var He=oc;const C0="http://.",Rs=()=>{const e=bn(),t=Et();return n=>{if(n)if(Qr(n))t.path!==n&&e.push(n);else if(Wr(n))window&&window.open(n);else{const r=t.path.slice(0,t.path.lastIndexOf("/"));e.push(new URL(`${r}/${encodeURI(n)}`,C0).pathname)}}},sc=()=>{const e=fe(),t=Se();return S(()=>{const{author:n}=t.value;return n?Gl(n):n===!1?[]:Gl(e.value.author,!1)})},k0=()=>{const e=Se();return S(()=>v0(e.value.category).map(t=>({name:t,path:""})))},S0=()=>{const e=Se();return S(()=>m0(e.value.tag).map(t=>({name:t,path:""})))},x0=()=>{const e=Se(),t=be();return S(()=>{const n=Yp(e.value.date);if(n)return n;const{createdTime:r}=t.value.git||{};return r?new Date(r):null})},A0=()=>{const e=fe(),t=be(),n=Se(),r=sc(),o=k0(),s=S0(),l=x0(),a=nc(),i=b0(),u=S(()=>({author:r.value,category:o.value,date:l.value,localizedDate:t.value.localizedDate,tag:s.value,isOriginal:n.value.isOriginal||!1,readingTime:a.value,readingTimeLocale:i.value,pageview:"pageview"in n.value?n.value.pageview:!0})),f=S(()=>"pageInfo"in n.value?n.value.pageInfo:"pageInfo"in e.value?e.value.pageInfo:null);return{info:u,items:f}},cr=()=>{const e=En();return S(()=>!!e.value.pure)},{mobileBreakPoint:T0,pcBreakPoint:L0}=rc,Ql=e=>e.endsWith("px")?Number(e.slice(0,-2)):null,Xr=()=>{const e=Q(!1),t=Q(!1),n=()=>{e.value=window.innerWidth<=(Ql(T0)??719),t.value=window.innerWidth>=(Ql(L0)??1440)};return ge("resize",n,!1),ge("orientationchange",n,!1),ye(()=>{n()}),{isMobile:e,isPC:t}},lc=Symbol(""),ur=()=>{const e=ke(lc);if(!e)throw new Error("useDarkmode() is called without provider.");return e},R0=e=>{const t=En(),n=Ch(),r=S(()=>t.value.darkmode||"switch"),o=Pi("vuepress-theme-hope-scheme","auto"),s=S(()=>{const a=r.value;return a==="disable"?!1:a==="enable"?!0:a==="auto"?n.value:a==="toggle"?o.value==="dark":o.value==="dark"||o.value==="auto"&&n.value}),l=S(()=>{const a=r.value;return a==="switch"||a==="toggle"});e.provide(lc,{canToggle:l,config:r,isDarkmode:s,status:o}),Object.defineProperties(e.config.globalProperties,{$isDarkmode:{get:()=>s.value}})},P0=()=>{const{config:e,isDarkmode:t,status:n}=ur();Ta(()=>{e.value==="disable"?n.value="light":e.value==="enable"?n.value="dark":e.value==="toggle"&&n.value==="auto"&&(n.value="light")}),ye(()=>{oe(t,r=>document.documentElement.setAttribute("data-theme",r?"dark":"light"),{immediate:!0})})},Qn=(e,t,n=!1)=>"activeMatch"in t?new RegExp(t.activeMatch,"u").test(e.path):Zi(e,t.link)?!0:"children"in t&&!n?t.children.some(r=>Qn(e,r)):!1,ac=(e,t)=>t.type==="group"?t.children.some(n=>n.type==="group"?ac(e,n):n.type==="page"&&Qn(e,n,!0))||"prefix"in t&&Zi(e,t.prefix):!1,Fo=e=>!hs(e)&&!Wr(e);var dt=(e=>(e.title="t",e.shortTitle="s",e.icon="i",e.index="I",e.order="O",e.breadcrumbExclude="b",e))(dt||{}),M0=(e=>(e.type="y",e.author="a",e.date="d",e.localizedDate="l",e.category="c",e.tag="g",e.isEncrypted="n",e.isOriginal="o",e.readingTime="r",e.excerpt="e",e.sticky="u",e.cover="v",e))(M0||{}),O0=(e=>(e.article="a",e.home="h",e.slide="s",e.page="p",e))(O0||{});const Ps=(e,t=!1,n)=>{const{meta:r,path:o,notFound:s}=Qp(e,n);return s?{text:o,link:o}:{text:!t&&r[dt.shortTitle]?r[dt.shortTitle]:r[dt.title]||o,link:o,...r[dt.icon]?{icon:r[dt.icon]}:{}}},Yt=(e="",t="")=>Qr(t)||Wr(t)?t:`${ii(e)}${t}`,jo=({config:e,prefix:t=""})=>{const n=(r,o=t)=>{const s=ve(r)?Ps(Yt(o,r)):r.link?{...r,...Fo(r.link)?{link:Ft(Yt(o,r.link)).path}:{}}:r;if("children"in s){const l=Yt(o,s.prefix),a=s.children==="structure"?Ls[l]:s.children;return{type:"group",...s,prefix:l,children:a.map(i=>n(i,l))}}return{type:"page",...s}};return e.map(r=>n(r))},I0=({config:e,page:t,headerDepth:n})=>{const r=Jr(e).sort((o,s)=>s.length-o.length);for(const o of r)if(Yn(decodeURI(t.path),o)){const s=e[o];return s?jo({config:s==="structure"?Ls[o]:s,page:t,headerDepth:n,prefix:o}):[]}return console.warn(`${t.path} is missing sidebar config.`),[]},$0=({config:e,routeLocale:t,page:n,headerDepth:r})=>e==="structure"?jo({config:Ls[t],page:n,headerDepth:r,prefix:t}):No(e)?jo({config:e,page:n,headerDepth:r}):Ur(e)?I0({config:e,page:n,headerDepth:r}):[],ic=Symbol(""),N0=()=>{const e=Se(),t=fe(),n=be(),r=Ct(),o=S(()=>e.value.home?!1:e.value.sidebar??t.value.sidebar??"structure"),s=S(()=>e.value.headerDepth??t.value.headerDepth??2),l=xi(()=>[o.value,s.value,n.value.path,null],()=>$0({config:o.value,routeLocale:r.value,page:n.value,headerDepth:s.value}));Zt(ic,l)},Ms=()=>{const e=ke(ic);if(!e)throw new Error("useSidebarItems() is called without provider.");return e};var H0=K({name:"PageFooter",setup(){const e=En(),t=fe(),n=Se(),r=sc(),o=S(()=>{const{copyright:u,footer:f}=n.value;return f!==!1&&!!(u||f||t.value.displayFooter)}),s=S(()=>{const{footer:u}=n.value;return u===!1?!1:ve(u)?u:t.value.footer||""}),l=S(()=>r.value.map(({name:u})=>u).join(", ")),a=u=>`Copyright © ${new Date().getFullYear()} ${l.value} ${u?`${u} Licensed`:""}`,i=S(()=>{const{copyright:u,license:f=""}=n.value,{license:p}=e.value,{copyright:d}=t.value;return u??(f?a(f):ve(d)?d:l.value||p?a(p):!1)});return()=>o.value?c("footer",{class:"vp-footer-wrapper"},[s.value?c("div",{class:"vp-footer",innerHTML:s.value}):null,i.value?c("div",{class:"vp-copyright",innerHTML:i.value}):null]):null}}),Ve=K({name:"AutoLink",inheritAttrs:!1,props:{config:{type:Object,required:!0},exact:Boolean,noExternalLinkIcon:Boolean},emits:["focusout"],slots:Object,setup(e,{attrs:t,emit:n,slots:r}){const o=Et(),s=_i(),l=Fr(e,"config"),a=S(()=>tn(l.value.link)),i=S(()=>!a.value&&Wr(l.value.link)),u=S(()=>l.value.target||(a.value?"_blank":void 0)),f=S(()=>u.value==="_blank"),p=S(()=>!a.value&&!i.value&&!f.value),d=S(()=>l.value.rel||(f.value?"noopener noreferrer":null)),v=S(()=>l.value.ariaLabel||l.value.text),g=S(()=>{if(e.exact)return!1;const b=Jr(s.value.locales);return b.length?b.every(y=>y!==l.value.link):l.value.link!=="/"}),E=S(()=>p.value?l.value.activeMatch?new RegExp(l.value.activeMatch,"u").test(o.path):g.value?Yn(o.path,l.value.link):o.path===l.value.link:!1);return()=>{const{before:b,after:y,default:C}=r,{text:w,icon:x,link:H}=l.value;return p.value?c(Ke,{to:H,"aria-label":v.value,...t,class:["nav-link",{active:E.value},t.class],onFocusout:()=>n("focusout")},()=>C?C():[b?b():c(He,{icon:x}),w,y==null?void 0:y()]):c("a",{href:H,rel:d.value,target:u.value,"aria-label":v.value,...t,class:["nav-link",t.class],onFocusout:()=>n("focusout")},C?C():[b?b():c(He,{icon:x}),w,e.noExternalLinkIcon?null:c($i),y==null?void 0:y()])}}}),D0=K({name:"NavbarDropdownLink",props:{config:{type:Object,required:!0}},slots:Object,setup(e,{slots:t}){const n=be(),r=Fr(e,"config"),o=S(()=>r.value.ariaLabel||r.value.text),s=Q(!1);oe(()=>n.value.path,()=>{s.value=!1});const l=a=>{a.detail===0&&(s.value=!s.value)};return()=>{var a;return c("div",{class:["dropdown-wrapper",{open:s.value}]},[c("button",{type:"button",class:"dropdown-title","aria-label":o.value,onClick:l},[((a=t.title)==null?void 0:a.call(t))||c("span",{class:"title"},[c(He,{icon:r.value.icon}),e.config.text]),c("span",{class:"arrow"}),c("ul",{class:"nav-dropdown"},r.value.children.map((i,u)=>{const f=u===r.value.children.length-1;return c("li",{class:"dropdown-item"},"children"in i?[c("h4",{class:"dropdown-subtitle"},i.link?c(Ve,{config:i,onFocusout:()=>{i.children.length===0&&f&&(s.value=!1)}}):c("span",i.text)),c("ul",{class:"dropdown-subitem-wrapper"},i.children.map((p,d)=>c("li",{class:"dropdown-subitem"},c(Ve,{config:p,onFocusout:()=>{d===i.children.length-1&&f&&(s.value=!1)}}))))]:c(Ve,{config:i,onFocusout:()=>{f&&(s.value=!1)}}))}))])])}}});const cc=(e,t="")=>ve(e)?Ps(Yt(t,e)):"children"in e?{...e,...e.link&&Fo(e.link)?{link:Ft(Yt(t,e.link)).path}:{},children:e.children.map(n=>cc(n,Yt(t,e.prefix)))}:{...e,link:Fo(e.link)?Ft(Yt(t,e.link)).path:e.link},uc=()=>{const e=fe(),t=()=>(e.value.navbar||[]).map(n=>cc(n));return xi(()=>e.value.navbar,()=>t())},B0=()=>{const e=fe(),t=S(()=>e.value.repo||null),n=S(()=>t.value?u0(t.value):null),r=S(()=>t.value?Ts(t.value):null),o=S(()=>n.value?e.value.repoLabel??(r.value===null?"Source":r.value):null);return S(()=>!n.value||!o.value||e.value.repoDisplay===!1?null:{type:r.value||"Source",label:o.value,link:n.value})};var F0=K({name:"NavScreenDropdown",props:{config:{type:Object,required:!0}},setup(e){const t=be(),n=Fr(e,"config"),r=S(()=>n.value.ariaLabel||n.value.text),o=Q(!1);oe(()=>t.value.path,()=>{o.value=!1});const s=(l,a)=>a[a.length-1]===l;return()=>[c("button",{type:"button",class:["nav-screen-dropdown-title",{active:o.value}],"aria-label":r.value,onClick:()=>{o.value=!o.value}},[c("span",{class:"title"},[c(He,{icon:n.value.icon}),e.config.text]),c("span",{class:["arrow",o.value?"down":"end"]})]),c("ul",{class:["nav-screen-dropdown",{hide:!o.value}]},n.value.children.map(l=>c("li",{class:"dropdown-item"},"children"in l?[c("h4",{class:"dropdown-subtitle"},l.link?c(Ve,{config:l,onFocusout:()=>{s(l,n.value.children)&&l.children.length===0&&(o.value=!1)}}):c("span",l.text)),c("ul",{class:"dropdown-subitem-wrapper"},l.children.map(a=>c("li",{class:"dropdown-subitem"},c(Ve,{config:a,onFocusout:()=>{s(a,l.children)&&s(l,n.value.children)&&(o.value=!1)}}))))]:c(Ve,{config:l,onFocusout:()=>{s(l,n.value.children)&&(o.value=!1)}}))))]}}),j0=K({name:"NavScreenLinks",setup(){const e=uc();return()=>e.value.length?c("nav",{class:"nav-screen-links"},e.value.map(t=>c("div",{class:"navbar-links-item"},"children"in t?c(F0,{config:t}):c(Ve,{config:t})))):null}});const fc=()=>c(Ce,{name:"dark"},()=>c("path",{d:"M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"}));fc.displayName="DarkIcon";const dc=()=>c(Ce,{name:"light"},()=>c("path",{d:"M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"}));dc.displayName="LightIcon";const pc=()=>c(Ce,{name:"auto"},()=>c("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"}));pc.displayName="AutoIcon";const hc=()=>c(Ce,{name:"enter-fullscreen"},()=>c("path",{d:"M762.773 90.24h-497.28c-96.106 0-174.4 78.293-174.4 174.4v497.28c0 96.107 78.294 174.4 174.4 174.4h497.28c96.107 0 175.04-78.293 174.4-174.4V264.64c0-96.213-78.186-174.4-174.4-174.4zm-387.2 761.173H215.04c-21.867 0-40.427-17.92-41.067-41.066V649.92c0-22.507 17.92-40.427 40.427-40.427 11.307 0 21.227 4.694 28.48 11.947 7.253 7.253 11.947 17.92 11.947 28.48v62.293l145.28-145.28c15.893-15.893 41.813-15.893 57.706 0 15.894 15.894 15.894 41.814 0 57.707l-145.28 145.28h62.294c22.506 0 40.426 17.92 40.426 40.427s-17.173 41.066-39.68 41.066zM650.24 165.76h160.427c21.866 0 40.426 17.92 41.066 41.067v160.426c0 22.507-17.92 40.427-40.426 40.427-11.307 0-21.227-4.693-28.48-11.947-7.254-7.253-11.947-17.92-11.947-28.48v-62.186L625.6 450.347c-15.893 15.893-41.813 15.893-57.707 0-15.893-15.894-15.893-41.814 0-57.707l145.28-145.28H650.88c-22.507 0-40.427-17.92-40.427-40.427s17.174-41.173 39.787-41.173z"}));hc.displayName="EnterFullScreenIcon";const vc=()=>c(Ce,{name:"cancel-fullscreen"},()=>c("path",{d:"M778.468 78.62H247.922c-102.514 0-186.027 83.513-186.027 186.027V795.08c0 102.514 83.513 186.027 186.027 186.027h530.432c102.514 0 186.71-83.513 186.026-186.027V264.647C964.494 162.02 880.981 78.62 778.468 78.62zM250.88 574.35h171.122c23.324 0 43.122 19.115 43.804 43.805v171.121c0 24.008-19.114 43.122-43.122 43.122-12.06 0-22.641-5.006-30.378-12.743s-12.743-19.115-12.743-30.379V722.83L224.597 877.91c-16.953 16.952-44.6 16.952-61.553 0-16.953-16.954-16.953-44.602 0-61.554L318.009 661.39h-66.446c-24.007 0-43.122-19.114-43.122-43.122 0-24.12 18.432-43.918 42.439-43.918zm521.899-98.873H601.657c-23.325 0-43.122-19.114-43.805-43.804V260.55c0-24.007 19.115-43.122 43.122-43.122 12.06 0 22.642 5.007 30.379 12.743s12.743 19.115 12.743 30.38v66.445l154.965-154.965c16.953-16.953 44.601-16.953 61.554 0 16.953 16.953 16.953 44.6 0 61.554L705.536 388.55h66.446c24.007 0 43.122 19.115 43.122 43.122.114 24.007-18.318 43.804-42.325 43.804z"}));vc.displayName="CancelFullScreenIcon";const mc=()=>c(Ce,{name:"outlook"},()=>[c("path",{d:"M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4 38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32 51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0 102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2 6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4 0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2 9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224 419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4 470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0 22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6 12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128 505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2 16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8 86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4 80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6 6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"})]);mc.displayName="OutlookIcon";var gc=K({name:"AppearanceSwitch",setup(){const{config:e,isDarkmode:t,status:n}=ur(),r=cr(),o=()=>{e.value==="switch"?n.value={light:"dark",dark:"auto",auto:"light"}[n.value]:n.value=n.value==="light"?"dark":"light"},s=async l=>{if(!(document.startViewTransition&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches&&!r.value)||!l){o();return}const a=l.clientX,i=l.clientY,u=Math.hypot(Math.max(a,innerWidth-a),Math.max(i,innerHeight-i)),f=t.value;await document.startViewTransition(async()=>{o(),await zt()}).ready,t.value!==f&&document.documentElement.animate({clipPath:t.value?[`circle(${u}px at ${a}px ${i}px)`,`circle(0px at ${a}px ${i}px)`]:[`circle(0px at ${a}px ${i}px)`,`circle(${u}px at ${a}px ${i}px)`]},{duration:400,pseudoElement:t.value?"::view-transition-old(root)":"::view-transition-new(root)"})};return()=>c("button",{type:"button",id:"appearance-switch",onClick:s},[c(pc,{style:{display:n.value==="auto"?"block":"none"}}),c(fc,{style:{display:n.value==="dark"?"block":"none"}}),c(dc,{style:{display:n.value==="light"?"block":"none"}})])}}),V0=K({name:"AppearanceMode",setup(){const e=fe(),{canToggle:t}=ur(),n=S(()=>e.value.outlookLocales.darkmode);return()=>t.value?c("div",{class:"appearance-wrapper"},[c("label",{class:"appearance-title",for:"appearance-switch"},n.value),c(gc)]):null}});const wo=rc.enableThemeColor==="true";var yc=K({name:"ToggleFullScreenButton",setup(){const e=fe(),{isSupported:t,isFullscreen:n,toggle:r}=_s(),o=S(()=>e.value.outlookLocales.fullscreen);return()=>t?c("div",{class:"full-screen-wrapper"},[c("label",{class:"full-screen-title",for:"full-screen-switch"},o.value),c("button",{type:"button",id:"full-screen-switch",class:"full-screen",ariaPressed:n.value,onClick:()=>r()},n.value?c(vc):c(hc))]):null}}),bc=K({name:"OutlookSettings",setup(){const e=En(),t=cr(),n=S(()=>!t.value&&e.value.fullscreen);return()=>c(Yr,()=>[null,c(V0),n.value?c(yc):null])}}),z0=K({name:"NavScreen",props:{show:Boolean},emits:["close"],slots:Object,setup(e,{emit:t,slots:n}){const r=be(),{isMobile:o}=Xr(),s=Ne(),l=Es(s);return ye(()=>{s.value=document.body,oe(o,a=>{!a&&e.show&&(l.value=!1,t("close"))}),oe(()=>r.value.path,()=>{l.value=!1,t("close")})}),en(()=>{l.value=!1}),()=>c(Bt,{name:"fade",onEnter:()=>{l.value=!0},onAfterLeave:()=>{l.value=!1}},()=>{var a,i;return e.show?c("div",{id:"nav-screen"},c("div",{class:"vp-nav-screen-container"},[(a=n.before)==null?void 0:a.call(n),c(j0),c("div",{class:"vp-outlook-wrapper"},c(bc)),(i=n.after)==null?void 0:i.call(n)])):null})}}),W0=K({name:"NavbarBrand",setup(){const e=Ct(),t=Gr(),n=fe(),r=S(()=>n.value.home||e.value),o=S(()=>t.value.title),s=S(()=>n.value.navTitle??o.value),l=S(()=>n.value.logo?Fe(n.value.logo):null),a=S(()=>n.value.logoDark?Fe(n.value.logoDark):null);return()=>c(Ke,{to:r.value,class:"vp-brand"},()=>[l.value?c("img",{class:["vp-nav-logo",{light:!!a.value}],src:l.value,alt:""}):null,a.value?c("img",{class:["vp-nav-logo dark"],src:a.value,alt:""}):null,s.value?c("span",{class:["vp-site-name",{"hide-in-pad":l.value&&n.value.hideSiteNameOnMobile!==!1}]},s.value):null])}}),U0=K({name:"NavbarLinks",setup(){const e=uc();return()=>e.value.length?c("nav",{class:"vp-nav-links"},e.value.map(t=>c("div",{class:"vp-nav-item hide-in-mobile"},"children"in t?c(D0,{config:t}):c(Ve,{config:t})))):null}}),q0=K({name:"RepoLink",setup(){const e=B0();return()=>e.value?c("div",{class:"vp-nav-item vp-action"},c("a",{class:"vp-action-link",href:e.value.link,target:"_blank",rel:"noopener noreferrer","aria-label":e.value.label},c(f0,{type:e.value.type,style:{width:"1.25rem",height:"1.25rem",verticalAlign:"middle"}}))):null}});const wc=({active:e=!1},{emit:t})=>c("button",{type:"button",class:["vp-toggle-navbar-button",{"is-active":e}],"aria-label":"Toggle Navbar","aria-expanded":e,"aria-controls":"nav-screen",onClick:()=>t("toggle")},c("span",[c("span",{class:"vp-top"}),c("span",{class:"vp-middle"}),c("span",{class:"vp-bottom"})]));wc.displayName="ToggleNavbarButton";var K0=wc;const Vo=(e,{emit:t})=>c("button",{type:"button",class:"vp-toggle-sidebar-button",title:"Toggle Sidebar",onClick:()=>t("toggle")},c("span",{class:"icon"}));Vo.displayName="ToggleSidebarButton",Vo.emits=["toggle"];var G0=Vo,Y0=K({name:"OutlookButton",setup(){const{isSupported:e}=_s(),t=En(),n=cr(),r=be(),{canToggle:o}=ur(),s=Q(!1),l=S(()=>!n.value&&t.value.fullscreen&&e);return oe(()=>r.value.path,()=>{s.value=!1}),()=>o.value||l.value||wo?c("div",{class:"vp-nav-item hide-in-mobile"},o.value&&!l.value&&!wo?c(gc):l.value&&!o.value&&!wo?c(yc):c("button",{type:"button",class:["outlook-button",{open:s.value}],tabindex:"-1","aria-hidden":!0},[c(mc),c("div",{class:"outlook-dropdown"},c(bc))])):null}}),J0=K({name:"NavBar",emits:["toggleSidebar"],slots:Object,setup(e,{emit:t,slots:n}){const r=fe(),{isMobile:o}=Xr(),s=Q(!1),l=S(()=>{const{navbarAutoHide:f="mobile"}=r.value;return f!=="none"&&(f==="always"||o.value)}),a=S(()=>r.value.navbarLayout||{start:["Brand"],center:["Links"],end:["Language","Repo","Outlook","Search"]}),i={Brand:W0,Language:bo,Links:U0,Repo:q0,Outlook:Y0,Search:bt("Docsearch")?pt("Docsearch"):bt("SearchBox")?pt("SearchBox"):bo},u=f=>i[f]??(bt(f)?pt(f):bo);return()=>{var f,p,d,v,g,E;return[c("header",{key:"navbar",id:"navbar",class:["vp-navbar",{"auto-hide":l.value,"hide-icon":r.value.navbarIcon===!1}]},[c("div",{class:"vp-navbar-start"},[c(G0,{onToggle:()=>{s.value&&(s.value=!1),t("toggleSidebar")}}),(f=n.startBefore)==null?void 0:f.call(n),(a.value.start||[]).map(b=>c(u(b))),(p=n.startAfter)==null?void 0:p.call(n)]),c("div",{class:"vp-navbar-center"},[(d=n.centerBefore)==null?void 0:d.call(n),(a.value.center||[]).map(b=>c(u(b))),(v=n.centerAfter)==null?void 0:v.call(n)]),c("div",{class:"vp-navbar-end"},[(g=n.endBefore)==null?void 0:g.call(n),(a.value.end||[]).map(b=>c(u(b))),(E=n.endAfter)==null?void 0:E.call(n),c(K0,{active:s.value,onToggle:()=>{s.value=!s.value}})])]),c(z0,{show:s.value,onClose:()=>{s.value=!1}},{before:()=>{var b;return(b=n.screenTop)==null?void 0:b.call(n)},after:()=>{var b;return(b=n.screenBottom)==null?void 0:b.call(n)}})]}}}),Q0=K({name:"SidebarChild",props:{config:{type:Object,required:!0}},setup(e){const t=Et();return()=>ve(e.config.link)?c(Ve,{class:["vp-sidebar-link","vp-sidebar-page",{active:Qn(t,e.config,!0)}],exact:!0,config:e.config}):c("p",e,[c(He,{icon:e.config.icon}),e.config.text])}}),X0=K({name:"SidebarGroup",props:{config:{type:Object,required:!0},open:{type:Boolean,required:!0}},emits:["toggle"],setup(e,{emit:t}){const n=Et(),r=S(()=>Qn(n,e.config)),o=S(()=>Qn(n,e.config,!0));return()=>{const{collapsible:s,children:l=[],icon:a,prefix:i,link:u,text:f}=e.config;return c("section",{class:"vp-sidebar-group"},[c(s?"button":"p",{class:["vp-sidebar-header",{clickable:s||u,exact:o.value,active:r.value}],...s?{type:"button",onClick:()=>t("toggle"),onKeydown:p=>{p.key==="Enter"&&t("toggle")}}:{}},[c(He,{icon:a}),u?c(Ve,{class:"vp-sidebar-title",config:{text:f,link:u},noExternalLinkIcon:!0}):c("span",{class:"vp-sidebar-title"},f),s?c("span",{class:["vp-arrow",e.open?"down":"end"]}):null]),e.open||!s?c(_c,{key:i,config:l}):null])}}}),_c=K({name:"SidebarLinks",props:{config:{type:Array,required:!0}},setup(e){const t=Et(),n=Q(-1),r=o=>{n.value=o===n.value?-1:o};return oe(()=>t.path,()=>{const o=e.config.findIndex(s=>ac(t,s));n.value=o},{immediate:!0,flush:"post"}),()=>c("ul",{class:"vp-sidebar-links"},e.config.map((o,s)=>c("li",o.type==="group"?c(X0,{config:o,open:s===n.value,onToggle:()=>r(s)}):c(Q0,{config:o}))))}}),Z0=K({name:"SideBar",slots:Object,setup(e,{slots:t}){const n=Et(),r=fe(),o=Ms(),s=Ne();return ye(()=>{oe(()=>n.hash,l=>{const a=document.querySelector(`.vp-sidebar a.vp-sidebar-link[href="${n.path}${l}"]`);if(!a)return;const{top:i,height:u}=s.value.getBoundingClientRect(),{top:f,height:p}=a.getBoundingClientRect();fi+u&&a.scrollIntoView(!1)},{immediate:!0})}),()=>{var l,a,i;return c("aside",{ref:s,id:"sidebar",class:["vp-sidebar",{"hide-icon":r.value.sidebarIcon===!1}],key:"sidebar"},[(l=t.top)==null?void 0:l.call(t),((a=t.default)==null?void 0:a.call(t))||c(_c,{config:o.value}),(i=t.bottom)==null?void 0:i.call(t)])}}}),Ec=K({name:"CommonWrapper",props:{containerClass:{type:String,default:""},noNavbar:Boolean,noSidebar:Boolean,noToc:Boolean},slots:Object,setup(e,{slots:t}){const n=bn(),r=be(),o=Se(),s=fe(),{isMobile:l,isPC:a}=Xr(),[i,u]=Do(!1),[f,p]=Do(!1),d=Ms(),v=Q(!1),g=S(()=>e.noNavbar||o.value.navbar===!1||s.value.navbar===!1?!1:!!(r.value.title||s.value.logo||s.value.repo||s.value.navbar)),E=S(()=>e.noSidebar?!1:o.value.sidebar!==!1&&d.value.length!==0&&!o.value.home),b=S(()=>e.noToc||o.value.home?!1:o.value.toc||s.value.toc!==!1&&o.value.toc!==!1),y={x:0,y:0},C=k=>{y.x=k.changedTouches[0].clientX,y.y=k.changedTouches[0].clientY},w=k=>{const V=k.changedTouches[0].clientX-y.x,N=k.changedTouches[0].clientY-y.y;Math.abs(V)>Math.abs(N)*1.5&&Math.abs(V)>40&&(V>0&&y.x<=80?u(!0):u(!1))},x=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;let H=0;return ge("scroll",uh(()=>{const k=x();k<=58||k{k||u(!1)}),ye(()=>{const k=Es(document.body);oe(i,N=>{k.value=N});const V=n.afterEach(()=>{u(!1)});en(()=>{k.value=!1,V()})}),()=>c(bt("GlobalEncrypt")?pt("GlobalEncrypt"):qi,()=>c("div",{class:["theme-container",{"no-navbar":!g.value,"no-sidebar":!E.value&&!(t.sidebar||t.sidebarTop||t.sidebarBottom),"has-toc":b.value,"hide-navbar":v.value,"sidebar-collapsed":!l.value&&!a.value&&f.value,"sidebar-open":l.value&&i.value},e.containerClass,o.value.containerClass||""],onTouchStart:C,onTouchEnd:w},[g.value?c(J0,{onToggleSidebar:()=>u()},{startBefore:()=>{var k;return(k=t.navbarStartBefore)==null?void 0:k.call(t)},startAfter:()=>{var k;return(k=t.navbarStartAfter)==null?void 0:k.call(t)},centerBefore:()=>{var k;return(k=t.navbarCenterBefore)==null?void 0:k.call(t)},centerAfter:()=>{var k;return(k=t.navbarCenterAfter)==null?void 0:k.call(t)},endBefore:()=>{var k;return(k=t.navbarEndBefore)==null?void 0:k.call(t)},endAfter:()=>{var k;return(k=t.navbarEndAfter)==null?void 0:k.call(t)},screenTop:()=>{var k;return(k=t.navScreenTop)==null?void 0:k.call(t)},screenBottom:()=>{var k;return(k=t.navScreenBottom)==null?void 0:k.call(t)}}):null,c(Bt,{name:"fade"},()=>i.value?c("div",{class:"vp-sidebar-mask",onClick:()=>u(!1)}):null),c(Bt,{name:"fade"},()=>l.value?null:c("div",{class:"toggle-sidebar-wrapper",onClick:()=>p()},c("span",{class:["arrow",f.value?"end":"start"]}))),c(Z0,{},{...t.sidebar?{default:()=>t.sidebar()}:{},top:()=>{var k;return(k=t.sidebarTop)==null?void 0:k.call(t)},bottom:()=>{var k;return(k=t.sidebarBottom)==null?void 0:k.call(t)}}),t.default(),c(H0)]))}});const cn=K({name:"DropTransition",props:{type:{type:String,default:"single"},delay:{type:Number,default:0},duration:{type:Number,default:.25},appear:Boolean},slots:Object,setup(e,{slots:t}){const n=o=>{o.style.transition=`transform ${e.duration}s ease-in-out ${e.delay}s, opacity ${e.duration}s ease-in-out ${e.delay}s`,o.style.transform="translateY(-20px)",o.style.opacity="0"},r=o=>{o.style.transform="translateY(0)",o.style.opacity="1"};return()=>c(e.type==="single"?Bt:dd,{name:"drop",appear:e.appear,onAppear:n,onAfterAppear:r,onEnter:n,onAfterEnter:r,onBeforeLeave:n},()=>t.default())}}),zo=({custom:e})=>c(Ci,{class:["theme-hope-content",{custom:e}]});zo.displayName="MarkdownContent",zo.props={custom:Boolean};var Cc=zo;const kc=()=>c(Ce,{name:"author"},()=>c("path",{d:"M649.6 633.6c86.4-48 147.2-144 147.2-249.6 0-160-128-288-288-288s-288 128-288 288c0 108.8 57.6 201.6 147.2 249.6-121.6 48-214.4 153.6-240 288-3.2 9.6 0 19.2 6.4 25.6 3.2 9.6 12.8 12.8 22.4 12.8h704c9.6 0 19.2-3.2 25.6-12.8 6.4-6.4 9.6-16 6.4-25.6-25.6-134.4-121.6-240-243.2-288z"}));kc.displayName="AuthorIcon";const Sc=()=>c(Ce,{name:"calendar"},()=>c("path",{d:"M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"}));Sc.displayName="CalendarIcon";const xc=()=>c(Ce,{name:"category"},()=>c("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));xc.displayName="CategoryIcon";const Ac=()=>c(Ce,{name:"eye"},()=>c("path",{d:"M992 512.096c0-5.76-.992-10.592-1.28-11.136-.192-2.88-1.152-8.064-2.08-10.816-.256-.672-.544-1.376-.832-2.08-.48-1.568-1.024-3.104-1.6-4.32C897.664 290.112 707.104 160 512 160c-195.072 0-385.632 130.016-473.76 322.592-1.056 2.112-1.792 4.096-2.272 5.856a55.512 55.512 0 00-.64 1.6c-1.76 5.088-1.792 8.64-1.632 7.744-.832 3.744-1.568 11.168-1.568 11.168-.224 2.272-.224 4.032.032 6.304 0 0 .736 6.464 1.088 7.808.128 1.824.576 4.512 1.12 6.976h-.032c.448 2.08 1.12 4.096 1.984 6.08.48 1.536.992 2.976 1.472 4.032C126.432 733.856 316.992 864 512 864c195.136 0 385.696-130.048 473.216-321.696 1.376-2.496 2.24-4.832 2.848-6.912.256-.608.48-1.184.672-1.728 1.536-4.48 1.856-8.32 1.728-8.32l-.032.032c.608-3.104 1.568-7.744 1.568-13.28zM512 672c-88.224 0-160-71.776-160-160s71.776-160 160-160 160 71.776 160 160-71.776 160-160 160z"}));Ac.displayName="EyeIcon";const Tc=()=>c(Ce,{name:"fire"},()=>c("path",{d:"M726.4 201.6c-12.8-9.6-28.8-6.4-38.4 0-9.6 9.6-16 25.6-9.6 38.4 6.4 12.8 9.6 28.8 12.8 44.8C604.8 83.2 460.8 38.4 454.4 35.2c-9.6-3.2-22.4 0-28.8 6.4-9.6 6.4-12.8 19.2-9.6 28.8 12.8 86.4-25.6 188.8-115.2 310.4-6.4-25.6-16-51.2-32-80-9.6-9.6-22.4-16-35.2-12.8-16 3.2-25.6 12.8-25.6 28.8-3.2 48-25.6 92.8-51.2 140.8C134.4 499.2 112 544 102.4 592c-32 150.4 99.2 329.6 233.6 380.8 9.6 3.2 19.2 6.4 32 9.6-25.6-19.2-41.6-51.2-48-96C294.4 691.2 505.6 640 515.2 460.8c153.6 105.6 224 336 137.6 505.6 3.2 0 6.4-3.2 9.6-3.2 0 0 3.2 0 3.2-3.2 163.2-89.6 252.8-208 259.2-345.6 16-211.2-163.2-390.4-198.4-412.8z"}));Tc.displayName="FireIcon";const Lc=()=>c(Ce,{name:"print"},()=>c("path",{d:"M819.2 364.8h-44.8V128c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v236.8h-44.8C145.067 364.8 96 413.867 96 473.6v192c0 59.733 49.067 108.8 108.8 108.8h44.8V896c0 17.067 14.933 32 32 32h460.8c17.067 0 32-14.933 32-32V774.4h44.8c59.733 0 108.8-49.067 108.8-108.8v-192c0-59.733-49.067-108.8-108.8-108.8zM313.6 160h396.8v204.8H313.6V160zm396.8 704H313.6V620.8h396.8V864zM864 665.6c0 25.6-19.2 44.8-44.8 44.8h-44.8V588.8c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v121.6h-44.8c-25.6 0-44.8-19.2-44.8-44.8v-192c0-25.6 19.2-44.8 44.8-44.8h614.4c25.6 0 44.8 19.2 44.8 44.8v192z"}));Lc.displayName="PrintIcon";const Rc=()=>c(Ce,{name:"tag"},()=>c("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));Rc.displayName="TagIcon";const Pc=()=>c(Ce,{name:"timer"},()=>c("path",{d:"M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"}));Pc.displayName="TimerIcon";const Mc=()=>c(Ce,{name:"word"},()=>[c("path",{d:"M518.217 432.64V73.143A73.143 73.143 0 01603.43 1.097a512 512 0 01419.474 419.474 73.143 73.143 0 01-72.046 85.212H591.36a73.143 73.143 0 01-73.143-73.143z"}),c("path",{d:"M493.714 566.857h340.297a73.143 73.143 0 0173.143 85.577A457.143 457.143 0 11371.566 117.76a73.143 73.143 0 0185.577 73.143v339.383a36.571 36.571 0 0036.571 36.571z"})]);Mc.displayName="WordIcon";const kt=()=>{const e=fe();return S(()=>e.value.metaLocales)};var ev=K({name:"AuthorInfo",inheritAttrs:!1,props:{author:{type:Array,required:!0},pure:Boolean},setup(e){const t=kt();return()=>e.author.length?c("span",{class:"page-author-info","aria-label":`${t.value.author}${e.pure?"":"🖊"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(kc),c("span",e.author.map(n=>n.url?c("a",{class:"page-author-item",href:n.url,target:"_blank",rel:"noopener noreferrer"},n.name):c("span",{class:"page-author-item"},n.name))),c("span",{property:"author",content:e.author.map(n=>n.name).join(", ")})]):null}}),tv=K({name:"CategoryInfo",inheritAttrs:!1,props:{category:{type:Array,required:!0},pure:Boolean},setup(e){const t=kt(),n=Rs();return()=>e.category.length?c("span",{class:"page-category-info","aria-label":`${t.value.category}${e.pure?"":"🌈"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(xc),e.category.map(({name:r,path:o})=>c("span",{class:["page-category-item",{[`category${Qi(r,9)}`]:!e.pure,clickable:o}],role:o?"navigation":"",onClick:()=>{o&&n(o)}},r)),c("meta",{property:"articleSection",content:e.category.map(({name:r})=>r).join(",")})]):null}}),nv=K({name:"DateInfo",inheritAttrs:!1,props:{date:{type:Object,default:null},localizedDate:{type:String,default:""},pure:Boolean},setup(e){const t=wi(),n=kt();return()=>e.date?c("span",{class:"page-date-info","aria-label":`${n.value.date}${e.pure?"":"📅"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(Sc),c("span",c(Yr,()=>e.localizedDate||e.date.toLocaleDateString(t.value))),c("meta",{property:"datePublished",content:e.date.toISOString()||""})]):null}}),rv=K({name:"OriginalInfo",inheritAttrs:!1,props:{isOriginal:Boolean},setup(e){const t=kt();return()=>e.isOriginal?c("span",{class:"page-original-info"},t.value.origin):null}}),ov=K({name:"PageViewInfo",inheritAttrs:!1,props:{pageview:{type:[Boolean,String],default:!1},pure:Boolean},setup(e){const t=Et(),n=kt(),r=Ne(),o=Q(0);return kh(r,()=>{},{childList:!0}),()=>e.pageview?c("span",{class:"page-pageview-info","aria-label":`${n.value.views}${e.pure?"":"🔢"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(o.value<1e3?Ac:Tc),c("span",{ref:r,id:"ArtalkPV",class:"vp-pageview waline-pageview-count","data-path":ve(e.pageview)?e.pageview:t.path,"data-page-key":ve(e.pageview)?e.pageview:t.path},"...")]):null}}),sv=K({name:"ReadingTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},readingTimeLocale:{type:Object,default:()=>null},pure:Boolean},setup(e){const t=kt(),n=S(()=>{if(!e.readingTime)return null;const{minutes:r}=e.readingTime;return r<1?"PT1M":`PT${Math.round(r)}M`});return()=>{var r,o;return(r=e.readingTimeLocale)!=null&&r.time?c("span",{class:"page-reading-time-info","aria-label":`${t.value.readingTime}${e.pure?"":"⌛"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(Pc),c("span",(o=e.readingTimeLocale)==null?void 0:o.time),c("meta",{property:"timeRequired",content:n.value})]):null}}}),lv=K({name:"TagInfo",inheritAttrs:!1,props:{tag:{type:Array,default:()=>[]},pure:Boolean},setup(e){const t=kt(),n=Rs();return()=>e.tag.length?c("span",{class:"page-tag-info","aria-label":`${t.value.tag}${e.pure?"":"🏷"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(Rc),e.tag.map(({name:r,path:o})=>c("span",{class:["page-tag-item",{[`tag${Qi(r,9)}`]:!e.pure,clickable:o}],role:o?"navigation":"",onClick:()=>{o&&n(o)}},r)),c("meta",{property:"keywords",content:e.tag.map(({name:r})=>r).join(",")})]):null}}),av=K({name:"ReadTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},readingTimeLocale:{type:Object,default:()=>null},pure:Boolean},setup(e){const t=kt();return()=>{var n,r,o;return(n=e.readingTimeLocale)!=null&&n.words?c("span",{class:"page-word-info","aria-label":`${t.value.words}${e.pure?"":"🔠"}`,...e.pure?{}:{"data-balloon-pos":"up"}},[c(Mc),c("span",(r=e.readingTimeLocale)==null?void 0:r.words),c("meta",{property:"wordCount",content:(o=e.readingTime)==null?void 0:o.words})]):null}}}),iv=K({name:"PageInfo",components:{AuthorInfo:ev,CategoryInfo:tv,DateInfo:nv,OriginalInfo:rv,PageViewInfo:ov,ReadingTimeInfo:sv,TagInfo:lv,WordInfo:av},props:{items:{type:[Array,Boolean],default:()=>["Author","Original","Date","PageView","ReadingTime","Category","Tag"]},info:{type:Object,required:!0}},setup(e){const t=cr();return()=>e.items?c("div",{class:"page-info"},e.items.map(n=>c(pt(`${n}Info`),{...e.info,pure:t.value}))):null}}),cv=K({name:"PrintButton",setup(){const e=En(),t=fe();return()=>e.value.print===!1?null:c("button",{type:"button",class:"print-button",title:t.value.metaLocales.print,onClick:()=>{window.print()}},c(Lc))}}),uv=K({name:"TOC",props:{items:{type:Array,default:()=>[]},headerDepth:{type:Number,default:2}},slots:Object,setup(e,{slots:t}){const n=Et(),r=be(),o=kt(),[s,l]=Do(),a=Ne(),i=Q("-1.7rem"),u=p=>{var d;(d=a.value)==null||d.scrollTo({top:p,behavior:"smooth"})},f=()=>{if(a.value){const p=document.querySelector(".vp-toc-item.active");p?i.value=`${p.getBoundingClientRect().top-a.value.getBoundingClientRect().top+a.value.scrollTop}px`:i.value="-1.7rem"}else i.value="-1.7rem"};return ye(()=>{oe(()=>n.hash,p=>{if(a.value){const d=document.querySelector(`#toc a.toc-link[href$="${p}"]`);if(!d)return;const{top:v,height:g}=a.value.getBoundingClientRect(),{top:E,height:b}=d.getBoundingClientRect();Ev+g&&u(a.value.scrollTop+E+b-v-g)}}),oe(()=>n.fullPath,f,{flush:"post",immediate:!0})}),()=>{var g,E;const p=({title:b,level:y,slug:C})=>c(Ke,{to:`#${C}`,class:["vp-toc-link",`level${y}`],onClick:()=>{l()}},()=>b),d=(b,y)=>b.length&&y>0?c("ul",{class:"vp-toc-list"},b.map(C=>{const w=d(C.children,y-1);return[c("li",{class:["vp-toc-item",{active:n.hash===`#${C.slug}`}]},p(C)),w?c("li",w):null]})):null,v=e.items.length?d(e.items,e.headerDepth):r.value.headers?d(r.value.headers,e.headerDepth):null;return v?c("div",{class:"vp-toc-placeholder"},[c("aside",{id:"toc"},[(g=t.before)==null?void 0:g.call(t),c("div",{class:"vp-toc-header",onClick:()=>{l()}},[o.value.toc,c(cv),c("div",{class:["arrow",s.value?"down":"end"]})]),c("div",{class:["vp-toc-wrapper",s.value?"open":""],ref:a},[v,c("div",{class:"vp-toc-marker",style:{top:i.value}})]),(E=t.after)==null?void 0:E.call(t)])]):null}}}),Oc=K({name:"SkipLink",props:{content:{type:String,default:"main-content"}},setup(e){const t=be(),n=fe(),r=Ne(),o=({target:s})=>{const l=document.querySelector(s.hash);if(l){const a=()=>{l.removeAttribute("tabindex"),l.removeEventListener("blur",a)};l.setAttribute("tabindex","-1"),l.addEventListener("blur",a),l.focus(),window.scrollTo(0,0)}};return ye(()=>{oe(()=>t.value.path,()=>r.value.focus())}),()=>[c("span",{ref:r,tabindex:"-1"}),c("a",{href:`#${e.content}`,class:"vp-skip-link sr-only",onClick:o},n.value.routeLocales.skipToContent)]}});let _o=null,xn=null;const Wo={wait:()=>_o,pending:()=>{_o=new Promise(e=>{xn=e})},resolve:()=>{xn==null||xn(),_o=null,xn=null}},Ic=(e,{slots:t})=>c(Bt,{name:"fade-slide-y",mode:"out-in",onBeforeEnter:Wo.resolve,onBeforeLeave:Wo.pending},()=>{var n;return(n=t.default)==null?void 0:n.call(t)});Ic.displayName="FadeSlideY";const fv=(e,t)=>{const n=e.replace(t,"/").split("/"),r=[];let o=vs(t);return n.forEach((s,l)=>{l!==n.length-1?(o+=`${s}/`,r.push({link:o,name:s||"Home"})):s!==""&&(o+=s,r.push({link:o,name:s}))}),r},$c=(e,{slots:t})=>{var p,d;const{bgImage:n,bgImageDark:r,bgImageStyle:o,color:s,description:l,image:a,imageDark:i,header:u,features:f=[]}=e;return c("div",{class:"vp-feature-wrapper"},[n?c("div",{class:["vp-feature-bg",{light:r}],style:[{"background-image":`url(${n})`},o]}):null,r?c("div",{class:"vp-feature-bg dark",style:[{"background-image":`url(${r})`},o]}):null,c("div",{class:"vp-feature",style:s?{color:s}:{}},[((p=t.image)==null?void 0:p.call(t,e))||[a?c("img",{class:["vp-feature-image",{light:i}],src:Fe(a),alt:""}):null,i?c("img",{class:"vp-feature-image dark",src:Fe(i),alt:""}):null],((d=t.info)==null?void 0:d.call(t,e))||[u?c("h2",{class:"vp-feature-header"},u):null,l?c("p",{class:"vp-feature-description",innerHTML:l}):null],f.length?c("div",{class:"vp-features"},f.map(({icon:v,title:g,details:E,link:b})=>{const y=[c("h3",{class:"vp-feature-title"},[c(He,{icon:v}),c("span",{innerHTML:g})]),c("p",{class:"vp-feature-details",innerHTML:E})];return b?hs(b)?c("a",{class:"vp-feature-item link",href:b,"aria-label":g,target:"_blank"},y):c(Ke,{class:"vp-feature-item link",to:b,"aria-label":g},()=>y):c("div",{class:"vp-feature-item"},y)})):null])])};$c.displayName="FeaturePanel";var Xl=$c,dv=K({name:"HeroInfo",slots:Object,setup(e,{slots:t}){const n=Se(),r=Gr(),o=S(()=>n.value.heroFullScreen??!1),s=S(()=>{const{heroText:u,tagline:f}=n.value;return{text:u??r.value.title??"Hello",tagline:f??r.value.description??"",isFullScreen:o.value}}),l=S(()=>{const{heroText:u,heroImage:f,heroImageDark:p,heroAlt:d,heroImageStyle:v}=n.value;return{image:f?Fe(f):null,imageDark:p?Fe(p):null,style:v,alt:d||u||"",isFullScreen:o.value}}),a=S(()=>{const{bgImage:u,bgImageDark:f,bgImageStyle:p}=n.value;return{image:ve(u)?Fe(u):null,imageDark:ve(f)?Fe(f):null,bgStyle:p,isFullScreen:o.value}}),i=S(()=>n.value.actions??[]);return()=>{var u,f,p;return c("header",{class:["vp-hero-info-wrapper",{fullscreen:o.value}]},[((u=t.heroBg)==null?void 0:u.call(t,a.value))||[a.value.image?c("div",{class:["vp-hero-mask",{light:a.value.imageDark}],style:[{"background-image":`url(${a.value.image})`},a.value.bgStyle]}):null,a.value.imageDark?c("div",{class:"vp-hero-mask dark",style:[{"background-image":`url(${a.value.imageDark})`},a.value.bgStyle]}):null],c("div",{class:"vp-hero-info"},[((f=t.heroImage)==null?void 0:f.call(t,l.value))||c(cn,{appear:!0,type:"group"},()=>[l.value.image?c("img",{key:"light",class:["vp-hero-image",{light:l.value.imageDark}],style:l.value.style,src:l.value.image,alt:l.value.alt}):null,l.value.imageDark?c("img",{key:"dark",class:"vp-hero-image dark",style:l.value.style,src:l.value.imageDark,alt:l.value.alt}):null]),((p=t.heroInfo)==null?void 0:p.call(t,s.value))??c("div",{class:"vp-hero-infos"},[s.value.text?c(cn,{appear:!0,delay:.04},()=>c("h1",{id:"main-title"},s.value.text)):null,s.value.tagline?c(cn,{appear:!0,delay:.08},()=>c("p",{id:"main-description",innerHTML:s.value.tagline})):null,i.value.length?c(cn,{appear:!0,delay:.12},()=>c("p",{class:"vp-hero-actions"},i.value.map(d=>c(Ve,{class:["vp-hero-action",d.type||"default"],config:d,noExternalLinkIcon:!0},d.icon?{before:()=>c(He,{icon:d.icon})}:{})))):null])])])}}});const Nc=(e,{slots:t})=>{var d,v,g;const{bgImage:n,bgImageDark:r,bgImageStyle:o,color:s,description:l,image:a,imageDark:i,header:u,highlights:f=[],type:p="un-order"}=e;return c("div",{class:"vp-highlight-wrapper",style:s?{color:s}:{}},[n?c("div",{class:["vp-highlight-bg",{light:r}],style:[{"background-image":`url(${n})`},o]}):null,r?c("div",{class:"vp-highlight-bg dark",style:[{"background-image":`url(${r})`},o]}):null,c("div",{class:"vp-highlight"},[((d=t.image)==null?void 0:d.call(t,e))||[a?c("img",{class:["vp-highlight-image",{light:i}],src:Fe(a),alt:""}):null,i?c("img",{class:"vp-highlight-image dark",src:Fe(i),alt:""}):null],((v=t.info)==null?void 0:v.call(t,e))||[c("div",{class:"vp-highlight-info-wrapper"},c("div",{class:"vp-highlight-info"},[u?c("h2",{class:"vp-highlight-header",innerHTML:u}):null,l?c("p",{class:"vp-highlight-description",innerHTML:l}):null,((g=t.highlights)==null?void 0:g.call(t,f))||c(p==="order"?"ol":p==="no-order"?"dl":"ul",{class:"vp-highlights"},f.map(({icon:E,title:b,details:y,link:C})=>{const w=[c(p==="no-order"?"dt":"h3",{class:"vp-highlight-title"},[E?c(He,{class:"vp-highlight-icon",icon:E}):null,c("span",{innerHTML:b})]),y?c(p==="no-order"?"dd":"p",{class:"vp-highlight-details",innerHTML:y}):null];return c(p==="no-order"?"div":"li",{class:["vp-highlight-item-wrapper",{link:C}]},C?hs(C)?c("a",{class:"vp-highlight-item link",href:C,"aria-label":b,target:"_blank"},w):c(Ke,{class:"vp-highlight-item link",to:C,"aria-label":b},()=>w):c("div",{class:"vp-highlight-item"},w))}))]))]])])};Nc.displayName="HighlightPanel";var pv=Nc,hv=K({name:"HomePage",slots:Object,setup(e,{slots:t}){const n=cr(),r=Se(),o=S(()=>{const{features:l}=r.value;return No(l)?l:null}),s=S(()=>{const{highlights:l}=r.value;return No(l)?l:null});return()=>{var l,a,i,u;return c("main",{id:"main-content",class:["vp-project-home ",{pure:n.value}],"aria-labelledby":r.value.heroText===null?"":"main-title"},[(l=t.top)==null?void 0:l.call(t),c(dv),((a=s.value)==null?void 0:a.map(f=>"features"in f?c(Xl,f):c(pv,f)))||(o.value?c(cn,{appear:!0,delay:.24},()=>c(Xl,{features:o.value})):null),(i=t.center)==null?void 0:i.call(t),c(cn,{appear:!0,delay:.32},()=>c(Cc)),(u=t.bottom)==null?void 0:u.call(t)])}}}),vv=K({name:"BreadCrumb",setup(){const e=be(),t=Ct(),n=Se(),r=fe(),o=Ne([]),s=S(()=>(n.value.breadcrumb||n.value.breadcrumb!==!1&&r.value.breadcrumb!==!1)&&o.value.length>1),l=S(()=>n.value.breadcrumbIcon||n.value.breadcrumbIcon!==!1&&r.value.breadcrumbIcon!==!1),a=()=>{const i=fv(e.value.path,t.value).map(({link:u,name:f})=>{const{path:p,meta:d,notFound:v}=Ft(u);return v||d[dt.breadcrumbExclude]?null:{title:d[dt.shortTitle]||d[dt.title]||f,icon:d[dt.icon],path:p}}).filter(u=>u!==null);i.length>1&&(o.value=i)};return ye(()=>{oe(()=>e.value.path,a,{immediate:!0})}),()=>c("nav",{class:["vp-breadcrumb",{disable:!s.value}]},s.value?c("ol",{vocab:"https://schema.org/",typeof:"BreadcrumbList"},o.value.map((i,u)=>c("li",{class:{"is-active":o.value.length-1===u},property:"itemListElement",typeof:"ListItem"},[c(Ke,{to:i.path,property:"item",typeof:"WebPage"},()=>[l.value?c(He,{icon:i.icon}):null,c("span",{property:"name"},i.title||"Unknown")]),c("meta",{property:"position",content:u+1})]))):[])}});const Zl=(e,t)=>e===!1||Ur(e)?e:ve(e)?Ps(e,!0,t):null,Uo=(e,t,n)=>{const r=e.findIndex(o=>o.link===t);if(r!==-1){const o=e[r+n];return o!=null&&o.link?o:null}for(const o of e)if("children"in o){const s=Uo(o.children,t,n);if(s)return s}return null};var mv=K({name:"PageNav",setup(){const e=fe(),t=Se(),n=Ms(),r=be(),o=Rs(),s=Et(),l=S(()=>{const i=Zl(t.value.prev,s.path);return i===!1?null:i||(e.value.prevLink===!1?null:Uo(n.value,r.value.path,-1))}),a=S(()=>{const i=Zl(t.value.next,s.path);return i===!1?null:i||(e.value.nextLink===!1?null:Uo(n.value,r.value.path,1))});return ge("keydown",i=>{i.altKey&&(i.key==="ArrowRight"?a.value&&(o(a.value.link),i.preventDefault()):i.key==="ArrowLeft"&&l.value&&(o(l.value.link),i.preventDefault()))}),()=>l.value||a.value?c("nav",{class:"vp-page-nav"},[l.value?c(Ve,{class:"prev",config:l.value},()=>{var i,u;return[c("div",{class:"hint"},[c("span",{class:"arrow start"}),e.value.metaLocales.prev]),c("div",{class:"link"},[c(He,{icon:(i=l.value)==null?void 0:i.icon}),(u=l.value)==null?void 0:u.text])]}):null,a.value?c(Ve,{class:"next",config:a.value},()=>{var i,u;return[c("div",{class:"hint"},[e.value.metaLocales.next,c("span",{class:"arrow end"})]),c("div",{class:"link"},[(i=a.value)==null?void 0:i.text,c(He,{icon:(u=a.value)==null?void 0:u.icon})])]}):null]):null}});const gv=()=>{const e=fe(),t=be(),n=Se();return S(()=>{var r;return n.value.contributors??e.value.contributors??!0?((r=t.value.git)==null?void 0:r.contributors)??null:null})},yv={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},bv=({docsRepo:e,docsBranch:t,docsDir:n,filePathRelative:r,editLinkPattern:o})=>{if(!r)return null;const s=Ts(e);let l;return o?l=o:s!==null&&(l=yv[s]),l?l.replace(/:repo/u,tn(e)?e:`https://github.com/${e}`).replace(/:branch/u,t).replace(/:path/u,ci(`${vs(n)}/${r}`)):null},wv=()=>{const e=fe(),t=be(),n=Se();return S(()=>{const{repo:r,docsRepo:o=r,docsBranch:s="main",docsDir:l="",editLink:a,editLinkPattern:i=""}=e.value;if(!(n.value.editLink??a??!0)||!o)return null;const u=bv({docsRepo:o,docsBranch:s,docsDir:l,editLinkPattern:i,filePathRelative:t.value.filePathRelative});return u?{text:e.value.metaLocales.editLink,link:u}:null})},_v=()=>{const e=Gr(),t=fe(),n=be(),r=Se();return S(()=>{var o,s;return!(r.value.lastUpdated??t.value.lastUpdated??!0)||!((o=n.value.git)!=null&&o.updatedTime)?null:new Date((s=n.value.git)==null?void 0:s.updatedTime).toLocaleString(e.value.lang)})};var Ev=K({name:"PageTitle",setup(){const e=be(),t=Se(),n=fe(),{info:r,items:o}=A0();return()=>c("div",{class:"vp-page-title"},[c("h1",[n.value.titleIcon===!1?null:c(He,{icon:t.value.icon}),e.value.title]),c(iv,{info:r.value,...o.value===null?{}:{items:o.value}}),c("hr")])}});const Hc=()=>c(Ce,{name:"edit"},()=>[c("path",{d:"M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"}),c("path",{d:"M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"})]);Hc.displayName="EditIcon";var Cv=K({name:"PageMeta",setup(){const e=fe(),t=wv(),n=_v(),r=gv();return()=>{const{metaLocales:o}=e.value;return c("footer",{class:"vp-page-meta"},[t.value?c("div",{class:"vp-meta-item edit-link"},c(Ve,{class:"vp-meta-label",config:t.value},{before:()=>c(Hc)})):null,c("div",{class:"vp-meta-item git-info"},[n.value?c("div",{class:"update-time"},[c("span",{class:"vp-meta-label"},`${o.lastUpdated}: `),c(Yr,()=>c("span",{class:"vp-meta-info"},n.value))]):null,r.value&&r.value.length?c("div",{class:"contributors"},[c("span",{class:"vp-meta-label"},`${o.contributors}: `),r.value.map(({email:s,name:l},a)=>[c("span",{class:"vp-meta-info",title:`email: ${s}`},l),a!==r.value.length-1?",":""])]):null])])}}}),kv=K({name:"NormalPage",slots:Object,setup(e,{slots:t}){const n=Se(),{isDarkmode:r}=ur(),o=fe(),s=S(()=>n.value.toc||n.value.toc!==!1&&o.value.toc!==!1);return()=>c("main",{id:"main-content",class:"vp-page"},c(bt("LocalEncrypt")?pt("LocalEncrypt"):qi,()=>{var l,a,i,u;return[(l=t.top)==null?void 0:l.call(t),n.value.cover?c("div",{class:"page-cover"},c("img",{src:Fe(n.value.cover),alt:"","no-view":""})):null,c(vv),c(Ev),s.value?c(uv,{headerDepth:n.value.headerDepth??o.value.headerDepth??2},{before:()=>{var f;return(f=t.tocBefore)==null?void 0:f.call(t)},after:()=>{var f;return(f=t.tocAfter)==null?void 0:f.call(t)}}):null,(a=t.contentBefore)==null?void 0:a.call(t),c(Cc),(i=t.contentAfter)==null?void 0:i.call(t),c(Cv),c(mv),bt("CommentService")?c(pt("CommentService"),{darkmode:r.value}):null,(u=t.bottom)==null?void 0:u.call(t)]}))}}),Sv=K({name:"Layout",slots:Object,setup(e,{slots:t}){fe();const n=be(),r=Se(),{isMobile:o}=Xr(),s=S(()=>"none");return()=>[c(Oc),c(Ec,{},{default:()=>{var l;return((l=t.default)==null?void 0:l.call(t))||(r.value.home?c(hv):c(Ic,()=>c(kv,{key:n.value.path},{top:()=>{var a;return(a=t.top)==null?void 0:a.call(t)},bottom:()=>{var a;return(a=t.bottom)==null?void 0:a.call(t)},contentBefore:()=>{var a;return(a=t.contentBefore)==null?void 0:a.call(t)},contentAfter:()=>{var a;return(a=t.contentAfter)==null?void 0:a.call(t)},tocBefore:()=>{var a;return(a=t.tocBefore)==null?void 0:a.call(t)},tocAfter:()=>{var a;return(a=t.tocAfter)==null?void 0:a.call(t)}})))},...s.value==="none"?{}:{navScreenBottom:()=>c(pt("BloggerInfo"))},...!o.value&&s.value==="always"?{sidebar:()=>c(pt("BloggerInfo"))}:{}})]}}),xv=K({name:"NotFoundHint",setup(){const e=fe(),t=()=>{const n=e.value.routeLocales.notFoundMsg;return n[Math.floor(Math.random()*n.length)]};return()=>c("div",{class:"not-found-hint"},[c("p",{class:"error-code"},"404"),c("h1",{class:"error-title"},e.value.routeLocales.notFoundTitle),c("p",{class:"error-hint"},t())])}}),Av=K({name:"NotFound",slots:Object,setup(e,{slots:t}){const n=bn(),r=Ct(),o=fe();return()=>[c(Oc),c(Ec,{noSidebar:!0},()=>{var s;return c("main",{id:"main-content",class:"vp-page not-found"},((s=t.default)==null?void 0:s.call(t))||[c(xv),c("div",{class:"actions"},[c("button",{type:"button",class:"action-button",onClick:()=>{window.history.go(-1)}},o.value.routeLocales.back),c("button",{type:"button",class:"action-button",onClick:()=>{n.push(o.value.home??r.value)}},o.value.routeLocales.home)])])})]}});Nh(e=>{const t=e.t,n=e.I!==!1,r=e.i;return n?{title:t,content:r?()=>[c(He,{icon:r}),t]:null,order:e.O,index:e.I}:null});const Tv=et({enhance:({app:e,router:t})=>{const{scrollBehavior:n}=t.options;t.options.scrollBehavior=async(...r)=>(await Wo.wait(),n(...r)),R0(e),e.component("HopeIcon",He)},setup:()=>{P0(),N0()},layouts:{Layout:Sv,NotFound:Av}}),Sr=[Up,Zp,$h,jh,Wh,Gh,Xh,r1,h1,m1,R1,c0,Tv],Lv=JSON.parse('{"base":"/ModernCpp-ConcurrentProgramming-Tutorial/","lang":"zh-CN","title":"现代C++并发编程教程","description":"现代C++并发编程教程","head":[["link",{"rel":"icon","href":"/ModernCpp-ConcurrentProgramming-Tutorial/image/现代C++并发编程教程.png"}]],"locales":{}}');var Tn=Ne(Lv),Rv=up,Pv=()=>{const e=$p({history:Rv(vs("/ModernCpp-ConcurrentProgramming-Tutorial/")),routes:[{name:"vuepress-route",path:"/:catchAll(.*)",components:{}}],scrollBehavior:(t,n,r)=>r||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{if(t.path!==n.path||n===gt){const r=Ft(t.path);if(r.path!==t.path)return r.path;const o=await r.loader();t.meta={...r.meta,_pageChunk:o}}else t.path===n.path&&(t.meta=n.meta)}),e},Mv=e=>{e.component("ClientOnly",Yr),e.component("Content",Ci),e.component("RouteLink",Ke)},Ov=(e,t,n)=>{const r=S(()=>t.currentRoute.value.path),o=ls((b,y)=>({get(){return b(),t.currentRoute.value.meta._pageChunk},set(C){t.currentRoute.value.meta._pageChunk=C,y()}})),s=S(()=>qt.resolveLayouts(n)),l=S(()=>qt.resolveRouteLocale(Tn.value.locales,r.value)),a=S(()=>qt.resolveSiteLocaleData(Tn.value,l.value)),i=S(()=>o.value.comp),u=S(()=>o.value.data),f=S(()=>u.value.frontmatter),p=S(()=>qt.resolvePageHeadTitle(u.value,a.value)),d=S(()=>qt.resolvePageHead(p.value,f.value,a.value)),v=S(()=>qt.resolvePageLang(u.value,a.value)),g=S(()=>qt.resolvePageLayout(u.value,s.value)),E={layouts:s,pageData:u,pageComponent:i,pageFrontmatter:f,pageHead:d,pageHeadTitle:p,pageLang:v,pageLayout:g,redirects:$o,routeLocale:l,routePath:r,routes:Gn,siteData:Tn,siteLocaleData:a};return e.provide(ys,E),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>f.value},$head:{get:()=>d.value},$headTitle:{get:()=>p.value},$lang:{get:()=>v.value},$page:{get:()=>u.value},$routeLocale:{get:()=>l.value},$site:{get:()=>Tn.value},$siteLocale:{get:()=>a.value},$withBase:{get:()=>Fe}}),E},Iv=()=>{const e=Dp(),t=wi();let n=[];const r=()=>{e.value.forEach(l=>{const a=$v(l);a&&n.push(a)})},o=()=>{const l=[];return e.value.forEach(a=>{const i=Nv(a);i&&l.push(i)}),l},s=()=>{document.documentElement.lang=t.value;const l=o();n.forEach((a,i)=>{const u=l.findIndex(f=>a.isEqualNode(f));u===-1?(a.remove(),delete n[i]):l.splice(u,1)}),l.forEach(a=>document.head.appendChild(a)),n=[...n.filter(a=>!!a),...l]};Zt(jp,s),ye(()=>{r(),oe(e,s,{immediate:!1})})},$v=([e,t,n=""])=>{const r=Object.entries(t).map(([a,i])=>ve(i)?`[${a}=${JSON.stringify(i)}]`:i===!0?`[${a}]`:"").join(""),o=`head > ${e}${r}`;return Array.from(document.querySelectorAll(o)).find(a=>a.innerText===n)||null},Nv=([e,t,n])=>{if(!ve(e))return null;const r=document.createElement(e);return Ur(t)&&Object.entries(t).forEach(([o,s])=>{ve(s)?r.setAttribute(o,s):s===!0&&r.setAttribute(o,"")}),ve(n)&&r.appendChild(document.createTextNode(n)),r},Hv=bd,Dv=async()=>{var n;const e=Hv({name:"Vuepress",setup(){var s;Iv();for(const l of Sr)(s=l.setup)==null||s.call(l);const r=Sr.flatMap(({rootComponents:l=[]})=>l.map(a=>c(a))),o=Bp();return()=>[c(o.value),r]}}),t=Pv();Mv(e),Ov(e,t,Sr);for(const r of Sr)await((n=r.enhance)==null?void 0:n.call(r,{app:e,router:t,siteData:Tn}));return e.use(t),{app:e,router:t}};Dv().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{ye as A,G1 as B,Ui as C,en as D,zv as E,Ur as F,V1 as G,W1 as H,Ti as I,Nr as J,ve as K,xs as P,Ke as R,Qa as a,Xa as b,Fv as c,Dv as createVueApp,xe as d,jv as e,K as f,bn as g,Ct as h,ks as i,tr as j,Q as k,S as l,ge as m,oe as n,Bv as o,c as p,Vi as q,pt as r,z1 as s,Fr as t,Vv as u,F1 as v,Hu as w,j1 as x,be as y,Ne as z}; diff --git a/assets/index.html-BLSw2nVP.js b/assets/index.html-BLSw2nVP.js new file mode 100644 index 00000000..abcec6d8 --- /dev/null +++ b/assets/index.html-BLSw2nVP.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as n,d as r}from"./app-Oub5ASTw.js";const l={};function s(c,i){const e=a("Catalog");return o(),n("div",null,[r(e)])}const f=t(l,[["render",s],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/image/","title":"Image","lang":"zh-CN","frontmatter":{"title":"Image","article":false,"feed":false,"sitemap":false},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{f as comp,d as data}; diff --git a/assets/index.html-BM0ukr18.js b/assets/index.html-BM0ukr18.js new file mode 100644 index 00000000..54e0f28b --- /dev/null +++ b/assets/index.html-BM0ukr18.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as n,a as e}from"./app-Oub5ASTw.js";const o={},c=e("h1",{id:"详细分析",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#详细分析"},[e("span",null,"详细分析")])],-1),r=e("p",null,"放一些详细分析源码实现之类的内容。",-1),s=[c,r];function i(d,m){return a(),n("div",null,s)}const h=t(o,[["render",i],["__file","index.html.vue"]]),p=JSON.parse('{"path":"/md/%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/","title":"详细分析","lang":"zh-CN","frontmatter":{},"headers":[],"git":{"createdTime":1710500650000,"updatedTime":1711004504000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":2}]},"readingTime":{"minutes":0.07,"words":20},"filePathRelative":"md/详细分析/README.md","localizedDate":"2024年3月15日","excerpt":"\\n

    放一些详细分析源码实现之类的内容。

    \\n"}');export{h as comp,p as data}; diff --git a/assets/index.html-BTzaSACv.js b/assets/index.html-BTzaSACv.js new file mode 100644 index 00000000..c4246b82 --- /dev/null +++ b/assets/index.html-BTzaSACv.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as n,d as r}from"./app-Oub5ASTw.js";const l={};function s(c,i){const e=a("Catalog");return o(),n("div",null,[r(e)])}const _=t(l,[["render",s],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/md/","title":"Md","lang":"zh-CN","frontmatter":{"title":"Md","article":false,"feed":false,"sitemap":false},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{_ as comp,f as data}; diff --git a/assets/index.html-Cr_1xWLb.js b/assets/index.html-Cr_1xWLb.js new file mode 100644 index 00000000..b3c36bfc --- /dev/null +++ b/assets/index.html-Cr_1xWLb.js @@ -0,0 +1 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as s,c as l,a as e,b as r,d as t}from"./app-Oub5ASTw.js";const c="/ModernCpp-ConcurrentProgramming-Tutorial/image/%E7%8E%B0%E4%BB%A3C++%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%95%99%E7%A8%8B.png",i={},p={align:"center"},h=e("a",{herf:"https://zh.cppreference.com/w/cpp/thread"},[e("img",{src:c,width:"512px",alt:"cpp"})],-1),m=e("h1",{id:"现代c-并发编程教程",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#现代c-并发编程教程"},[e("span",null,"现代C++并发编程教程")])],-1),d=e("p",null,[r("本仓库用来存放 B 站课程"),e("a",{href:""},"《现代 C++ 并发编程教程》"),r("的教案、代码。")],-1),_={href:"https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh-hans",target:"_blank",rel:"noopener noreferrer"},u={href:"/image/%E6%8D%90%E8%B5%A0",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/issues",target:"_blank",rel:"noopener noreferrer"},f={href:"https://github.com/Mq-b/ModernCpp-ConcurrentProgramming-Tutorial/pulls",target:"_blank",rel:"noopener noreferrer"},C=e("strong",null,"铭记您的贡献",-1),b=e("hr",null,null,-1),E=e("p",null,[r("  国内的 C++ 并发编程的教程并不稀少,不管是书籍、博客、视频。然而大多数是粗糙的、不够准确、复杂的。而我们想以更加"),e("strong",null,"现代"),r("、"),e("strong",null,"简单"),r("、"),e("strong",null,"准确"),r("的方式进行教学。")],-1),B={href:"https://github.com/Mq-b/Modern-Cpp-templates-tutorial",target:"_blank",rel:"noopener noreferrer"},k=e("strong",null,"现代C++模板教程",-1),M=e("p",null,[r("  本教程假设开发者的最低水平为:"),e("strong",null,[e("code",null,"C++11 + STL + template")]),r("。")],-1),x=e("p",null,"  虽强调现代,但不用担心,我们几乎是从头教学,即使你从来没使用过 C++ 进行多线程编程,也不成问题。",-1),q=e("p",null,"  我们希望您的编译器版本和标准尽可能的高,我们的代码均会测试三大编译器 gcc、clang、msvc。需要更高的标准会进行强调。",-1);function v(T,N){const n=a("ExternalLinkIcon");return s(),l("div",null,[e("div",p,[h,m,d,e("p",null,[r("不管是否购买课程,任何组织和个人遵守 "),e("a",_,[r("CC BY-NC-ND 4.0"),t(n)]),r(" 协议均可随意使用学习。")]),e("p",null,[e("a",u,[r("捐赠"),t(n)]),r("、"),e("a",g,[r("issues"),t(n)]),r("、"),e("a",f,[r("pr"),t(n)]),r(" 均会在致谢列表中"),C,r("。")])]),b,E,e("p",null,[r("  我们在教学中可能常常为您展示部分标准库源码,自己手动实现一些库,这是必须的,希望您是已经较为熟练使用模板(如果没有,可以先学习 "),e("a",B,[k,t(n)]),r(")。阅读源码可以帮助我们更轻松的理解标准库设施的使用与原理。")]),M,x,q])}const D=o(i,[["render",v],["__file","index.html.vue"]]),P=JSON.parse('{"path":"/","title":"现代C++并发编程教程","lang":"zh-CN","frontmatter":{},"headers":[],"git":{"createdTime":1709094546000,"updatedTime":1713362983000,"contributors":[{"name":"归故里","email":"3326284481@qq.com","commits":5},{"name":"mq白","email":"97590219+Mq-b@users.noreply.github.com","commits":2},{"name":"Suzukaze","email":"1027743497@qq.com","commits":1}]},"readingTime":{"minutes":1.35,"words":404},"filePathRelative":"README.md","localizedDate":"2024年2月28日","excerpt":"
    \\n\\n\\"cpp\\"\\n\\n

    现代C++并发编程教程

    \\n

    本仓库用来存放 B 站课程《现代 C++ 并发编程教程》的教案、代码。

    \\n

    不管是否购买课程,任何组织和个人遵守 CC BY-NC-ND 4.0 协议均可随意使用学习。

    \\n

    捐赠issuespr 均会在致谢列表中铭记您的贡献

    \\n
    "}');export{D as comp,P as data}; diff --git a/assets/index.html-aGXxoBnY.js b/assets/index.html-aGXxoBnY.js new file mode 100644 index 00000000..b6ebd9d8 --- /dev/null +++ b/assets/index.html-aGXxoBnY.js @@ -0,0 +1 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as s,c as a,a as e,b as t,d as i}from"./app-Oub5ASTw.js";const c="/ModernCpp-ConcurrentProgramming-Tutorial/assets/%E6%94%AF%E4%BB%98%E5%AE%9D10-BcbPWYKj.jpg",p="/ModernCpp-ConcurrentProgramming-Tutorial/assets/%E6%94%AF%E4%BB%98%E5%AE%9D20-ChMdJN1i.jpg",m="/ModernCpp-ConcurrentProgramming-Tutorial/assets/%E6%94%AF%E4%BB%98%E5%AE%9D88.88-cwJg-qu6.jpg",l={},d=e("div",{align:"center"},[e("img",{src:c,width:"256px",alt:"cpp"}),e("img",{src:p,width:"256px",alt:"cpp"}),e("img",{src:m,width:"256px",alt:"cpp"})],-1),_=e("hr",null,null,-1),g={href:"https://github.com/Mq-b/Modern-Cpp-templates-tutorial/discussions/5",target:"_blank",rel:"noopener noreferrer"},u=e("strong",null,"捐赠初始记录名单",-1);function h(E,f){const n=o("ExternalLinkIcon");return s(),a("div",null,[d,_,e("p",null,[t("  我们会收集捐赠者进行感谢,所以请您捐赠了可以选择备注,或者联系我,或者直接在"),e("a",g,[u,i(n)]),t("中进行评论。")])])}const C=r(l,[["render",h],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/image/%E6%8D%90%E8%B5%A0/","title":"","lang":"zh-CN","frontmatter":{},"headers":[],"git":{"createdTime":1709533660000,"updatedTime":1713362983000,"contributors":[{"name":"Suzukaze","email":"1027743497@qq.com","commits":1},{"name":"归故里","email":"3326284481@qq.com","commits":1}]},"readingTime":{"minutes":0.32,"words":96},"filePathRelative":"image/捐赠/README.md","localizedDate":"2024年3月4日","excerpt":"
    \\n\\n\\n\\n
    \\n
    \\n

      我们会收集捐赠者进行感谢,所以请您捐赠了可以选择备注,或者联系我,或者直接在捐赠初始记录名单中进行评论。

    \\n"}');export{C as comp,b as data}; diff --git a/assets/photoswipe.esm-SzV8tJDW.js b/assets/photoswipe.esm-SzV8tJDW.js new file mode 100644 index 00000000..4048314e --- /dev/null +++ b/assets/photoswipe.esm-SzV8tJDW.js @@ -0,0 +1,4 @@ +/*! + * PhotoSwipe 5.4.3 - https://photoswipe.com + * (c) 2023 Dmytro Semenov + */function f(r,t,i){const e=document.createElement(t);return r&&(e.className=r),i&&i.appendChild(e),e}function p(r,t){return r.x=t.x,r.y=t.y,t.id!==void 0&&(r.id=t.id),r}function M(r){r.x=Math.round(r.x),r.y=Math.round(r.y)}function A(r,t){const i=Math.abs(r.x-t.x),e=Math.abs(r.y-t.y);return Math.sqrt(i*i+e*e)}function x(r,t){return r.x===t.x&&r.y===t.y}function I(r,t,i){return Math.min(Math.max(r,t),i)}function b(r,t,i){let e=`translate3d(${r}px,${t||0}px,0)`;return i!==void 0&&(e+=` scale3d(${i},${i},1)`),e}function y(r,t,i,e){r.style.transform=b(t,i,e)}const $="cubic-bezier(.4,0,.22,1)";function R(r,t,i,e){r.style.transition=t?`${t} ${i}ms ${e||$}`:"none"}function L(r,t,i){r.style.width=typeof t=="number"?`${t}px`:t,r.style.height=typeof i=="number"?`${i}px`:i}function U(r){R(r)}function q(r){return"decode"in r?r.decode().catch(()=>{}):r.complete?Promise.resolve(r):new Promise((t,i)=>{r.onload=()=>t(r),r.onerror=i})}const _={IDLE:"idle",LOADING:"loading",LOADED:"loaded",ERROR:"error"};function G(r){return"button"in r&&r.button===1||r.ctrlKey||r.metaKey||r.altKey||r.shiftKey}function K(r,t,i=document){let e=[];if(r instanceof Element)e=[r];else if(r instanceof NodeList||Array.isArray(r))e=Array.from(r);else{const s=typeof r=="string"?r:t;s&&(e=Array.from(i.querySelectorAll(s)))}return e}function C(){return!!(navigator.vendor&&navigator.vendor.match(/apple/i))}let F=!1;try{window.addEventListener("test",null,Object.defineProperty({},"passive",{get:()=>{F=!0}}))}catch{}class X{constructor(){this._pool=[]}add(t,i,e,s){this._toggleListener(t,i,e,s)}remove(t,i,e,s){this._toggleListener(t,i,e,s,!0)}removeAll(){this._pool.forEach(t=>{this._toggleListener(t.target,t.type,t.listener,t.passive,!0,!0)}),this._pool=[]}_toggleListener(t,i,e,s,n,o){if(!t)return;const a=n?"removeEventListener":"addEventListener";i.split(" ").forEach(l=>{if(l){o||(n?this._pool=this._pool.filter(d=>d.type!==l||d.listener!==e||d.target!==t):this._pool.push({target:t,type:l,listener:e,passive:s}));const c=F?{passive:s||!1}:!1;t[a](l,e,c)}})}}function B(r,t){if(r.getViewportSizeFn){const i=r.getViewportSizeFn(r,t);if(i)return i}return{x:document.documentElement.clientWidth,y:window.innerHeight}}function S(r,t,i,e,s){let n=0;if(t.paddingFn)n=t.paddingFn(i,e,s)[r];else if(t.padding)n=t.padding[r];else{const o="padding"+r[0].toUpperCase()+r.slice(1);t[o]&&(n=t[o])}return Number(n)||0}function N(r,t,i,e){return{x:t.x-S("left",r,t,i,e)-S("right",r,t,i,e),y:t.y-S("top",r,t,i,e)-S("bottom",r,t,i,e)}}class Y{constructor(t){this.slide=t,this.currZoomLevel=1,this.center={x:0,y:0},this.max={x:0,y:0},this.min={x:0,y:0}}update(t){this.currZoomLevel=t,this.slide.width?(this._updateAxis("x"),this._updateAxis("y"),this.slide.pswp.dispatch("calcBounds",{slide:this.slide})):this.reset()}_updateAxis(t){const{pswp:i}=this.slide,e=this.slide[t==="x"?"width":"height"]*this.currZoomLevel,n=S(t==="x"?"left":"top",i.options,i.viewportSize,this.slide.data,this.slide.index),o=this.slide.panAreaSize[t];this.center[t]=Math.round((o-e)/2)+n,this.max[t]=e>o?Math.round(o-e)+n:this.center[t],this.min[t]=e>o?n:this.center[t]}reset(){this.center.x=0,this.center.y=0,this.max.x=0,this.max.y=0,this.min.x=0,this.min.y=0}correctPan(t,i){return I(i,this.max[t],this.min[t])}}const T=4e3;class k{constructor(t,i,e,s){this.pswp=s,this.options=t,this.itemData=i,this.index=e,this.panAreaSize=null,this.elementSize=null,this.fit=1,this.fill=1,this.vFill=1,this.initial=1,this.secondary=1,this.max=1,this.min=1}update(t,i,e){const s={x:t,y:i};this.elementSize=s,this.panAreaSize=e;const n=e.x/s.x,o=e.y/s.y;this.fit=Math.min(1,no?n:o),this.vFill=Math.min(1,o),this.initial=this._getInitial(),this.secondary=this._getSecondary(),this.max=Math.max(this.initial,this.secondary,this._getMax()),this.min=Math.min(this.fit,this.initial,this.secondary),this.pswp&&this.pswp.dispatch("zoomLevelsUpdate",{zoomLevels:this,slideData:this.itemData})}_parseZoomLevelOption(t){const i=t+"ZoomLevel",e=this.options[i];if(e)return typeof e=="function"?e(this):e==="fill"?this.fill:e==="fit"?this.fit:Number(e)}_getSecondary(){let t=this._parseZoomLevelOption("secondary");return t||(t=Math.min(1,this.fit*3),this.elementSize&&t*this.elementSize.x>T&&(t=T/this.elementSize.x),t)}_getInitial(){return this._parseZoomLevelOption("initial")||this.fit}_getMax(){return this._parseZoomLevelOption("max")||Math.max(1,this.fit*4)}}class j{constructor(t,i,e){this.data=t,this.index=i,this.pswp=e,this.isActive=i===e.currIndex,this.currentResolution=0,this.panAreaSize={x:0,y:0},this.pan={x:0,y:0},this.isFirstSlide=this.isActive&&!e.opener.isOpen,this.zoomLevels=new k(e.options,t,i,e),this.pswp.dispatch("gettingData",{slide:this,data:this.data,index:i}),this.content=this.pswp.contentLoader.getContentBySlide(this),this.container=f("pswp__zoom-wrap","div"),this.holderElement=null,this.currZoomLevel=1,this.width=this.content.width,this.height=this.content.height,this.heavyAppended=!1,this.bounds=new Y(this),this.prevDisplayedWidth=-1,this.prevDisplayedHeight=-1,this.pswp.dispatch("slideInit",{slide:this})}setIsActive(t){t&&!this.isActive?this.activate():!t&&this.isActive&&this.deactivate()}append(t){this.holderElement=t,this.container.style.transformOrigin="0 0",this.data&&(this.calculateSize(),this.load(),this.updateContentSize(),this.appendHeavy(),this.holderElement.appendChild(this.container),this.zoomAndPanToInitial(),this.pswp.dispatch("firstZoomPan",{slide:this}),this.applyCurrentZoomPan(),this.pswp.dispatch("afterSetContent",{slide:this}),this.isActive&&this.activate())}load(){this.content.load(!1),this.pswp.dispatch("slideLoad",{slide:this})}appendHeavy(){const{pswp:t}=this;this.heavyAppended||!t.opener.isOpen||t.mainScroll.isShifted()||!this.isActive&&!!0||this.pswp.dispatch("appendHeavy",{slide:this}).defaultPrevented||(this.heavyAppended=!0,this.content.append(),this.pswp.dispatch("appendHeavyContent",{slide:this}))}activate(){this.isActive=!0,this.appendHeavy(),this.content.activate(),this.pswp.dispatch("slideActivate",{slide:this})}deactivate(){this.isActive=!1,this.content.deactivate(),this.currZoomLevel!==this.zoomLevels.initial&&this.calculateSize(),this.currentResolution=0,this.zoomAndPanToInitial(),this.applyCurrentZoomPan(),this.updateContentSize(),this.pswp.dispatch("slideDeactivate",{slide:this})}destroy(){this.content.hasSlide=!1,this.content.remove(),this.container.remove(),this.pswp.dispatch("slideDestroy",{slide:this})}resize(){this.currZoomLevel===this.zoomLevels.initial||!this.isActive?(this.calculateSize(),this.currentResolution=0,this.zoomAndPanToInitial(),this.applyCurrentZoomPan(),this.updateContentSize()):(this.calculateSize(),this.bounds.update(this.currZoomLevel),this.panTo(this.pan.x,this.pan.y))}updateContentSize(t){const i=this.currentResolution||this.zoomLevels.initial;if(!i)return;const e=Math.round(this.width*i)||this.pswp.viewportSize.x,s=Math.round(this.height*i)||this.pswp.viewportSize.y;!this.sizeChanged(e,s)&&!t||this.content.setDisplayedSize(e,s)}sizeChanged(t,i){return t!==this.prevDisplayedWidth||i!==this.prevDisplayedHeight?(this.prevDisplayedWidth=t,this.prevDisplayedHeight=i,!0):!1}getPlaceholderElement(){var t;return(t=this.content.placeholder)===null||t===void 0?void 0:t.element}zoomTo(t,i,e,s){const{pswp:n}=this;if(!this.isZoomable()||n.mainScroll.isShifted())return;n.dispatch("beforeZoomTo",{destZoomLevel:t,centerPoint:i,transitionDuration:e}),n.animations.stopAllPan();const o=this.currZoomLevel;s||(t=I(t,this.zoomLevels.min,this.zoomLevels.max)),this.setZoomLevel(t),this.pan.x=this.calculateZoomToPanOffset("x",i,o),this.pan.y=this.calculateZoomToPanOffset("y",i,o),M(this.pan);const a=()=>{this._setResolution(t),this.applyCurrentZoomPan()};e?n.animations.startTransition({isPan:!0,name:"zoomTo",target:this.container,transform:this.getCurrentTransform(),onComplete:a,duration:e,easing:n.options.easing}):a()}toggleZoom(t){this.zoomTo(this.currZoomLevel===this.zoomLevels.initial?this.zoomLevels.secondary:this.zoomLevels.initial,t,this.pswp.options.zoomAnimationDuration)}setZoomLevel(t){this.currZoomLevel=t,this.bounds.update(this.currZoomLevel)}calculateZoomToPanOffset(t,i,e){if(this.bounds.max[t]-this.bounds.min[t]===0)return this.bounds.center[t];i||(i=this.pswp.getViewportCenterPoint()),e||(e=this.zoomLevels.initial);const n=this.currZoomLevel/e;return this.bounds.correctPan(t,(this.pan[t]-i[t])*n+i[t])}panTo(t,i){this.pan.x=this.bounds.correctPan("x",t),this.pan.y=this.bounds.correctPan("y",i),this.applyCurrentZoomPan()}isPannable(){return!!this.width&&this.currZoomLevel>this.zoomLevels.fit}isZoomable(){return!!this.width&&this.content.isZoomable()}applyCurrentZoomPan(){this._applyZoomTransform(this.pan.x,this.pan.y,this.currZoomLevel),this===this.pswp.currSlide&&this.pswp.dispatch("zoomPanUpdate",{slide:this})}zoomAndPanToInitial(){this.currZoomLevel=this.zoomLevels.initial,this.bounds.update(this.currZoomLevel),p(this.pan,this.bounds.center),this.pswp.dispatch("initialZoomPan",{slide:this})}_applyZoomTransform(t,i,e){e/=this.currentResolution||this.zoomLevels.initial,y(this.container,t,i,e)}calculateSize(){const{pswp:t}=this;p(this.panAreaSize,N(t.options,t.viewportSize,this.data,this.index)),this.zoomLevels.update(this.width,this.height,this.panAreaSize),t.dispatch("calcSlideSize",{slide:this})}getCurrentTransform(){const t=this.currZoomLevel/(this.currentResolution||this.zoomLevels.initial);return b(this.pan.x,this.pan.y,t)}_setResolution(t){t!==this.currentResolution&&(this.currentResolution=t,this.updateContentSize(),this.pswp.dispatch("resolutionChanged"))}}const Q=.35,J=.6,z=.4,E=.5;function tt(r,t){return r*t/(1-t)}class it{constructor(t){this.gestures=t,this.pswp=t.pswp,this.startPan={x:0,y:0}}start(){this.pswp.currSlide&&p(this.startPan,this.pswp.currSlide.pan),this.pswp.animations.stopAll()}change(){const{p1:t,prevP1:i,dragAxis:e}=this.gestures,{currSlide:s}=this.pswp;if(e==="y"&&this.pswp.options.closeOnVerticalDrag&&s&&s.currZoomLevel<=s.zoomLevels.fit&&!this.gestures.isMultitouch){const n=s.pan.y+(t.y-i.y);if(!this.pswp.dispatch("verticalDrag",{panY:n}).defaultPrevented){this._setPanWithFriction("y",n,J);const o=1-Math.abs(this._getVerticalDragRatio(s.pan.y));this.pswp.applyBgOpacity(o),s.applyCurrentZoomPan()}}else this._panOrMoveMainScroll("x")||(this._panOrMoveMainScroll("y"),s&&(M(s.pan),s.applyCurrentZoomPan()))}end(){const{velocity:t}=this.gestures,{mainScroll:i,currSlide:e}=this.pswp;let s=0;if(this.pswp.animations.stopAll(),i.isShifted()){const o=(i.x-i.getCurrSlideX())/this.pswp.viewportSize.x;t.x<-E&&o<0||t.x<.1&&o<-.5?(s=1,t.x=Math.min(t.x,0)):(t.x>E&&o>0||t.x>-.1&&o>.5)&&(s=-1,t.x=Math.max(t.x,0)),i.moveIndexBy(s,!0,t.x)}e&&e.currZoomLevel>e.zoomLevels.max||this.gestures.isMultitouch?this.gestures.zoomLevels.correctZoomPan(!0):(this._finishPanGestureForAxis("x"),this._finishPanGestureForAxis("y"))}_finishPanGestureForAxis(t){const{velocity:i}=this.gestures,{currSlide:e}=this.pswp;if(!e)return;const{pan:s,bounds:n}=e,o=s[t],a=this.pswp.bgOpacity<1&&t==="y",l=o+tt(i[t],.995);if(a){const v=this._getVerticalDragRatio(o),w=this._getVerticalDragRatio(l);if(v<0&&w<-z||v>0&&w>z){this.pswp.close();return}}const c=n.correctPan(t,l);if(o===c)return;const d=c===l?1:.82,u=this.pswp.bgOpacity,m=c-o;this.pswp.animations.startSpring({name:"panGesture"+t,isPan:!0,start:o,end:c,velocity:i[t],dampingRatio:d,onUpdate:v=>{if(a&&this.pswp.bgOpacity<1){const w=1-(c-v)/m;this.pswp.applyBgOpacity(I(u+(1-u)*w,0,1))}s[t]=Math.floor(v),e.applyCurrentZoomPan()}})}_panOrMoveMainScroll(t){const{p1:i,dragAxis:e,prevP1:s,isMultitouch:n}=this.gestures,{currSlide:o,mainScroll:a}=this.pswp,h=i[t]-s[t],l=a.x+h;if(!h||!o)return!1;if(t==="x"&&!o.isPannable()&&!n)return a.moveTo(l,!0),!0;const{bounds:c}=o,d=o.pan[t]+h;if(this.pswp.options.allowPanToNext&&e==="x"&&t==="x"&&!n){const u=a.getCurrSlideX(),m=a.x-u,v=h>0,w=!v;if(d>c.min[t]&&v){if(c.min[t]<=this.startPan[t])return a.moveTo(l,!0),!0;this._setPanWithFriction(t,d)}else if(d0)return a.moveTo(Math.max(l,u),!0),!0;if(m<0)return a.moveTo(Math.min(l,u),!0),!0}else this._setPanWithFriction(t,d)}else t==="y"?!a.isShifted()&&c.min.y!==c.max.y&&this._setPanWithFriction(t,d):this._setPanWithFriction(t,d);return!1}_getVerticalDragRatio(t){var i,e;return(t-((i=(e=this.pswp.currSlide)===null||e===void 0?void 0:e.bounds.center.y)!==null&&i!==void 0?i:0))/(this.pswp.viewportSize.y/3)}_setPanWithFriction(t,i,e){const{currSlide:s}=this.pswp;if(!s)return;const{pan:n,bounds:o}=s;if(o.correctPan(t,i)!==i||e){const h=Math.round(i-n[t]);n[t]+=h*(e||Q)}else n[t]=i}}const et=.05,st=.15;function O(r,t,i){return r.x=(t.x+i.x)/2,r.y=(t.y+i.y)/2,r}class nt{constructor(t){this.gestures=t,this._startPan={x:0,y:0},this._startZoomPoint={x:0,y:0},this._zoomPoint={x:0,y:0},this._wasOverFitZoomLevel=!1,this._startZoomLevel=1}start(){const{currSlide:t}=this.gestures.pswp;t&&(this._startZoomLevel=t.currZoomLevel,p(this._startPan,t.pan)),this.gestures.pswp.animations.stopAllPan(),this._wasOverFitZoomLevel=!1}change(){const{p1:t,startP1:i,p2:e,startP2:s,pswp:n}=this.gestures,{currSlide:o}=n;if(!o)return;const a=o.zoomLevels.min,h=o.zoomLevels.max;if(!o.isZoomable()||n.mainScroll.isShifted())return;O(this._startZoomPoint,i,s),O(this._zoomPoint,t,e);let l=1/A(i,s)*A(t,e)*this._startZoomLevel;if(l>o.zoomLevels.initial+o.zoomLevels.initial/15&&(this._wasOverFitZoomLevel=!0),lh&&(l=h+(l-h)*et);o.pan.x=this._calculatePanForZoomLevel("x",l),o.pan.y=this._calculatePanForZoomLevel("y",l),o.setZoomLevel(l),o.applyCurrentZoomPan()}end(){const{pswp:t}=this.gestures,{currSlide:i}=t;(!i||i.currZoomLevele.zoomLevels.max?n=e.zoomLevels.max:(o=!1,n=s);const a=i.bgOpacity,h=i.bgOpacity<1,l=p({x:0,y:0},e.pan);let c=p({x:0,y:0},l);t&&(this._zoomPoint.x=0,this._zoomPoint.y=0,this._startZoomPoint.x=0,this._startZoomPoint.y=0,this._startZoomLevel=s,p(this._startPan,l)),o&&(c={x:this._calculatePanForZoomLevel("x",n),y:this._calculatePanForZoomLevel("y",n)}),e.setZoomLevel(n),c={x:e.bounds.correctPan("x",c.x),y:e.bounds.correctPan("y",c.y)},e.setZoomLevel(s);const d=!x(c,l);if(!d&&!o&&!h){e._setResolution(n),e.applyCurrentZoomPan();return}i.animations.stopAllPan(),i.animations.startSpring({isPan:!0,start:0,end:1e3,velocity:0,dampingRatio:1,naturalFrequency:40,onUpdate:u=>{if(u/=1e3,d||o){if(d&&(e.pan.x=l.x+(c.x-l.x)*u,e.pan.y=l.y+(c.y-l.y)*u),o){const m=s+(n-s)*u;e.setZoomLevel(m)}e.applyCurrentZoomPan()}h&&i.bgOpacity<1&&i.applyBgOpacity(I(a+(1-a)*u,0,1))},onComplete:()=>{e._setResolution(n),e.applyCurrentZoomPan()}})}}function Z(r){return!!r.target.closest(".pswp__container")}class ot{constructor(t){this.gestures=t}click(t,i){const e=i.target.classList,s=e.contains("pswp__img"),n=e.contains("pswp__item")||e.contains("pswp__zoom-wrap");s?this._doClickOrTapAction("imageClick",t,i):n&&this._doClickOrTapAction("bgClick",t,i)}tap(t,i){Z(i)&&this._doClickOrTapAction("tap",t,i)}doubleTap(t,i){Z(i)&&this._doClickOrTapAction("doubleTap",t,i)}_doClickOrTapAction(t,i,e){var s;const{pswp:n}=this.gestures,{currSlide:o}=n,a=t+"Action",h=n.options[a];if(!n.dispatch(a,{point:i,originalEvent:e}).defaultPrevented){if(typeof h=="function"){h.call(n,i,e);return}switch(h){case"close":case"next":n[h]();break;case"zoom":o==null||o.toggleZoom(i);break;case"zoom-or-close":o!=null&&o.isZoomable()&&o.zoomLevels.secondary!==o.zoomLevels.initial?o.toggleZoom(i):n.options.clickToCloseNonZoomable&&n.close();break;case"toggle-controls":(s=this.gestures.pswp.element)===null||s===void 0||s.classList.toggle("pswp--ui-visible");break}}}}const rt=10,at=300,ht=25;class lt{constructor(t){this.pswp=t,this.dragAxis=null,this.p1={x:0,y:0},this.p2={x:0,y:0},this.prevP1={x:0,y:0},this.prevP2={x:0,y:0},this.startP1={x:0,y:0},this.startP2={x:0,y:0},this.velocity={x:0,y:0},this._lastStartP1={x:0,y:0},this._intervalP1={x:0,y:0},this._numActivePoints=0,this._ongoingPointers=[],this._touchEventEnabled="ontouchstart"in window,this._pointerEventEnabled=!!window.PointerEvent,this.supportsTouch=this._touchEventEnabled||this._pointerEventEnabled&&navigator.maxTouchPoints>1,this._numActivePoints=0,this._intervalTime=0,this._velocityCalculated=!1,this.isMultitouch=!1,this.isDragging=!1,this.isZooming=!1,this.raf=null,this._tapTimer=null,this.supportsTouch||(t.options.allowPanToNext=!1),this.drag=new it(this),this.zoomLevels=new nt(this),this.tapHandler=new ot(this),t.on("bindEvents",()=>{t.events.add(t.scrollWrap,"click",this._onClick.bind(this)),this._pointerEventEnabled?this._bindEvents("pointer","down","up","cancel"):this._touchEventEnabled?(this._bindEvents("touch","start","end","cancel"),t.scrollWrap&&(t.scrollWrap.ontouchmove=()=>{},t.scrollWrap.ontouchend=()=>{})):this._bindEvents("mouse","down","up")})}_bindEvents(t,i,e,s){const{pswp:n}=this,{events:o}=n,a=s?t+s:"";o.add(n.scrollWrap,t+i,this.onPointerDown.bind(this)),o.add(window,t+"move",this.onPointerMove.bind(this)),o.add(window,t+e,this.onPointerUp.bind(this)),a&&o.add(n.scrollWrap,a,this.onPointerUp.bind(this))}onPointerDown(t){const i=t.type==="mousedown"||t.pointerType==="mouse";if(i&&t.button>0)return;const{pswp:e}=this;if(!e.opener.isOpen){t.preventDefault();return}e.dispatch("pointerDown",{originalEvent:t}).defaultPrevented||(i&&(e.mouseDetected(),this._preventPointerEventBehaviour(t,"down")),e.animations.stopAll(),this._updatePoints(t,"down"),this._numActivePoints===1&&(this.dragAxis=null,p(this.startP1,this.p1)),this._numActivePoints>1?(this._clearTapTimer(),this.isMultitouch=!0):this.isMultitouch=!1)}onPointerMove(t){this._preventPointerEventBehaviour(t,"move"),this._numActivePoints&&(this._updatePoints(t,"move"),!this.pswp.dispatch("pointerMove",{originalEvent:t}).defaultPrevented&&(this._numActivePoints===1&&!this.isDragging?(this.dragAxis||this._calculateDragDirection(),this.dragAxis&&!this.isDragging&&(this.isZooming&&(this.isZooming=!1,this.zoomLevels.end()),this.isDragging=!0,this._clearTapTimer(),this._updateStartPoints(),this._intervalTime=Date.now(),this._velocityCalculated=!1,p(this._intervalP1,this.p1),this.velocity.x=0,this.velocity.y=0,this.drag.start(),this._rafStopLoop(),this._rafRenderLoop())):this._numActivePoints>1&&!this.isZooming&&(this._finishDrag(),this.isZooming=!0,this._updateStartPoints(),this.zoomLevels.start(),this._rafStopLoop(),this._rafRenderLoop())))}_finishDrag(){this.isDragging&&(this.isDragging=!1,this._velocityCalculated||this._updateVelocity(!0),this.drag.end(),this.dragAxis=null)}onPointerUp(t){this._numActivePoints&&(this._updatePoints(t,"up"),!this.pswp.dispatch("pointerUp",{originalEvent:t}).defaultPrevented&&(this._numActivePoints===0&&(this._rafStopLoop(),this.isDragging?this._finishDrag():!this.isZooming&&!this.isMultitouch&&this._finishTap(t)),this._numActivePoints<2&&this.isZooming&&(this.isZooming=!1,this.zoomLevels.end(),this._numActivePoints===1&&(this.dragAxis=null,this._updateStartPoints()))))}_rafRenderLoop(){(this.isDragging||this.isZooming)&&(this._updateVelocity(),this.isDragging?x(this.p1,this.prevP1)||this.drag.change():(!x(this.p1,this.prevP1)||!x(this.p2,this.prevP2))&&this.zoomLevels.change(),this._updatePrevPoints(),this.raf=requestAnimationFrame(this._rafRenderLoop.bind(this)))}_updateVelocity(t){const i=Date.now(),e=i-this._intervalTime;e<50&&!t||(this.velocity.x=this._getVelocity("x",e),this.velocity.y=this._getVelocity("y",e),this._intervalTime=i,p(this._intervalP1,this.p1),this._velocityCalculated=!0)}_finishTap(t){const{mainScroll:i}=this.pswp;if(i.isShifted()){i.moveIndexBy(0,!0);return}if(t.type.indexOf("cancel")>0)return;if(t.type==="mouseup"||t.pointerType==="mouse"){this.tapHandler.click(this.startP1,t);return}const e=this.pswp.options.doubleTapAction?at:0;this._tapTimer?(this._clearTapTimer(),A(this._lastStartP1,this.startP1){this.tapHandler.tap(this.startP1,t),this._clearTapTimer()},e))}_clearTapTimer(){this._tapTimer&&(clearTimeout(this._tapTimer),this._tapTimer=null)}_getVelocity(t,i){const e=this.p1[t]-this._intervalP1[t];return Math.abs(e)>1&&i>5?e/i:0}_rafStopLoop(){this.raf&&(cancelAnimationFrame(this.raf),this.raf=null)}_preventPointerEventBehaviour(t,i){this.pswp.applyFilters("preventPointerEvent",!0,t,i)&&t.preventDefault()}_updatePoints(t,i){if(this._pointerEventEnabled){const e=t,s=this._ongoingPointers.findIndex(n=>n.id===e.pointerId);i==="up"&&s>-1?this._ongoingPointers.splice(s,1):i==="down"&&s===-1?this._ongoingPointers.push(this._convertEventPosToPoint(e,{x:0,y:0})):s>-1&&this._convertEventPosToPoint(e,this._ongoingPointers[s]),this._numActivePoints=this._ongoingPointers.length,this._numActivePoints>0&&p(this.p1,this._ongoingPointers[0]),this._numActivePoints>1&&p(this.p2,this._ongoingPointers[1])}else{const e=t;this._numActivePoints=0,e.type.indexOf("touch")>-1?e.touches&&e.touches.length>0&&(this._convertEventPosToPoint(e.touches[0],this.p1),this._numActivePoints++,e.touches.length>1&&(this._convertEventPosToPoint(e.touches[1],this.p2),this._numActivePoints++)):(this._convertEventPosToPoint(t,this.p1),i==="up"?this._numActivePoints=0:this._numActivePoints++)}}_updatePrevPoints(){p(this.prevP1,this.p1),p(this.prevP2,this.p2)}_updateStartPoints(){p(this.startP1,this.p1),p(this.startP2,this.p2),this._updatePrevPoints()}_calculateDragDirection(){if(this.pswp.mainScroll.isShifted())this.dragAxis="x";else{const t=Math.abs(this.p1.x-this.startP1.x)-Math.abs(this.p1.y-this.startP1.y);if(t!==0){const i=t>0?"x":"y";Math.abs(this.p1[i]-this.startP1[i])>=rt&&(this.dragAxis=i)}}}_convertEventPosToPoint(t,i){return i.x=t.pageX-this.pswp.offset.x,i.y=t.pageY-this.pswp.offset.y,"pointerId"in t?i.id=t.pointerId:t.identifier!==void 0&&(i.id=t.identifier),i}_onClick(t){this.pswp.mainScroll.isShifted()&&(t.preventDefault(),t.stopPropagation())}}const ct=.35;class dt{constructor(t){this.pswp=t,this.x=0,this.slideWidth=0,this._currPositionIndex=0,this._prevPositionIndex=0,this._containerShiftIndex=-1,this.itemHolders=[]}resize(t){const{pswp:i}=this,e=Math.round(i.viewportSize.x+i.viewportSize.x*i.options.spacing),s=e!==this.slideWidth;s&&(this.slideWidth=e,this.moveTo(this.getCurrSlideX())),this.itemHolders.forEach((n,o)=>{s&&y(n.el,(o+this._containerShiftIndex)*this.slideWidth),t&&n.slide&&n.slide.resize()})}resetPosition(){this._currPositionIndex=0,this._prevPositionIndex=0,this.slideWidth=0,this._containerShiftIndex=-1}appendHolders(){this.itemHolders=[];for(let t=0;t<3;t++){const i=f("pswp__item","div",this.pswp.container);i.setAttribute("role","group"),i.setAttribute("aria-roledescription","slide"),i.setAttribute("aria-hidden","true"),i.style.display=t===1?"block":"none",this.itemHolders.push({el:i})}}canBeSwiped(){return this.pswp.getNumItems()>1}moveIndexBy(t,i,e){const{pswp:s}=this;let n=s.potentialIndex+t;const o=s.getNumItems();if(s.canLoop()){n=s.getLoopedIndex(n);const h=(t+o)%o;h<=o/2?t=h:t=h-o}else n<0?n=0:n>=o&&(n=o-1),t=n-s.potentialIndex;s.potentialIndex=n,this._currPositionIndex-=t,s.animations.stopMainScroll();const a=this.getCurrSlideX();if(!i)this.moveTo(a),this.updateCurrItem();else{s.animations.startSpring({isMainScroll:!0,start:this.x,end:a,velocity:e||0,naturalFrequency:30,dampingRatio:1,onUpdate:l=>{this.moveTo(l)},onComplete:()=>{this.updateCurrItem(),s.appendHeavy()}});let h=s.potentialIndex-s.currIndex;if(s.canLoop()){const l=(h+o)%o;l<=o/2?h=l:h=l-o}Math.abs(h)>1&&this.updateCurrItem()}return!!t}getCurrSlideX(){return this.slideWidth*this._currPositionIndex}isShifted(){return this.x!==this.getCurrSlideX()}updateCurrItem(){var t;const{pswp:i}=this,e=this._prevPositionIndex-this._currPositionIndex;if(!e)return;this._prevPositionIndex=this._currPositionIndex,i.currIndex=i.potentialIndex;let s=Math.abs(e),n;s>=3&&(this._containerShiftIndex+=e+(e>0?-3:3),s=3);for(let o=0;o0?(n=this.itemHolders.shift(),n&&(this.itemHolders[2]=n,this._containerShiftIndex++,y(n.el,(this._containerShiftIndex+2)*this.slideWidth),i.setContent(n,i.currIndex-s+o+2))):(n=this.itemHolders.pop(),n&&(this.itemHolders.unshift(n),this._containerShiftIndex--,y(n.el,this._containerShiftIndex*this.slideWidth),i.setContent(n,i.currIndex+s-o-2)));Math.abs(this._containerShiftIndex)>50&&!this.isShifted()&&(this.resetPosition(),this.resize()),i.animations.stopAllPan(),this.itemHolders.forEach((o,a)=>{o.slide&&o.slide.setIsActive(a===1)}),i.currSlide=(t=this.itemHolders[1])===null||t===void 0?void 0:t.slide,i.contentLoader.updateLazy(e),i.currSlide&&i.currSlide.applyCurrentZoomPan(),i.dispatch("change")}moveTo(t,i){if(!this.pswp.canLoop()&&i){let e=(this.slideWidth*this._currPositionIndex-t)/this.slideWidth;e+=this.pswp.currIndex;const s=Math.round(t-this.x);(e<0&&s>0||e>=this.pswp.getNumItems()-1&&s<0)&&(t=this.x+s*ct)}this.x=t,this.pswp.container&&y(this.pswp.container,t),this.pswp.dispatch("moveMainScroll",{x:t,dragging:i??!1})}}const pt={Escape:27,z:90,ArrowLeft:37,ArrowUp:38,ArrowRight:39,ArrowDown:40,Tab:9},g=(r,t)=>t?r:pt[r];class ut{constructor(t){this.pswp=t,this._wasFocused=!1,t.on("bindEvents",()=>{t.options.trapFocus&&(t.options.initialPointerPos||this._focusRoot(),t.events.add(document,"focusin",this._onFocusIn.bind(this))),t.events.add(document,"keydown",this._onKeyDown.bind(this))});const i=document.activeElement;t.on("destroy",()=>{t.options.returnFocus&&i&&this._wasFocused&&i.focus()})}_focusRoot(){!this._wasFocused&&this.pswp.element&&(this.pswp.element.focus(),this._wasFocused=!0)}_onKeyDown(t){const{pswp:i}=this;if(i.dispatch("keydown",{originalEvent:t}).defaultPrevented||G(t))return;let e,s,n=!1;const o="key"in t;switch(o?t.key:t.keyCode){case g("Escape",o):i.options.escKey&&(e="close");break;case g("z",o):e="toggleZoom";break;case g("ArrowLeft",o):s="x";break;case g("ArrowUp",o):s="y";break;case g("ArrowRight",o):s="x",n=!0;break;case g("ArrowDown",o):n=!0,s="y";break;case g("Tab",o):this._focusRoot();break}if(s){t.preventDefault();const{currSlide:a}=i;i.options.arrowKeys&&s==="x"&&i.getNumItems()>1?e=n?"next":"prev":a&&a.currZoomLevel>a.zoomLevels.fit&&(a.pan[s]+=n?-80:80,a.panTo(a.pan.x,a.pan.y))}e&&(t.preventDefault(),i[e]())}_onFocusIn(t){const{template:i}=this.pswp;i&&document!==t.target&&i!==t.target&&!i.contains(t.target)&&i.focus()}}const mt="cubic-bezier(.4,0,.22,1)";class ft{constructor(t){var i;this.props=t;const{target:e,onComplete:s,transform:n,onFinish:o=()=>{},duration:a=333,easing:h=mt}=t;this.onFinish=o;const l=n?"transform":"opacity",c=(i=t[l])!==null&&i!==void 0?i:"";this._target=e,this._onComplete=s,this._finished=!1,this._onTransitionEnd=this._onTransitionEnd.bind(this),this._helperTimeout=setTimeout(()=>{R(e,l,a,h),this._helperTimeout=setTimeout(()=>{e.addEventListener("transitionend",this._onTransitionEnd,!1),e.addEventListener("transitioncancel",this._onTransitionEnd,!1),this._helperTimeout=setTimeout(()=>{this._finalizeAnimation()},a+500),e.style[l]=c},30)},0)}_onTransitionEnd(t){t.target===this._target&&this._finalizeAnimation()}_finalizeAnimation(){this._finished||(this._finished=!0,this.onFinish(),this._onComplete&&this._onComplete())}destroy(){this._helperTimeout&&clearTimeout(this._helperTimeout),U(this._target),this._target.removeEventListener("transitionend",this._onTransitionEnd,!1),this._target.removeEventListener("transitioncancel",this._onTransitionEnd,!1),this._finished||this._finalizeAnimation()}}const _t=12,vt=.75;class gt{constructor(t,i,e){this.velocity=t*1e3,this._dampingRatio=i||vt,this._naturalFrequency=e||_t,this._dampedFrequency=this._naturalFrequency,this._dampingRatio<1&&(this._dampedFrequency*=Math.sqrt(1-this._dampingRatio*this._dampingRatio))}easeFrame(t,i){let e=0,s;i/=1e3;const n=Math.E**(-this._dampingRatio*this._naturalFrequency*i);if(this._dampingRatio===1)s=this.velocity+this._naturalFrequency*t,e=(t+s*i)*n,this.velocity=e*-this._naturalFrequency+s*n;else if(this._dampingRatio<1){s=1/this._dampedFrequency*(this._dampingRatio*this._naturalFrequency*t+this.velocity);const o=Math.cos(this._dampedFrequency*i),a=Math.sin(this._dampedFrequency*i);e=n*(t*o+s*a),this.velocity=e*-this._naturalFrequency*this._dampingRatio+n*(-this._dampedFrequency*t*a+this._dampedFrequency*s*o)}return e}}class yt{constructor(t){this.props=t,this._raf=0;const{start:i,end:e,velocity:s,onUpdate:n,onComplete:o,onFinish:a=()=>{},dampingRatio:h,naturalFrequency:l}=t;this.onFinish=a;const c=new gt(s,h,l);let d=Date.now(),u=i-e;const m=()=>{this._raf&&(u=c.easeFrame(u,Date.now()-d),Math.abs(u)<1&&Math.abs(c.velocity)<50?(n(e),o&&o(),this.onFinish()):(d=Date.now(),n(u+e),this._raf=requestAnimationFrame(m)))};this._raf=requestAnimationFrame(m)}destroy(){this._raf>=0&&cancelAnimationFrame(this._raf),this._raf=0}}class wt{constructor(){this.activeAnimations=[]}startSpring(t){this._start(t,!0)}startTransition(t){this._start(t)}_start(t,i){const e=i?new yt(t):new ft(t);return this.activeAnimations.push(e),e.onFinish=()=>this.stop(e),e}stop(t){t.destroy();const i=this.activeAnimations.indexOf(t);i>-1&&this.activeAnimations.splice(i,1)}stopAll(){this.activeAnimations.forEach(t=>{t.destroy()}),this.activeAnimations=[]}stopAllPan(){this.activeAnimations=this.activeAnimations.filter(t=>t.props.isPan?(t.destroy(),!1):!0)}stopMainScroll(){this.activeAnimations=this.activeAnimations.filter(t=>t.props.isMainScroll?(t.destroy(),!1):!0)}isPanRunning(){return this.activeAnimations.some(t=>t.props.isPan)}}class Pt{constructor(t){this.pswp=t,t.events.add(t.element,"wheel",this._onWheel.bind(this))}_onWheel(t){t.preventDefault();const{currSlide:i}=this.pswp;let{deltaX:e,deltaY:s}=t;if(i&&!this.pswp.dispatch("wheel",{originalEvent:t}).defaultPrevented)if(t.ctrlKey||this.pswp.options.wheelToZoom){if(i.isZoomable()){let n=-s;t.deltaMode===1?n*=.05:n*=t.deltaMode?1:.002,n=2**n;const o=i.currZoomLevel*n;i.zoomTo(o,{x:t.clientX,y:t.clientY})}}else i.isPannable()&&(t.deltaMode===1&&(e*=18,s*=18),i.panTo(i.pan.x-e,i.pan.y-s))}}function St(r){if(typeof r=="string")return r;if(!r||!r.isCustomSVG)return"";const t=r;let i='",i}class xt{constructor(t,i){var e;const s=i.name||i.className;let n=i.html;if(t.options[s]===!1)return;typeof t.options[s+"SVG"]=="string"&&(n=t.options[s+"SVG"]),t.dispatch("uiElementCreate",{data:i});let o="";i.isButton?(o+="pswp__button ",o+=i.className||`pswp__button--${i.name}`):o+=i.className||`pswp__${i.name}`;let a=i.isButton?i.tagName||"button":i.tagName||"div";a=a.toLowerCase();const h=f(o,a);if(i.isButton){a==="button"&&(h.type="button");let{title:d}=i;const{ariaLabel:u}=i;typeof t.options[s+"Title"]=="string"&&(d=t.options[s+"Title"]),d&&(h.title=d);const m=u||d;m&&h.setAttribute("aria-label",m)}h.innerHTML=St(n),i.onInit&&i.onInit(h,t),i.onClick&&(h.onclick=d=>{typeof i.onClick=="string"?t[i.onClick]():typeof i.onClick=="function"&&i.onClick(d,h,t)});const l=i.appendTo||"bar";let c=t.element;l==="bar"?(t.topBar||(t.topBar=f("pswp__top-bar pswp__hide-on-close","div",t.scrollWrap)),c=t.topBar):(h.classList.add("pswp__hide-on-close"),l==="wrapper"&&(c=t.scrollWrap)),(e=c)===null||e===void 0||e.appendChild(t.applyFilters("uiElement",h,i))}}function H(r,t,i){r.classList.add("pswp__button--arrow"),r.setAttribute("aria-controls","pswp__items"),t.on("change",()=>{t.options.loop||(i?r.disabled=!(t.currIndex0))})}const bt={name:"arrowPrev",className:"pswp__button--arrow--prev",title:"Previous",order:10,isButton:!0,appendTo:"wrapper",html:{isCustomSVG:!0,size:60,inner:'',outlineID:"pswp__icn-arrow"},onClick:"prev",onInit:H},It={name:"arrowNext",className:"pswp__button--arrow--next",title:"Next",order:11,isButton:!0,appendTo:"wrapper",html:{isCustomSVG:!0,size:60,inner:'',outlineID:"pswp__icn-arrow"},onClick:"next",onInit:(r,t)=>{H(r,t,!0)}},At={name:"close",title:"Close",order:20,isButton:!0,html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-close"},onClick:"close"},Lt={name:"zoom",title:"Zoom",order:10,isButton:!0,html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-zoom"},onClick:"toggleZoom"},Ct={name:"preloader",appendTo:"bar",order:7,html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-loading"},onInit:(r,t)=>{let i,e=null;const s=(a,h)=>{r.classList.toggle("pswp__preloader--"+a,h)},n=a=>{i!==a&&(i=a,s("active",a))},o=()=>{var a;if(!((a=t.currSlide)!==null&&a!==void 0&&a.content.isLoading())){n(!1),e&&(clearTimeout(e),e=null);return}e||(e=setTimeout(()=>{var h;n(!!(!((h=t.currSlide)===null||h===void 0)&&h.content.isLoading())),e=null},t.options.preloaderDelay))};t.on("change",o),t.on("loadComplete",a=>{t.currSlide===a.slide&&o()}),t.ui&&(t.ui.updatePreloaderVisibility=o)}},Tt={name:"counter",order:5,onInit:(r,t)=>{t.on("change",()=>{r.innerText=t.currIndex+1+t.options.indexIndicatorSep+t.getNumItems()})}};function D(r,t){r.classList.toggle("pswp--zoomed-in",t)}class zt{constructor(t){this.pswp=t,this.isRegistered=!1,this.uiElementsData=[],this.items=[],this.updatePreloaderVisibility=()=>{},this._lastUpdatedZoomLevel=void 0}init(){const{pswp:t}=this;this.isRegistered=!1,this.uiElementsData=[At,bt,It,Lt,Ct,Tt],t.dispatch("uiRegister"),this.uiElementsData.sort((i,e)=>(i.order||0)-(e.order||0)),this.items=[],this.isRegistered=!0,this.uiElementsData.forEach(i=>{this.registerElement(i)}),t.on("change",()=>{var i;(i=t.element)===null||i===void 0||i.classList.toggle("pswp--one-slide",t.getNumItems()===1)}),t.on("zoomPanUpdate",()=>this._onZoomPanUpdate())}registerElement(t){this.isRegistered?this.items.push(new xt(this.pswp,t)):this.uiElementsData.push(t)}_onZoomPanUpdate(){const{template:t,currSlide:i,options:e}=this.pswp;if(this.pswp.opener.isClosing||!t||!i)return;let{currZoomLevel:s}=i;if(this.pswp.opener.isOpen||(s=i.zoomLevels.initial),s===this._lastUpdatedZoomLevel)return;this._lastUpdatedZoomLevel=s;const n=i.zoomLevels.initial-i.zoomLevels.secondary;if(Math.abs(n)<.01||!i.isZoomable()){D(t,!1),t.classList.remove("pswp--zoom-allowed");return}t.classList.add("pswp--zoom-allowed");const o=s===i.zoomLevels.initial?i.zoomLevels.secondary:i.zoomLevels.initial;D(t,o<=s),(e.imageClickAction==="zoom"||e.imageClickAction==="zoom-or-close")&&t.classList.add("pswp--click-to-zoom")}}function Et(r){const t=r.getBoundingClientRect();return{x:t.left,y:t.top,w:t.width}}function Ot(r,t,i){const e=r.getBoundingClientRect(),s=e.width/t,n=e.height/i,o=s>n?s:n,a=(e.width-t*o)/2,h=(e.height-i*o)/2,l={x:e.left+a,y:e.top+h,w:t*o};return l.innerRect={w:e.width,h:e.height,x:a,y:h},l}function Zt(r,t,i){const e=i.dispatch("thumbBounds",{index:r,itemData:t,instance:i});if(e.thumbBounds)return e.thumbBounds;const{element:s}=t;let n,o;if(s&&i.options.thumbSelector!==!1){const a=i.options.thumbSelector||"img";o=s.matches(a)?s:s.querySelector(a)}return o=i.applyFilters("thumbEl",o,t,r),o&&(t.thumbCropped?n=Ot(o,t.width||t.w||0,t.height||t.h||0):n=Et(o)),i.applyFilters("thumbBounds",n,t,r)}class Dt{constructor(t,i){this.type=t,this.defaultPrevented=!1,i&&Object.assign(this,i)}preventDefault(){this.defaultPrevented=!0}}class Mt{constructor(){this._listeners={},this._filters={},this.pswp=void 0,this.options=void 0}addFilter(t,i,e=100){var s,n,o;this._filters[t]||(this._filters[t]=[]),(s=this._filters[t])===null||s===void 0||s.push({fn:i,priority:e}),(n=this._filters[t])===null||n===void 0||n.sort((a,h)=>a.priority-h.priority),(o=this.pswp)===null||o===void 0||o.addFilter(t,i,e)}removeFilter(t,i){this._filters[t]&&(this._filters[t]=this._filters[t].filter(e=>e.fn!==i)),this.pswp&&this.pswp.removeFilter(t,i)}applyFilters(t,...i){var e;return(e=this._filters[t])===null||e===void 0||e.forEach(s=>{i[0]=s.fn.apply(this,i)}),i[0]}on(t,i){var e,s;this._listeners[t]||(this._listeners[t]=[]),(e=this._listeners[t])===null||e===void 0||e.push(i),(s=this.pswp)===null||s===void 0||s.on(t,i)}off(t,i){var e;this._listeners[t]&&(this._listeners[t]=this._listeners[t].filter(s=>i!==s)),(e=this.pswp)===null||e===void 0||e.off(t,i)}dispatch(t,i){var e;if(this.pswp)return this.pswp.dispatch(t,i);const s=new Dt(t,i);return(e=this._listeners[t])===null||e===void 0||e.forEach(n=>{n.call(this,s)}),s}}class Rt{constructor(t,i){if(this.element=f("pswp__img pswp__img--placeholder",t?"img":"div",i),t){const e=this.element;e.decoding="async",e.alt="",e.src=t,e.setAttribute("role","presentation")}this.element.setAttribute("aria-hidden","true")}setDisplayedSize(t,i){this.element&&(this.element.tagName==="IMG"?(L(this.element,250,"auto"),this.element.style.transformOrigin="0 0",this.element.style.transform=b(0,0,t/250)):L(this.element,t,i))}destroy(){var t;(t=this.element)!==null&&t!==void 0&&t.parentNode&&this.element.remove(),this.element=null}}class Ft{constructor(t,i,e){this.instance=i,this.data=t,this.index=e,this.element=void 0,this.placeholder=void 0,this.slide=void 0,this.displayedImageWidth=0,this.displayedImageHeight=0,this.width=Number(this.data.w)||Number(this.data.width)||0,this.height=Number(this.data.h)||Number(this.data.height)||0,this.isAttached=!1,this.hasSlide=!1,this.isDecoding=!1,this.state=_.IDLE,this.data.type?this.type=this.data.type:this.data.src?this.type="image":this.type="html",this.instance.dispatch("contentInit",{content:this})}removePlaceholder(){this.placeholder&&!this.keepPlaceholder()&&setTimeout(()=>{this.placeholder&&(this.placeholder.destroy(),this.placeholder=void 0)},1e3)}load(t,i){if(this.slide&&this.usePlaceholder())if(this.placeholder){const e=this.placeholder.element;e&&!e.parentElement&&this.slide.container.prepend(e)}else{const e=this.instance.applyFilters("placeholderSrc",this.data.msrc&&this.slide.isFirstSlide?this.data.msrc:!1,this);this.placeholder=new Rt(e,this.slide.container)}this.element&&!i||this.instance.dispatch("contentLoad",{content:this,isLazy:t}).defaultPrevented||(this.isImageContent()?(this.element=f("pswp__img","img"),this.displayedImageWidth&&this.loadImage(t)):(this.element=f("pswp__content","div"),this.element.innerHTML=this.data.html||""),i&&this.slide&&this.slide.updateContentSize(!0))}loadImage(t){var i,e;if(!this.isImageContent()||!this.element||this.instance.dispatch("contentLoadImage",{content:this,isLazy:t}).defaultPrevented)return;const s=this.element;this.updateSrcsetSizes(),this.data.srcset&&(s.srcset=this.data.srcset),s.src=(i=this.data.src)!==null&&i!==void 0?i:"",s.alt=(e=this.data.alt)!==null&&e!==void 0?e:"",this.state=_.LOADING,s.complete?this.onLoaded():(s.onload=()=>{this.onLoaded()},s.onerror=()=>{this.onError()})}setSlide(t){this.slide=t,this.hasSlide=!0,this.instance=t.pswp}onLoaded(){this.state=_.LOADED,this.slide&&this.element&&(this.instance.dispatch("loadComplete",{slide:this.slide,content:this}),this.slide.isActive&&this.slide.heavyAppended&&!this.element.parentNode&&(this.append(),this.slide.updateContentSize(!0)),(this.state===_.LOADED||this.state===_.ERROR)&&this.removePlaceholder())}onError(){this.state=_.ERROR,this.slide&&(this.displayError(),this.instance.dispatch("loadComplete",{slide:this.slide,isError:!0,content:this}),this.instance.dispatch("loadError",{slide:this.slide,content:this}))}isLoading(){return this.instance.applyFilters("isContentLoading",this.state===_.LOADING,this)}isError(){return this.state===_.ERROR}isImageContent(){return this.type==="image"}setDisplayedSize(t,i){if(this.element&&(this.placeholder&&this.placeholder.setDisplayedSize(t,i),!this.instance.dispatch("contentResize",{content:this,width:t,height:i}).defaultPrevented&&(L(this.element,t,i),this.isImageContent()&&!this.isError()))){const e=!this.displayedImageWidth&&t;this.displayedImageWidth=t,this.displayedImageHeight=i,e?this.loadImage(!1):this.updateSrcsetSizes(),this.slide&&this.instance.dispatch("imageSizeChange",{slide:this.slide,width:t,height:i,content:this})}}isZoomable(){return this.instance.applyFilters("isContentZoomable",this.isImageContent()&&this.state!==_.ERROR,this)}updateSrcsetSizes(){if(!this.isImageContent()||!this.element||!this.data.srcset)return;const t=this.element,i=this.instance.applyFilters("srcsetSizesWidth",this.displayedImageWidth,this);(!t.dataset.largestUsedSize||i>parseInt(t.dataset.largestUsedSize,10))&&(t.sizes=i+"px",t.dataset.largestUsedSize=String(i))}usePlaceholder(){return this.instance.applyFilters("useContentPlaceholder",this.isImageContent(),this)}lazyLoad(){this.instance.dispatch("contentLazyLoad",{content:this}).defaultPrevented||this.load(!0)}keepPlaceholder(){return this.instance.applyFilters("isKeepingPlaceholder",this.isLoading(),this)}destroy(){this.hasSlide=!1,this.slide=void 0,!this.instance.dispatch("contentDestroy",{content:this}).defaultPrevented&&(this.remove(),this.placeholder&&(this.placeholder.destroy(),this.placeholder=void 0),this.isImageContent()&&this.element&&(this.element.onload=null,this.element.onerror=null,this.element=void 0))}displayError(){if(this.slide){var t,i;let e=f("pswp__error-msg","div");e.innerText=(t=(i=this.instance.options)===null||i===void 0?void 0:i.errorMsg)!==null&&t!==void 0?t:"",e=this.instance.applyFilters("contentErrorElement",e,this),this.element=f("pswp__content pswp__error-msg-container","div"),this.element.appendChild(e),this.slide.container.innerText="",this.slide.container.appendChild(this.element),this.slide.updateContentSize(!0),this.removePlaceholder()}}append(){if(this.isAttached||!this.element)return;if(this.isAttached=!0,this.state===_.ERROR){this.displayError();return}if(this.instance.dispatch("contentAppend",{content:this}).defaultPrevented)return;const t="decode"in this.element;this.isImageContent()?t&&this.slide&&(!this.slide.isActive||C())?(this.isDecoding=!0,this.element.decode().catch(()=>{}).finally(()=>{this.isDecoding=!1,this.appendImage()})):this.appendImage():this.slide&&!this.element.parentNode&&this.slide.container.appendChild(this.element)}activate(){this.instance.dispatch("contentActivate",{content:this}).defaultPrevented||!this.slide||(this.isImageContent()&&this.isDecoding&&!C()?this.appendImage():this.isError()&&this.load(!1,!0),this.slide.holderElement&&this.slide.holderElement.setAttribute("aria-hidden","false"))}deactivate(){this.instance.dispatch("contentDeactivate",{content:this}),this.slide&&this.slide.holderElement&&this.slide.holderElement.setAttribute("aria-hidden","true")}remove(){this.isAttached=!1,!this.instance.dispatch("contentRemove",{content:this}).defaultPrevented&&(this.element&&this.element.parentNode&&this.element.remove(),this.placeholder&&this.placeholder.element&&this.placeholder.element.remove())}appendImage(){this.isAttached&&(this.instance.dispatch("contentAppendImage",{content:this}).defaultPrevented||(this.slide&&this.element&&!this.element.parentNode&&this.slide.container.appendChild(this.element),(this.state===_.LOADED||this.state===_.ERROR)&&this.removePlaceholder()))}}const Bt=5;function W(r,t,i){const e=t.createContentFromData(r,i);let s;const{options:n}=t;if(n){s=new k(n,r,-1);let o;t.pswp?o=t.pswp.viewportSize:o=B(n,t);const a=N(n,o,r,i);s.update(e.width,e.height,a)}return e.lazyLoad(),s&&e.setDisplayedSize(Math.ceil(e.width*s.initial),Math.ceil(e.height*s.initial)),e}function Nt(r,t){const i=t.getItemData(r);if(!t.dispatch("lazyLoadSlide",{index:r,itemData:i}).defaultPrevented)return W(i,t,r)}class kt{constructor(t){this.pswp=t,this.limit=Math.max(t.options.preload[0]+t.options.preload[1]+1,Bt),this._cachedItems=[]}updateLazy(t){const{pswp:i}=this;if(i.dispatch("lazyLoad").defaultPrevented)return;const{preload:e}=i.options,s=t===void 0?!0:t>=0;let n;for(n=0;n<=e[1];n++)this.loadSlideByIndex(i.currIndex+(s?n:-n));for(n=1;n<=e[0];n++)this.loadSlideByIndex(i.currIndex+(s?-n:n))}loadSlideByIndex(t){const i=this.pswp.getLoopedIndex(t);let e=this.getContentByIndex(i);e||(e=Nt(i,this.pswp),e&&this.addToCache(e))}getContentBySlide(t){let i=this.getContentByIndex(t.index);return i||(i=this.pswp.createContentFromData(t.data,t.index),this.addToCache(i)),i.setSlide(t),i}addToCache(t){if(this.removeByIndex(t.index),this._cachedItems.push(t),this._cachedItems.length>this.limit){const i=this._cachedItems.findIndex(e=>!e.isAttached&&!e.hasSlide);i!==-1&&this._cachedItems.splice(i,1)[0].destroy()}}removeByIndex(t){const i=this._cachedItems.findIndex(e=>e.index===t);i!==-1&&this._cachedItems.splice(i,1)}getContentByIndex(t){return this._cachedItems.find(i=>i.index===t)}destroy(){this._cachedItems.forEach(t=>t.destroy()),this._cachedItems=[]}}class Ht extends Mt{getNumItems(){var t;let i=0;const e=(t=this.options)===null||t===void 0?void 0:t.dataSource;e&&"length"in e?i=e.length:e&&"gallery"in e&&(e.items||(e.items=this._getGalleryDOMElements(e.gallery)),e.items&&(i=e.items.length));const s=this.dispatch("numItems",{dataSource:e,numItems:i});return this.applyFilters("numItems",s.numItems,e)}createContentFromData(t,i){return new Ft(t,this,i)}getItemData(t){var i;const e=(i=this.options)===null||i===void 0?void 0:i.dataSource;let s={};Array.isArray(e)?s=e[t]:e&&"gallery"in e&&(e.items||(e.items=this._getGalleryDOMElements(e.gallery)),s=e.items[t]);let n=s;n instanceof Element&&(n=this._domElementToItemData(n));const o=this.dispatch("itemData",{itemData:n||{},index:t});return this.applyFilters("itemData",o.itemData,t)}_getGalleryDOMElements(t){var i,e;return(i=this.options)!==null&&i!==void 0&&i.children||(e=this.options)!==null&&e!==void 0&&e.childSelector?K(this.options.children,this.options.childSelector,t)||[]:[t]}_domElementToItemData(t){const i={element:t},e=t.tagName==="A"?t:t.querySelector("a");if(e){i.src=e.dataset.pswpSrc||e.href,e.dataset.pswpSrcset&&(i.srcset=e.dataset.pswpSrcset),i.width=e.dataset.pswpWidth?parseInt(e.dataset.pswpWidth,10):0,i.height=e.dataset.pswpHeight?parseInt(e.dataset.pswpHeight,10):0,i.w=i.width,i.h=i.height,e.dataset.pswpType&&(i.type=e.dataset.pswpType);const n=t.querySelector("img");if(n){var s;i.msrc=n.currentSrc||n.src,i.alt=(s=n.getAttribute("alt"))!==null&&s!==void 0?s:""}(e.dataset.pswpCropped||e.dataset.cropped)&&(i.thumbCropped=!0)}return this.applyFilters("domItemData",i,t,e)}lazyLoadData(t,i){return W(t,this,i)}}const P=.003;class Wt{constructor(t){this.pswp=t,this.isClosed=!0,this.isOpen=!1,this.isClosing=!1,this.isOpening=!1,this._duration=void 0,this._useAnimation=!1,this._croppedZoom=!1,this._animateRootOpacity=!1,this._animateBgOpacity=!1,this._placeholder=void 0,this._opacityElement=void 0,this._cropContainer1=void 0,this._cropContainer2=void 0,this._thumbBounds=void 0,this._prepareOpen=this._prepareOpen.bind(this),t.on("firstZoomPan",this._prepareOpen)}open(){this._prepareOpen(),this._start()}close(){if(this.isClosed||this.isClosing||this.isOpening)return;const t=this.pswp.currSlide;this.isOpen=!1,this.isOpening=!1,this.isClosing=!0,this._duration=this.pswp.options.hideAnimationDuration,t&&t.currZoomLevel*t.width>=this.pswp.options.maxWidthToAnimate&&(this._duration=0),this._applyStartProps(),setTimeout(()=>{this._start()},this._croppedZoom?30:0)}_prepareOpen(){if(this.pswp.off("firstZoomPan",this._prepareOpen),!this.isOpening){const t=this.pswp.currSlide;this.isOpening=!0,this.isClosing=!1,this._duration=this.pswp.options.showAnimationDuration,t&&t.zoomLevels.initial*t.width>=this.pswp.options.maxWidthToAnimate&&(this._duration=0),this._applyStartProps()}}_applyStartProps(){const{pswp:t}=this,i=this.pswp.currSlide,{options:e}=t;if(e.showHideAnimationType==="fade"?(e.showHideOpacity=!0,this._thumbBounds=void 0):e.showHideAnimationType==="none"?(e.showHideOpacity=!1,this._duration=0,this._thumbBounds=void 0):this.isOpening&&t._initialThumbBounds?this._thumbBounds=t._initialThumbBounds:this._thumbBounds=this.pswp.getThumbBounds(),this._placeholder=i==null?void 0:i.getPlaceholderElement(),t.animations.stopAll(),this._useAnimation=!!(this._duration&&this._duration>50),this._animateZoom=!!this._thumbBounds&&(i==null?void 0:i.content.usePlaceholder())&&(!this.isClosing||!t.mainScroll.isShifted()),!this._animateZoom)this._animateRootOpacity=!0,this.isOpening&&i&&(i.zoomAndPanToInitial(),i.applyCurrentZoomPan());else{var s;this._animateRootOpacity=(s=e.showHideOpacity)!==null&&s!==void 0?s:!1}if(this._animateBgOpacity=!this._animateRootOpacity&&this.pswp.options.bgOpacity>P,this._opacityElement=this._animateRootOpacity?t.element:t.bg,!this._useAnimation){this._duration=0,this._animateZoom=!1,this._animateBgOpacity=!1,this._animateRootOpacity=!0,this.isOpening&&(t.element&&(t.element.style.opacity=String(P)),t.applyBgOpacity(1));return}if(this._animateZoom&&this._thumbBounds&&this._thumbBounds.innerRect){var n;this._croppedZoom=!0,this._cropContainer1=this.pswp.container,this._cropContainer2=(n=this.pswp.currSlide)===null||n===void 0?void 0:n.holderElement,t.container&&(t.container.style.overflow="hidden",t.container.style.width=t.viewportSize.x+"px")}else this._croppedZoom=!1;this.isOpening?(this._animateRootOpacity?(t.element&&(t.element.style.opacity=String(P)),t.applyBgOpacity(1)):(this._animateBgOpacity&&t.bg&&(t.bg.style.opacity=String(P)),t.element&&(t.element.style.opacity="1")),this._animateZoom&&(this._setClosedStateZoomPan(),this._placeholder&&(this._placeholder.style.willChange="transform",this._placeholder.style.opacity=String(P)))):this.isClosing&&(t.mainScroll.itemHolders[0]&&(t.mainScroll.itemHolders[0].el.style.display="none"),t.mainScroll.itemHolders[2]&&(t.mainScroll.itemHolders[2].el.style.display="none"),this._croppedZoom&&t.mainScroll.x!==0&&(t.mainScroll.resetPosition(),t.mainScroll.resize()))}_start(){this.isOpening&&this._useAnimation&&this._placeholder&&this._placeholder.tagName==="IMG"?new Promise(t=>{let i=!1,e=!0;q(this._placeholder).finally(()=>{i=!0,e||t(!0)}),setTimeout(()=>{e=!1,i&&t(!0)},50),setTimeout(t,250)}).finally(()=>this._initiate()):this._initiate()}_initiate(){var t,i;(t=this.pswp.element)===null||t===void 0||t.style.setProperty("--pswp-transition-duration",this._duration+"ms"),this.pswp.dispatch(this.isOpening?"openingAnimationStart":"closingAnimationStart"),this.pswp.dispatch("initialZoom"+(this.isOpening?"In":"Out")),(i=this.pswp.element)===null||i===void 0||i.classList.toggle("pswp--ui-visible",this.isOpening),this.isOpening?(this._placeholder&&(this._placeholder.style.opacity="1"),this._animateToOpenState()):this.isClosing&&this._animateToClosedState(),this._useAnimation||this._onAnimationComplete()}_onAnimationComplete(){const{pswp:t}=this;if(this.isOpen=this.isOpening,this.isClosed=this.isClosing,this.isOpening=!1,this.isClosing=!1,t.dispatch(this.isOpen?"openingAnimationEnd":"closingAnimationEnd"),t.dispatch("initialZoom"+(this.isOpen?"InEnd":"OutEnd")),this.isClosed)t.destroy();else if(this.isOpen){var i;this._animateZoom&&t.container&&(t.container.style.overflow="visible",t.container.style.width="100%"),(i=t.currSlide)===null||i===void 0||i.applyCurrentZoomPan()}}_animateToOpenState(){const{pswp:t}=this;this._animateZoom&&(this._croppedZoom&&this._cropContainer1&&this._cropContainer2&&(this._animateTo(this._cropContainer1,"transform","translate3d(0,0,0)"),this._animateTo(this._cropContainer2,"transform","none")),t.currSlide&&(t.currSlide.zoomAndPanToInitial(),this._animateTo(t.currSlide.container,"transform",t.currSlide.getCurrentTransform()))),this._animateBgOpacity&&t.bg&&this._animateTo(t.bg,"opacity",String(t.options.bgOpacity)),this._animateRootOpacity&&t.element&&this._animateTo(t.element,"opacity","1")}_animateToClosedState(){const{pswp:t}=this;this._animateZoom&&this._setClosedStateZoomPan(!0),this._animateBgOpacity&&t.bgOpacity>.01&&t.bg&&this._animateTo(t.bg,"opacity","0"),this._animateRootOpacity&&t.element&&this._animateTo(t.element,"opacity","0")}_setClosedStateZoomPan(t){if(!this._thumbBounds)return;const{pswp:i}=this,{innerRect:e}=this._thumbBounds,{currSlide:s,viewportSize:n}=i;if(this._croppedZoom&&e&&this._cropContainer1&&this._cropContainer2){const o=-n.x+(this._thumbBounds.x-e.x)+e.w,a=-n.y+(this._thumbBounds.y-e.y)+e.h,h=n.x-e.w,l=n.y-e.h;t?(this._animateTo(this._cropContainer1,"transform",b(o,a)),this._animateTo(this._cropContainer2,"transform",b(h,l))):(y(this._cropContainer1,o,a),y(this._cropContainer2,h,l))}s&&(p(s.pan,e||this._thumbBounds),s.currZoomLevel=this._thumbBounds.w/s.width,t?this._animateTo(s.container,"transform",s.getCurrentTransform()):s.applyCurrentZoomPan())}_animateTo(t,i,e){if(!this._duration){t.style[i]=e;return}const{animations:s}=this.pswp,n={duration:this._duration,easing:this.pswp.options.easing,onComplete:()=>{s.activeAnimations.length||this._onAnimationComplete()},target:t};n[i]=e,s.startTransition(n)}}const Vt={allowPanToNext:!0,spacing:.1,loop:!0,pinchToClose:!0,closeOnVerticalDrag:!0,hideAnimationDuration:333,showAnimationDuration:333,zoomAnimationDuration:333,escKey:!0,arrowKeys:!0,trapFocus:!0,returnFocus:!0,maxWidthToAnimate:4e3,clickToCloseNonZoomable:!0,imageClickAction:"zoom-or-close",bgClickAction:"close",tapAction:"toggle-controls",doubleTapAction:"zoom",indexIndicatorSep:" / ",preloaderDelay:2e3,bgOpacity:.8,index:0,errorMsg:"The image cannot be loaded",preload:[1,2],easing:"cubic-bezier(.4,0,.22,1)"};class $t extends Ht{constructor(t){super(),this.options=this._prepareOptions(t||{}),this.offset={x:0,y:0},this._prevViewportSize={x:0,y:0},this.viewportSize={x:0,y:0},this.bgOpacity=1,this.currIndex=0,this.potentialIndex=0,this.isOpen=!1,this.isDestroying=!1,this.hasMouse=!1,this._initialItemData={},this._initialThumbBounds=void 0,this.topBar=void 0,this.element=void 0,this.template=void 0,this.container=void 0,this.scrollWrap=void 0,this.currSlide=void 0,this.events=new X,this.animations=new wt,this.mainScroll=new dt(this),this.gestures=new lt(this),this.opener=new Wt(this),this.keyboard=new ut(this),this.contentLoader=new kt(this)}init(){if(this.isOpen||this.isDestroying)return!1;this.isOpen=!0,this.dispatch("init"),this.dispatch("beforeOpen"),this._createMainStructure();let t="pswp--open";return this.gestures.supportsTouch&&(t+=" pswp--touch"),this.options.mainClass&&(t+=" "+this.options.mainClass),this.element&&(this.element.className+=" "+t),this.currIndex=this.options.index||0,this.potentialIndex=this.currIndex,this.dispatch("firstUpdate"),this.scrollWheel=new Pt(this),(Number.isNaN(this.currIndex)||this.currIndex<0||this.currIndex>=this.getNumItems())&&(this.currIndex=0),this.gestures.supportsTouch||this.mouseDetected(),this.updateSize(),this.offset.y=window.pageYOffset,this._initialItemData=this.getItemData(this.currIndex),this.dispatch("gettingData",{index:this.currIndex,data:this._initialItemData,slide:void 0}),this._initialThumbBounds=this.getThumbBounds(),this.dispatch("initialLayout"),this.on("openingAnimationEnd",()=>{const{itemHolders:i}=this.mainScroll;i[0]&&(i[0].el.style.display="block",this.setContent(i[0],this.currIndex-1)),i[2]&&(i[2].el.style.display="block",this.setContent(i[2],this.currIndex+1)),this.appendHeavy(),this.contentLoader.updateLazy(),this.events.add(window,"resize",this._handlePageResize.bind(this)),this.events.add(window,"scroll",this._updatePageScrollOffset.bind(this)),this.dispatch("bindEvents")}),this.mainScroll.itemHolders[1]&&this.setContent(this.mainScroll.itemHolders[1],this.currIndex),this.dispatch("change"),this.opener.open(),this.dispatch("afterInit"),!0}getLoopedIndex(t){const i=this.getNumItems();return this.options.loop&&(t>i-1&&(t-=i),t<0&&(t+=i)),I(t,0,i-1)}appendHeavy(){this.mainScroll.itemHolders.forEach(t=>{var i;(i=t.slide)===null||i===void 0||i.appendHeavy()})}goTo(t){this.mainScroll.moveIndexBy(this.getLoopedIndex(t)-this.potentialIndex)}next(){this.goTo(this.potentialIndex+1)}prev(){this.goTo(this.potentialIndex-1)}zoomTo(...t){var i;(i=this.currSlide)===null||i===void 0||i.zoomTo(...t)}toggleZoom(){var t;(t=this.currSlide)===null||t===void 0||t.toggleZoom()}close(){!this.opener.isOpen||this.isDestroying||(this.isDestroying=!0,this.dispatch("close"),this.events.removeAll(),this.opener.close())}destroy(){var t;if(!this.isDestroying){this.options.showHideAnimationType="none",this.close();return}this.dispatch("destroy"),this._listeners={},this.scrollWrap&&(this.scrollWrap.ontouchmove=null,this.scrollWrap.ontouchend=null),(t=this.element)===null||t===void 0||t.remove(),this.mainScroll.itemHolders.forEach(i=>{var e;(e=i.slide)===null||e===void 0||e.destroy()}),this.contentLoader.destroy(),this.events.removeAll()}refreshSlideContent(t){this.contentLoader.removeByIndex(t),this.mainScroll.itemHolders.forEach((i,e)=>{var s,n;let o=((s=(n=this.currSlide)===null||n===void 0?void 0:n.index)!==null&&s!==void 0?s:0)-1+e;if(this.canLoop()&&(o=this.getLoopedIndex(o)),o===t&&(this.setContent(i,t,!0),e===1)){var a;this.currSlide=i.slide,(a=i.slide)===null||a===void 0||a.setIsActive(!0)}}),this.dispatch("change")}setContent(t,i,e){if(this.canLoop()&&(i=this.getLoopedIndex(i)),t.slide){if(t.slide.index===i&&!e)return;t.slide.destroy(),t.slide=void 0}if(!this.canLoop()&&(i<0||i>=this.getNumItems()))return;const s=this.getItemData(i);t.slide=new j(s,i,this),i===this.currIndex&&(this.currSlide=t.slide),t.slide.append(t.el)}getViewportCenterPoint(){return{x:this.viewportSize.x/2,y:this.viewportSize.y/2}}updateSize(t){if(this.isDestroying)return;const i=B(this.options,this);!t&&x(i,this._prevViewportSize)||(p(this._prevViewportSize,i),this.dispatch("beforeResize"),p(this.viewportSize,this._prevViewportSize),this._updatePageScrollOffset(),this.dispatch("viewportSize"),this.mainScroll.resize(this.opener.isOpen),!this.hasMouse&&window.matchMedia("(any-hover: hover)").matches&&this.mouseDetected(),this.dispatch("resize"))}applyBgOpacity(t){this.bgOpacity=Math.max(t,0),this.bg&&(this.bg.style.opacity=String(this.bgOpacity*this.options.bgOpacity))}mouseDetected(){if(!this.hasMouse){var t;this.hasMouse=!0,(t=this.element)===null||t===void 0||t.classList.add("pswp--has_mouse")}}_handlePageResize(){this.updateSize(),/iPhone|iPad|iPod/i.test(window.navigator.userAgent)&&setTimeout(()=>{this.updateSize()},500)}_updatePageScrollOffset(){this.setScrollOffset(0,window.pageYOffset)}setScrollOffset(t,i){this.offset.x=t,this.offset.y=i,this.dispatch("updateScrollOffset")}_createMainStructure(){this.element=f("pswp","div"),this.element.setAttribute("tabindex","-1"),this.element.setAttribute("role","dialog"),this.template=this.element,this.bg=f("pswp__bg","div",this.element),this.scrollWrap=f("pswp__scroll-wrap","section",this.element),this.container=f("pswp__container","div",this.scrollWrap),this.scrollWrap.setAttribute("aria-roledescription","carousel"),this.container.setAttribute("aria-live","off"),this.container.setAttribute("id","pswp__items"),this.mainScroll.appendHolders(),this.ui=new zt(this),this.ui.init(),(this.options.appendToEl||document.body).appendChild(this.element)}getThumbBounds(){return Zt(this.currIndex,this.currSlide?this.currSlide.data:this._initialItemData,this)}canLoop(){return this.options.loop&&this.getNumItems()>2}_prepareOptions(t){return window.matchMedia("(prefers-reduced-motion), (update: slow)").matches&&(t.showHideAnimationType="none",t.zoomAnimationDuration=0),{...Vt,...t}}}export{$t as default}; diff --git a/assets/plugin-vue_export-helper-DlAUqK2U.js b/assets/plugin-vue_export-helper-DlAUqK2U.js new file mode 100644 index 00000000..718edd33 --- /dev/null +++ b/assets/plugin-vue_export-helper-DlAUqK2U.js @@ -0,0 +1 @@ +const s=(t,r)=>{const o=t.__vccOpts||t;for(const[c,e]of r)o[c]=e;return o};export{s as _}; diff --git a/assets/style-DsUWsep8.css b/assets/style-DsUWsep8.css new file mode 100644 index 00000000..e7acd6c0 --- /dev/null +++ b/assets/style-DsUWsep8.css @@ -0,0 +1 @@ +@charset "UTF-8";html[data-theme=dark]{--text-color: #9e9e9e;--bg-color: #0d1117;--bg-color-secondary: #161b22;--bg-color-tertiary: #21262c;--border-color: #30363d;--box-shadow: #282a32;--card-shadow: rgba(0, 0, 0, .3);--black: #fff;--grey-dark: #999;--grey-light: #666;--white: #000;--grey-darker: #bbb;--grey-lighter: #333;--grey14: #111;--bg-color-light: #161b22;--bg-color-back: #0d1117;--bg-color-float: #161b22;--bg-color-blur: rgba(13, 17, 23, .9);--bg-color-float-blur: rgba(22, 27, 34, .9);--text-color-light: #a8a8a8;--text-color-lighter: #b1b1b1;--text-color-bright: #c5c5c5;--border-color-light: #2e333a;--border-color-dark: #394048}:root{--theme-color: #3eaf7c;--text-color: #2c3e50;--bg-color: #fff;--bg-color-secondary: #f8f8f8;--bg-color-tertiary: #efeef4;--border-color: #eaecef;--box-shadow: #f0f1f2;--card-shadow: rgba(0, 0, 0, .15);--black: #000;--grey-dark: #666;--grey-light: #999;--white: #fff;--grey-darker: #333;--grey-lighter: #bbb;--grey14: #eee;--navbar-height: 3.75rem;--navbar-horizontal-padding: 1.5rem;--navbar-vertical-padding: .7rem;--navbar-mobile-height: 3.25rem;--navbar-mobile-horizontal-padding: 1rem;--navbar-mobile-vertical-padding: .5rem;--sidebar-width: 18rem;--sidebar-mobile-width: 16rem;--content-width: 780px;--home-page-width: 1160px;--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", STHeiti, "Microsoft YaHei", SimSun, sans-serif;--font-family-heading: Georgia Pro, Crimson, Georgia, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", STHeiti, "Microsoft YaHei", SimSun, sans-serif;--font-family-mono: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;--line-numbers-width: 2.5rem;--color-transition: .3s ease;--transform-transition: .3s ease;--vp-bg: var(--bg-color);--vp-bgl: var(--bg-color-light);--vp-bglt: var(--bg-color-tertiary);--vp-c: var(--text-color);--vp-cl: var(--text-color-light);--vp-clt: var(--text-color-lighter);--vp-brc: var(--border-color);--vp-brcd: var(--border-color-dark);--vp-tc: var(--theme-color);--vp-tcl: var(--theme-color-light);--vp-ct: var(--color-transition);--vp-tt: var(--transform-transition);--bg-color-light: #fff;--bg-color-back: #f8f8f8;--bg-color-float: #fff;--bg-color-blur: rgba(255, 255, 255, .9);--bg-color-float-blur: rgba(255, 255, 255, .9);--text-color-light: #3a5169;--text-color-lighter: #476582;--text-color-bright: #6a8bad;--border-color-light: #eceef1;--border-color-dark: #cfd4db;--theme-color-dark: #389e70;--theme-color-light: #4abf8a;--theme-color-mask: rgba(62, 175, 124, .15)}:root{--badge-tip-color: #42b983;--badge-warning-color: #f4cd00;--badge-danger-color: #f55;--badge-info-color: #0295ff;--badge-note-color: #666}.vp-badge{display:inline-block;vertical-align:center;height:18px;padding:0 6px;border-radius:3px;background:var(--vp-tc);color:var(--white);font-size:14px;line-height:18px;transition:background var(--vp-ct),color var(--vp-ct)}.vp-badge+.vp-badge{margin-inline-start:5px}h1 .vp-badge,h2 .vp-badge,h3 .vp-badge,h4 .vp-badge,h5 .vp-badge,h6 .vp-badge{vertical-align:top}.vp-badge.tip{background:var(--badge-tip-color)}.vp-badge.warning{background:var(--badge-warning-color)}.vp-badge.danger{background:var(--badge-danger-color)}.vp-badge.info{background:var(--badge-info-color)}.vp-badge.note{background:var(--badge-note-color)}.font-icon{display:inline-block}.theme-hope-content .font-icon{vertical-align:middle}@media screen{.sr-only{position:absolute;overflow:hidden;clip:rect 0,0,0,0;width:1px;height:1px;margin:-1px;padding:0;border:0}}@media print{.sr-only{display:none}}.vp-catalog-wrapper{margin-top:8px;margin-bottom:8px}.vp-catalog-wrapper.index ol{padding-inline-start:0}.vp-catalog-wrapper.index li{list-style-type:none}.vp-catalog-wrapper.index .vp-catalogs{padding-inline-start:0}.vp-catalog-wrapper.index .vp-catalog{list-style-type:none}.vp-catalog-wrapper.index .vp-catalog-title:before{content:"§" counter(catalog-item,upper-roman) " "}.vp-catalog-wrapper.index .vp-child-catalogs{counter-reset:child-catalog}.vp-catalog-wrapper.index .vp-child-catalog{counter-increment:child-catalog}.vp-catalog-wrapper.index .vp-child-catalog .vp-catalog-title:before{content:counter(catalog-item) "." counter(child-catalog) " "}.vp-catalog-wrapper.index .vp-sub-catalogs{padding-inline-start:.5rem}.vp-catalogs{margin:0;counter-reset:catalog-item}.vp-catalogs.deep{padding-inline-start:0}.vp-catalogs.deep .vp-catalog{list-style-type:none}.vp-catalogs .font-icon{vertical-align:baseline;margin-inline-end:.25rem}.vp-catalog{counter-increment:catalog-item}.vp-catalog-main-title{margin-top:calc(.5rem - var(--navbar-height, 3.6rem));margin-bottom:.5rem;padding-top:var(--navbar-height, 3.6rem);font-weight:500;font-size:1.75rem}.vp-catalog-main-title:first-child{margin-bottom:.5rem!important}.vp-catalog-main-title:only-child{margin-bottom:0!important}.vp-catalog-main-title .vp-link{text-decoration:none!important}.vp-catalog-child-title{margin-bottom:.5rem!important}.vp-catalog-child-title.has-children{margin-top:calc(.5rem - var(--navbar-height, 3.6rem));padding-top:var(--navbar-height, 3.6rem);border-bottom:1px solid var(--catalog-border-color);font-weight:500;font-size:1.3rem;transition:border-color .3s}.vp-catalog-child-title.has-children:only-child{margin-bottom:0!important}.vp-catalog-child-title .vp-link{text-decoration:none!important}.vp-catalog-sub-title{font-weight:500;font-size:1.1rem}.vp-catalog-sub-title:only-child{margin-bottom:0!important}.vp-catalog-title{color:inherit;text-decoration:none}.vp-catalog-title:hover{color:var(--catalog-active-color)}.vp-child-catalogs{margin:0}.vp-child-catalog{list-style-type:disc}.vp-sub-catalogs{counter-reset:sub-catalog}.vp-sub-catalog{counter-increment:sub-catalog}.vp-sub-catalog .vp-link:before{content:counter(catalog-item) "." counter(child-catalog) "." counter(sub-catalog) " "}.vp-sub-catalogs-wrapper{display:flex;flex-wrap:wrap}.vp-sub-catalog-link{display:inline-block;margin:4px 8px;padding:4px 8px;border-radius:6px;background-color:var(--catalog-bg-secondary-color);line-height:1.5;overflow-wrap:break-word;transition:background-color .3s,color .3s}.vp-sub-catalog-link:hover{background-color:var(--catalog-hover-color);color:var(--catalog-bg-color);text-decoration:none!important}.vp-catalog-header-anchor{font-size:.85em;float:left;margin-left:-1em;padding-right:0;margin-top:.125em;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-decoration:none;content:"¶"}@media print{.vp-catalog-header-anchor{display:none}}h2:hover .vp-catalog-header-anchor,h3:hover .vp-catalog-header-anchor{opacity:1;text-decoration:none}.vp-catalog-header-anchor:focus-visible{opacity:1}.vp-empty-catalog{font-size:1.25rem;text-align:center}:root{--catalog-bg-color: #fff;--catalog-bg-secondary-color: #f8f8f8;--catalog-border-color: #e5e5e5;--catalog-active-color: #3eaf7c;--catalog-hover-color: #71cda3}.vp-back-to-top-button{position:fixed!important;bottom:4rem;inset-inline-end:1rem;z-index:100;width:48px;height:48px;padding:8px;border-width:0;border-radius:50%;background:var(--back-to-top-bg-color);color:var(--back-to-top-color);box-shadow:2px 2px 10px 4px var(--back-to-top-shadow);cursor:pointer}@media (max-width: 959px){.vp-back-to-top-button{transform:scale(.8);transform-origin:100% 100%}}@media print{.vp-back-to-top-button{display:none}}.vp-back-to-top-button:hover{color:var(--back-to-top-color-hover)}.vp-back-to-top-button .back-to-top-icon{overflow:hidden;width:100%;height:100%;background:currentcolor;border-radius:50%;-webkit-mask-image:var(--back-to-top-icon);mask-image:var(--back-to-top-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:cover;mask-size:cover}.vp-scroll-progress{position:absolute;right:-2px;bottom:-2px;width:52px;height:52px}.vp-scroll-progress svg{width:100%;height:100%}.vp-scroll-progress circle{opacity:.9;transform:rotate(-90deg);transform-origin:50% 50%}.back-to-top-enter-active,.back-to-top-leave-active{transition:opacity .3s}.back-to-top-enter-from,.back-to-top-leave-to{opacity:0}:root{--back-to-top-z-index: 5;--back-to-top-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%201024'%3e%3cpath%20d='M512%20843.2c-36.2%200-66.4-13.6-85.8-21.8-10.8-4.6-22.6%203.6-21.8%2015.2l7%20102c.4%206.2%207.6%209.4%2012.6%205.6l29-22c3.6-2.8%209-1.8%2011.4%202l41%2064.2c3%204.8%2010.2%204.8%2013.2%200l41-64.2c2.4-3.8%207.8-4.8%2011.4-2l29%2022c5%203.8%2012.2.6%2012.6-5.6l7-102c.8-11.6-11-20-21.8-15.2-19.6%208.2-49.6%2021.8-85.8%2021.8'/%3e%3cpath%20d='m795.4%20586.2-96-98.2C699.4%20172%20513%2032%20513%2032S324.8%20172%20324.8%20488l-96%2098.2c-3.6%203.6-5.2%209-4.4%2014.2L261.2%20824c1.8%2011.4%2014.2%2017%2023.6%2010.8L419%20744s41.4%2040%2094.2%2040%2092.2-40%2092.2-40l134.2%2090.8c9.2%206.2%2021.6.6%2023.6-10.8l37-223.8c.4-5.2-1.2-10.4-4.8-14M513%20384c-34%200-61.4-28.6-61.4-64s27.6-64%2061.4-64c34%200%2061.4%2028.6%2061.4%2064S547%20384%20513%20384'/%3e%3c/svg%3e");--back-to-top-bg-color: #fff;--back-to-top-color: #3eaf7c;--back-to-top-color-hover: #71cda3;--back-to-top-shadow: rgb(0 0 0 / 20%)}:root{--external-link-icon-color: #aaa}.external-link-icon{position:relative;display:inline-block;color:var(--external-link-icon-color);vertical-align:middle;top:-1px}@media print{.external-link-icon{display:none}}.external-link-icon-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}:root{--nprogress-color: #29d;--nprogress-z-index: 1031}#nprogress{pointer-events:none}#nprogress .bar{background:var(--nprogress-color);position:fixed;z-index:var(--nprogress-z-index);top:0;left:0;width:100%;height:2px}div[class*=language-]:hover:before{display:none}div[class*=language-]:hover .vp-copy-code-button{opacity:1}.vp-copy-code-button{position:absolute;top:.5em;right:.5em;z-index:5;width:2.5rem;height:2.5rem;padding:0;border-width:0;border-radius:.5rem;background:transparent;outline:none;opacity:0;cursor:pointer;transition:opacity .4s}@media print{.vp-copy-code-button{display:none}}.vp-copy-code-button:focus,.vp-copy-code-button.copied{opacity:1}.vp-copy-code-button:hover,.vp-copy-code-button.copied{background:var(--copy-code-hover)}.vp-copy-code-button.copied .vp-copy-icon{-webkit-mask-image:var(--code-copied-icon);mask-image:var(--code-copied-icon)}.vp-copy-code-button.copied:after{content:attr(data-copied);position:absolute;top:0;right:calc(100% + .25rem);display:block;height:1.25rem;padding:.625rem;border-radius:.5rem;background:var(--copy-code-hover);color:var(--copy-code-color);font-weight:500;line-height:1.25rem;white-space:nowrap}.vp-copy-icon{width:1.25rem;height:1.25rem;padding:.625rem;background:currentcolor;color:var(--copy-code-color);font-size:1.25rem;-webkit-mask-image:var(--code-copy-icon);mask-image:var(--code-copy-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:1em;mask-size:1em}:root{--code-copy-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20height='20'%20width='20'%20stroke='rgba(128,128,128,1)'%20stroke-width='2'%3e%3cpath%20stroke-linecap='round'%20stroke-linejoin='round'%20d='M9%205H7a2%202%200%200%200-2%202v12a2%202%200%200%200%202%202h10a2%202%200%200%200%202-2V7a2%202%200%200%200-2-2h-2M9%205a2%202%200%200%200%202%202h2a2%202%200%200%200%202-2M9%205a2%202%200%200%201%202-2h2a2%202%200%200%201%202%202'%20/%3e%3c/svg%3e");--code-copied-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20height='20'%20width='20'%20stroke='rgba(128,128,128,1)'%20stroke-width='2'%3e%3cpath%20stroke-linecap='round'%20stroke-linejoin='round'%20d='M9%205H7a2%202%200%200%200-2%202v12a2%202%200%200%200%202%202h10a2%202%200%200%200%202-2V7a2%202%200%200%200-2-2h-2M9%205a2%202%200%200%200%202%202h2a2%202%200%200%200%202-2M9%205a2%202%200%200%201%202-2h2a2%202%200%200%201%202%202m-6%209%202%202%204-4'%20/%3e%3c/svg%3e");--copy-code-color: #9e9e9e;--copy-code-hover: rgb(0 0 0 / 50%)}.footnote-item{margin-top:calc(0rem - var(--navbar-height, 3.6rem));padding-top:calc(var(--navbar-height, 3.6rem) + .5rem)}.footnote-item>p{margin-bottom:0}.footnote-ref{position:relative}.footnote-anchor{position:absolute;top:calc(-.5rem - var(--navbar-height, 3.6rem))}:root{--important-title-color: #230555;--important-bg-color: #f4eefe;--important-border-color: #a371f7;--important-code-bg-color: rgb(163 113 247 / 10%);--info-title-color: #193c47;--info-bg-color: #eef9fd;--info-border-color: #4cb3d4;--info-code-bg-color: rgb(76 179 212 / 10%);--note-title-color: #474748;--note-bg-color: #fdfdfe;--note-border-color: #ccc;--note-code-bg-color: rgb(212 213 216 / 20%);--tip-title-color: #003100;--tip-bg-color: #e6f6e6;--tip-border-color: #009400;--tip-code-bg-color: rgb(0 148 0 / 15%);--warning-title-color: #4d3800;--warning-bg-color: #fff8e6;--warning-border-color: #e6a700;--warning-code-bg-color: rgb(230 167 0 / 15%);--caution-title-color: #4b1113;--caution-bg-color: #ffebec;--caution-border-color: #e13238;--caution-code-bg-color: rgb(225 50 56 / 15%);--detail-bg-color: #eee;--detail-text-color: inherit;--detail-code-bg-color: rgb(127 127 127 / 15%)}html[data-theme=dark]{--important-title-color: #f4eefe;--important-bg-color: #230555;--info-title-color: #eef9fd;--info-bg-color: #193c47;--note-title-color: #fdfdfe;--note-bg-color: #474748;--tip-title-color: #e6f6e6;--tip-bg-color: #003100;--warning-title-color: #fff8e6;--warning-bg-color: #4d3800;--caution-title-color: #ffebec;--caution-bg-color: #4b1113;--detail-bg-color: #333;--detail-text-color: #a8a8a8}.hint-container{position:relative;transition:background var(--vp-ct),border-color var(--vp-ct),color var(--vp-ct)}@media print{.hint-container{page-break-inside:avoid}}.hint-container .hint-container-title{position:relative;font-weight:600;line-height:1.25}.hint-container.important,.hint-container.info,.hint-container.note,.hint-container.tip,.hint-container.warning,.hint-container.caution{margin:1rem 0;padding:.25rem 1rem;border-inline-start-width:.3rem;border-inline-start-style:solid;border-radius:.5rem;color:inherit}@media (max-width: 419px){.hint-container.important,.hint-container.info,.hint-container.note,.hint-container.tip,.hint-container.warning,.hint-container.caution{margin-inline:-.75rem}}.hint-container.important .hint-container-title,.hint-container.info .hint-container-title,.hint-container.note .hint-container-title,.hint-container.tip .hint-container-title,.hint-container.warning .hint-container-title,.hint-container.caution .hint-container-title{padding-inline-start:1.75rem}@media print{.hint-container.important .hint-container-title,.hint-container.info .hint-container-title,.hint-container.note .hint-container-title,.hint-container.tip .hint-container-title,.hint-container.warning .hint-container-title,.hint-container.caution .hint-container-title{padding-inline-start:0}}.hint-container.important .hint-container-title:before,.hint-container.info .hint-container-title:before,.hint-container.note .hint-container-title:before,.hint-container.tip .hint-container-title:before,.hint-container.warning .hint-container-title:before,.hint-container.caution .hint-container-title:before{content:" ";position:absolute;top:calc(50% - .6125em);inset-inline-start:0;width:1.25em;height:1.25em;background-position:left;background-repeat:no-repeat}@media print{.hint-container.important .hint-container-title:before,.hint-container.info .hint-container-title:before,.hint-container.note .hint-container-title:before,.hint-container.tip .hint-container-title:before,.hint-container.warning .hint-container-title:before,.hint-container.caution .hint-container-title:before{display:none}}.hint-container.important p,.hint-container.info p,.hint-container.note p,.hint-container.tip p,.hint-container.warning p,.hint-container.caution p{line-height:1.5}.hint-container.important a,.hint-container.info a,.hint-container.note a,.hint-container.tip a,.hint-container.warning a,.hint-container.caution a{color:var(--vp-tc)}.hint-container.important{border-color:var(--important-border-color);background:var(--important-bg-color)}.hint-container.important>.hint-container-title{color:var(--important-title-color)}.hint-container.important>.hint-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M512 981.333a84.992 84.992 0 0 1-84.907-84.906h169.814A84.992 84.992 0 0 1 512 981.333zm384-128H128v-42.666l85.333-85.334v-256A298.325 298.325 0 0 1 448 177.92V128a64 64 0 0 1 128 0v49.92a298.325 298.325 0 0 1 234.667 291.413v256L896 810.667v42.666zm-426.667-256v85.334h85.334v-85.334h-85.334zm0-256V512h85.334V341.333h-85.334z' fill='%23a371f7'/%3E%3C/svg%3E")}.hint-container.important code{background:var(--important-code-bg-color)}.hint-container.info{border-color:var(--info-border-color);background:var(--info-bg-color)}.hint-container.info>.hint-container-title{color:var(--info-title-color)}.hint-container.info>.hint-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v6h2v-6h-2zm0-4v2h2V7h-2z' fill='%234cb3d4'/%3E%3C/svg%3E")}.hint-container.info code{background:var(--info-code-bg-color)}.hint-container.note{border-color:var(--note-border-color);background:var(--note-bg-color)}.hint-container.note>.hint-container-title{color:var(--note-title-color)}.hint-container.note>.hint-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v6h2v-6h-2zm0-4v2h2V7h-2z' fill='%23ccc'/%3E%3C/svg%3E")}.hint-container.note code{background:var(--note-code-bg-color)}.hint-container.tip{border-color:var(--tip-border-color);background:var(--tip-bg-color)}.hint-container.tip>.hint-container-title{color:var(--tip-title-color)}.hint-container.tip>.hint-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23009400' d='M7.941 18c-.297-1.273-1.637-2.314-2.187-3a8 8 0 1 1 12.49.002c-.55.685-1.888 1.726-2.185 2.998H7.94zM16 20v1a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-1h8zm-3-9.995V6l-4.5 6.005H11v4l4.5-6H13z'/%3E%3C/svg%3E")}.hint-container.tip code{background:var(--tip-code-bg-color)}.hint-container.warning{border-color:var(--warning-border-color);background:var(--warning-bg-color)}.hint-container.warning>.hint-container-title{color:var(--warning-title-color)}.hint-container.warning>.hint-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M576.286 752.57v-95.425q0-7.031-4.771-11.802t-11.3-4.772h-96.43q-6.528 0-11.3 4.772t-4.77 11.802v95.424q0 7.031 4.77 11.803t11.3 4.77h96.43q6.528 0 11.3-4.77t4.77-11.803zm-1.005-187.836 9.04-230.524q0-6.027-5.022-9.543-6.529-5.524-12.053-5.524H456.754q-5.524 0-12.053 5.524-5.022 3.516-5.022 10.547l8.538 229.52q0 5.023 5.022 8.287t12.053 3.265h92.913q7.032 0 11.803-3.265t5.273-8.287zM568.25 95.65l385.714 707.142q17.578 31.641-1.004 63.282-8.538 14.564-23.354 23.102t-31.892 8.538H126.286q-17.076 0-31.892-8.538T71.04 866.074q-18.582-31.641-1.004-63.282L455.75 95.65q8.538-15.57 23.605-24.61T512 62t32.645 9.04 23.605 24.61z' fill='%23e6a700'/%3E%3C/svg%3E")}.hint-container.warning code{background:var(--warning-code-bg-color)}.hint-container.caution{border-color:var(--caution-border-color);background:var(--caution-bg-color)}.hint-container.caution>.hint-container-title{color:var(--caution-title-color)}.hint-container.caution>.hint-container-title:before{background-image:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2c5.523 0 10 4.477 10 10v3.764a2 2 0 0 1-1.106 1.789L18 19v1a3 3 0 0 1-2.824 2.995L14.95 23a2.5 2.5 0 0 0 .044-.33L15 22.5V22a2 2 0 0 0-1.85-1.995L13 20h-2a2 2 0 0 0-1.995 1.85L9 22v.5c0 .171.017.339.05.5H9a3 3 0 0 1-3-3v-1l-2.894-1.447A2 2 0 0 1 2 15.763V12C2 6.477 6.477 2 12 2zm-4 9a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z' fill='%23e13238'/%3E%3C/svg%3E")}.hint-container.caution code{background:var(--caution-code-bg-color)}.hint-container.details{position:relative;display:block;margin:1rem 0;padding:1.5rem;border-radius:.5rem;background:var(--detail-bg-color);color:var(--detail-text-color);transition:background var(--vp-tt),color var(--vp-tt)}@media (max-width: 419px){.hint-container.details{margin-inline:-.75rem}}.hint-container.details h4{margin-top:0}.hint-container.details figure:last-child,.hint-container.details p:last-child{margin-bottom:0;padding-bottom:0}.hint-container.details a{color:var(--vp-tc)}.hint-container.details code{background:var(--detail-code-bg-color)}.hint-container.details summary{position:relative;margin:-1.5rem;padding-block:1.5rem;padding-inline:4rem 1.5rem;list-style:none;cursor:pointer}.hint-container.details summary::-webkit-details-marker{display:none}.hint-container.details summary::marker{color:transparent;font-size:0}.hint-container.details summary:before,.hint-container.details summary:after{content:" ";position:absolute;top:calc(50% - .75rem);inset-inline-start:1.5rem;width:1.5rem;height:1.5rem}.hint-container.details summary:before{border-radius:50%;background:#ccc;transition:background var(--vp-ct),transform var(--vp-tt)}html[data-theme=dark] .hint-container.details summary:before{background:#555}.hint-container.details summary:after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");line-height:normal;transition:transform var(--vp-tt);transform:rotate(90deg)}html[data-theme=dark] .hint-container.details summary:after{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.hint-container.details[open]>summary{margin-bottom:.5em}.hint-container.details[open]>summary:after{transform:rotate(180deg)}/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */.pswp{--pswp-bg: #000;--pswp-placeholder-bg: #222;--pswp-root-z-index: 100000;--pswp-preloader-color: rgba(79, 79, 79, .4);--pswp-preloader-color-secondary: rgba(255, 255, 255, .9);--pswp-icon-color: #fff;--pswp-icon-color-secondary: #4f4f4f;--pswp-icon-stroke-color: #4f4f4f;--pswp-icon-stroke-width: 2px;--pswp-error-text-color: var(--pswp-icon-color)}.pswp{position:fixed;top:0;left:0;width:100%;height:100%;z-index:var(--pswp-root-z-index);display:none;touch-action:none;outline:0;opacity:.003;contain:layout style size;-webkit-tap-highlight-color:rgba(0,0,0,0)}.pswp:focus{outline:0}.pswp *{box-sizing:border-box}.pswp img{max-width:none}.pswp--open{display:block}.pswp,.pswp__bg{transform:translateZ(0);will-change:opacity}.pswp__bg{opacity:.005;background:var(--pswp-bg)}.pswp,.pswp__scroll-wrap{overflow:hidden}.pswp__scroll-wrap,.pswp__bg,.pswp__container,.pswp__item,.pswp__content,.pswp__img,.pswp__zoom-wrap{position:absolute;top:0;left:0;width:100%;height:100%}.pswp__img,.pswp__zoom-wrap{width:auto;height:auto}.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img{cursor:zoom-in}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img{cursor:move;cursor:grab}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active{cursor:grabbing}.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,.pswp__img{cursor:zoom-out}.pswp__container,.pswp__img,.pswp__button,.pswp__counter{-webkit-user-select:none;-moz-user-select:none;user-select:none}.pswp__item{z-index:1;overflow:hidden}.pswp__hidden{display:none!important}.pswp__content{pointer-events:none}.pswp__content>*{pointer-events:auto}.pswp__error-msg-container{display:grid}.pswp__error-msg{margin:auto;font-size:1em;line-height:1;color:var(--pswp-error-text-color)}.pswp .pswp__hide-on-close{opacity:.005;will-change:opacity;transition:opacity var(--pswp-transition-duration) cubic-bezier(.4,0,.22,1);z-index:10;pointer-events:none}.pswp--ui-visible .pswp__hide-on-close{opacity:1;pointer-events:auto}.pswp__button{position:relative;display:block;width:50px;height:60px;padding:0;margin:0;overflow:hidden;cursor:pointer;background:none;border:0;box-shadow:none;opacity:.85;-webkit-appearance:none;-webkit-touch-callout:none}.pswp__button:hover,.pswp__button:active,.pswp__button:focus{transition:none;padding:0;background:none;border:0;box-shadow:none;opacity:1}.pswp__button:disabled{opacity:.3;cursor:auto}.pswp__icn{fill:var(--pswp-icon-color);color:var(--pswp-icon-color-secondary)}.pswp__icn{position:absolute;top:14px;left:9px;width:32px;height:32px;overflow:hidden;pointer-events:none}.pswp__icn-shadow{stroke:var(--pswp-icon-stroke-color);stroke-width:var(--pswp-icon-stroke-width);fill:none}.pswp__icn:focus{outline:0}div.pswp__img--placeholder,.pswp__img--with-bg{background:var(--pswp-placeholder-bg)}.pswp__top-bar{position:absolute;left:0;top:0;width:100%;height:60px;display:flex;flex-direction:row;justify-content:flex-end;z-index:10;pointer-events:none!important}.pswp__top-bar>*{pointer-events:auto;will-change:opacity}.pswp__button--close{margin-right:6px}.pswp__button--arrow{position:absolute;width:75px;height:100px;top:50%;margin-top:-50px}.pswp__button--arrow:disabled{display:none;cursor:default}.pswp__button--arrow .pswp__icn{top:50%;margin-top:-30px;width:60px;height:60px;background:none;border-radius:0}.pswp--one-slide .pswp__button--arrow{display:none}.pswp--touch .pswp__button--arrow{visibility:hidden}.pswp--has_mouse .pswp__button--arrow{visibility:visible}.pswp__button--arrow--prev{right:auto;left:0}.pswp__button--arrow--next{right:0}.pswp__button--arrow--next .pswp__icn{left:auto;right:14px;transform:scaleX(-1)}.pswp__button--zoom{display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__zoom-icn-bar-v{display:none}.pswp__preloader{position:relative;overflow:hidden;width:50px;height:60px;margin-right:auto}.pswp__preloader .pswp__icn{opacity:0;transition:opacity .2s linear;animation:pswp-clockwise .6s linear infinite}.pswp__preloader--active .pswp__icn{opacity:.85}@keyframes pswp-clockwise{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pswp__counter{height:30px;margin-top:15px;margin-inline-start:20px;font-size:14px;line-height:30px;color:var(--pswp-icon-color);text-shadow:1px 1px 3px var(--pswp-icon-color-secondary);opacity:.85}.pswp--one-slide .pswp__counter{display:none}.photo-swipe-loading{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center}.photo-swipe-bullets-indicator{position:absolute;bottom:30px;left:50%;display:flex;flex-direction:row;align-items:center;transform:translate(-50%)}.photo-swipe-bullet{width:12px;height:6px;margin:0 5px;border-radius:3px;background:var(--photo-swipe-bullet);transition:width .3s,color .3s}.photo-swipe-bullet.active{width:30px;background:var(--photo-swipe-bullet-active)}:root{--photo-swipe-bullet: #fff;--photo-swipe-bullet-active: #3eaf7c}.search-pro-button{border-width:0;background:transparent;display:inline-flex;align-items:center;box-sizing:content-box;height:1.25rem;margin-inline:1rem 0;margin-top:0;margin-bottom:0;padding:.5rem;border:0;border:1px solid var(--vp-bgl);border-radius:1rem;background:var(--vp-bgl);color:var(--vp-c);font-weight:500;cursor:pointer;transition:background var(--vp-ct),color var(--vp-ct)}@media print{.search-pro-button{display:none}}@media (max-width: 959px){.search-pro-button{border-radius:50%}}.search-pro-button:hover{border:1px solid var(--vp-tc);background-color:var(--vp-bglt);color:var(--vp-clt)}.search-pro-button .search-icon{width:1.25rem;height:1.25rem}.search-pro-placeholder{margin-inline:.25rem;font-size:1rem}@media (max-width: 959px){.search-pro-placeholder{display:none}}.search-pro-key-hints{font-size:.75rem}@media (max-width: 959px){.search-pro-key-hints{display:none}}.search-pro-key{display:inline-block;min-width:1em;margin-inline:.125rem;padding:.25rem;border:1px solid var(--vp-brc);border-radius:4px;box-shadow:1px 1px 4px 0 var(--card-shadow);line-height:1;letter-spacing:-.1em;transition:background var(--vp-ct),color var(--vp-ct),border var(--vp-ct) box-shadow var(--vp-ct)}@keyframes search-pro-fade-in{0%{opacity:.2}to{opacity:1}}.search-pro-modal-wrapper{position:fixed;top:0;right:0;bottom:0;left:0;z-index:997;display:flex;align-items:center;justify-content:center;overflow:auto;cursor:default}.search-pro-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:998;animation:.25s search-pro-fade-in;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.search-pro-modal{position:absolute;z-index:999;display:flex;flex-direction:column;width:calc(100% - 6rem);max-width:50em;border-radius:10px;background:var(--vp-bg);box-shadow:2px 2px 10px 0 var(--card-shadow);transition:background var(--vp-ct);animation:.15s pwa-opened}@media (max-width: 1280px){.search-pro-modal{animation:.25s pwa-mobile}}@media (max-width: 719px){.search-pro-modal{top:0;right:0;bottom:0;left:0;box-sizing:border-box;width:100%;max-width:unset;padding:env(--safe-area-inset-top) env(--safe-area-inset-right) env(--safe-area-inset-bottom) env(--safe-area-inset-left)}}.search-pro-box{display:flex;margin:1rem}.search-pro-box form{position:relative;display:flex;flex:1}.search-pro-box label{position:absolute;top:calc(50% - .75rem);inset-inline-start:.5rem;color:var(--vp-tc)}.search-pro-box label .search-icon{width:1.5rem;height:1.5rem}.search-pro-clear-button{border-width:0;background:transparent;cursor:pointer;position:absolute;top:calc(50% - 10px);inset-inline-end:.75rem;padding:0;color:var(--vp-tc)}.search-pro-clear-button:hover{border-radius:50%;background-color:#0000001a}.search-pro-close-button{border-width:0;background:transparent;cursor:pointer;display:none;margin-inline:.5rem -.5rem;padding:.5rem;color:var(--grey-darker);font-size:1rem}@media (max-width: 719px){.search-pro-close-button{display:block}}.search-pro-input{flex:1;width:0;margin:0;padding-block:.25rem;padding-inline:2.5rem 2rem;border:0;border:2px solid var(--vp-tc);border-radius:8px;background:var(--vp-bg);color:var(--vp-c);outline:none;font-size:1.25rem;line-height:2.5;-webkit-appearance:none;-moz-appearance:none;appearance:none}.search-pro-input::-webkit-search-cancel-button{display:none}.search-pro-suggestions{position:absolute;inset:calc(100% + 4px) 0 auto;z-index:20;overflow:visible;overflow-y:auto;max-height:50vh;margin:0;padding:0;border-radius:.5rem;background-color:var(--vp-bg);box-shadow:2px 2px 10px 0 var(--card-shadow);list-style:none;line-height:1.5}.search-pro-suggestion{padding:.25rem 1rem;border-top:1px solid var(--vp-brc);cursor:pointer}.search-pro-suggestion:first-child{border-top:none}.search-pro-suggestion.active,.search-pro-suggestion:hover{background-color:var(--vp-bglt)}.search-pro-auto-complete{display:none;float:right;margin:0 .5rem;padding:4px;border:1px solid var(--vp-brc);border-radius:4px;box-shadow:1px 1px 4px 0 var(--card-shadow);font-size:12px;line-height:1}.search-pro-suggestion.active .search-pro-auto-complete{display:block}.search-pro-result-wrapper{flex-grow:1;overflow-y:auto;min-height:40vh;max-height:calc(80vh - 10rem);padding:0 1rem}@media (max-width: 719px){.search-pro-result-wrapper{min-height:unset;max-height:unset}}.search-pro-result-wrapper.loading,.search-pro-result-wrapper.empty{display:flex;align-items:center;justify-content:center;padding:1.5rem;font-weight:600;font-size:22px;text-align:center}.search-pro-hints{margin-top:1rem;padding:.75rem .5rem;box-shadow:0 -1px 4px 0 var(--card-shadow);line-height:1}.search-pro-hint{display:inline-flex;align-items:center;margin:0 .5rem}.search-pro-hint kbd{margin:0 .5rem;padding:2px;border:1px solid var(--vp-brc);border-radius:4px;box-shadow:1px 1px 4px 0 var(--card-shadow)}.search-pro-hint kbd+kbd{margin-inline-start:-.25rem}.search-pro-hint svg{display:block;width:15px;height:15px}:root{--balloon-border-radius: 2px;--balloon-color: rgba(16, 16, 16, .95);--balloon-text-color: #fff;--balloon-font-size: 12px;--balloon-move: 4px}button[aria-label][data-balloon-pos]{overflow:visible}[aria-label][data-balloon-pos]{position:relative;cursor:pointer}[aria-label][data-balloon-pos]:after{opacity:0;pointer-events:none;transition:all .18s ease-out .18s;text-indent:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;font-weight:400;font-style:normal;text-shadow:none;font-size:var(--balloon-font-size);background:var(--balloon-color);border-radius:2px;color:var(--balloon-text-color);border-radius:var(--balloon-border-radius);content:attr(aria-label);padding:.5em 1em;position:absolute;white-space:nowrap;z-index:10}[aria-label][data-balloon-pos]:before{width:0;height:0;border:5px solid transparent;border-top-color:var(--balloon-color);opacity:0;pointer-events:none;transition:all .18s ease-out .18s;content:"";position:absolute;z-index:10}[aria-label][data-balloon-pos]:hover:before,[aria-label][data-balloon-pos]:hover:after,[aria-label][data-balloon-pos][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-visible]:after,[aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:before,[aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:after{opacity:1;pointer-events:none}[aria-label][data-balloon-pos].font-awesome:after{font-family:FontAwesome,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif}[aria-label][data-balloon-pos][data-balloon-break]:after{white-space:pre}[aria-label][data-balloon-pos][data-balloon-break][data-balloon-length]:after{white-space:pre-line;word-break:break-word}[aria-label][data-balloon-pos][data-balloon-blunt]:before,[aria-label][data-balloon-pos][data-balloon-blunt]:after{transition:none}[aria-label][data-balloon-pos][data-balloon-pos=up]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=up][data-balloon-visible]:after,[aria-label][data-balloon-pos][data-balloon-pos=down]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=down][data-balloon-visible]:after{transform:translate(-50%)}[aria-label][data-balloon-pos][data-balloon-pos=up]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=up][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-pos=down]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=down][data-balloon-visible]:before{transform:translate(-50%)}[aria-label][data-balloon-pos][data-balloon-pos*=-left]:after{left:0}[aria-label][data-balloon-pos][data-balloon-pos*=-left]:before{left:5px}[aria-label][data-balloon-pos][data-balloon-pos*=-right]:after{right:0}[aria-label][data-balloon-pos][data-balloon-pos*=-right]:before{right:5px}[aria-label][data-balloon-pos][data-balloon-po*=-left]:hover:after,[aria-label][data-balloon-pos][data-balloon-po*=-left][data-balloon-visible]:after,[aria-label][data-balloon-pos][data-balloon-pos*=-right]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos*=-right][data-balloon-visible]:after{transform:translate(0)}[aria-label][data-balloon-pos][data-balloon-po*=-left]:hover:before,[aria-label][data-balloon-pos][data-balloon-po*=-left][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-pos*=-right]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos*=-right][data-balloon-visible]:before{transform:translate(0)}[aria-label][data-balloon-pos][data-balloon-pos^=up]:before,[aria-label][data-balloon-pos][data-balloon-pos^=up]:after{bottom:100%;transform-origin:top;transform:translateY(var(--balloon-move))}[aria-label][data-balloon-pos][data-balloon-pos^=up]:after{margin-bottom:10px}[aria-label][data-balloon-pos][data-balloon-pos=up]:before,[aria-label][data-balloon-pos][data-balloon-pos=up]:after{left:50%;transform:translate(-50%,var(--balloon-move))}[aria-label][data-balloon-pos][data-balloon-pos^=down]:before,[aria-label][data-balloon-pos][data-balloon-pos^=down]:after{top:100%;transform:translateY(calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos^=down]:after{margin-top:10px}[aria-label][data-balloon-pos][data-balloon-pos^=down]:before{width:0;height:0;border:5px solid transparent;border-bottom-color:var(--balloon-color)}[aria-label][data-balloon-pos][data-balloon-pos=down]:after,[aria-label][data-balloon-pos][data-balloon-pos=down]:before{left:50%;transform:translate(-50%,calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=left]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=left][data-balloon-visible]:after,[aria-label][data-balloon-pos][data-balloon-pos=right]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=right][data-balloon-visible]:after{transform:translateY(-50%)}[aria-label][data-balloon-pos][data-balloon-pos=left]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=left][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-pos=right]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=right][data-balloon-visible]:before{transform:translateY(-50%)}[aria-label][data-balloon-pos][data-balloon-pos=left]:after,[aria-label][data-balloon-pos][data-balloon-pos=left]:before{right:100%;top:50%;transform:translate(var(--balloon-move),-50%)}[aria-label][data-balloon-pos][data-balloon-pos=left]:after{margin-right:10px}[aria-label][data-balloon-pos][data-balloon-pos=left]:before{width:0;height:0;border:5px solid transparent;border-left-color:var(--balloon-color)}[aria-label][data-balloon-pos][data-balloon-pos=right]:after,[aria-label][data-balloon-pos][data-balloon-pos=right]:before{left:100%;top:50%;transform:translate(calc(var(--balloon-move) * -1),-50%)}[aria-label][data-balloon-pos][data-balloon-pos=right]:after{margin-left:10px}[aria-label][data-balloon-pos][data-balloon-pos=right]:before{width:0;height:0;border:5px solid transparent;border-right-color:var(--balloon-color)}[aria-label][data-balloon-pos][data-balloon-length]:after{white-space:normal}[aria-label][data-balloon-pos][data-balloon-length=small]:after{width:80px}[aria-label][data-balloon-pos][data-balloon-length=medium]:after{width:150px}[aria-label][data-balloon-pos][data-balloon-length=large]:after{width:260px}[aria-label][data-balloon-pos][data-balloon-length=xlarge]:after{width:380px}@media screen and (max-width: 768px){[aria-label][data-balloon-pos][data-balloon-length=xlarge]:after{width:90vw}}[aria-label][data-balloon-pos][data-balloon-length=fit]:after{width:100%}:root{--navbar-bg-color: var(--bg-color-float-blur);--sidebar-bg-color: var(--bg-color-blur)}html[data-theme=dark]{--navbar-bg-color: var(--bg-color-blur);--sidebar-bg-color: var(--bg-color-blur)}#app{--code-hl-bg-color: var(--code-highlight-line-color);--code-ln-color: var(--code-line-color);--code-ln-wrapper-width: var(--line-numbers-width);--code-tabs-nav-text-color: var(--code-color);--code-tabs-nav-bg-color: var(--code-border-color);--code-tabs-nav-hover-color: var(--code-highlight-line-color);--sidebar-space: var(--sidebar-width)}@media (max-width: 959px){#app{--navbar-height: var(--navbar-mobile-height);--navbar-vertical-padding: var(--navbar-mobile-vertical-padding);--navbar-horizontal-padding: var(--navbar-mobile-horizontal-padding);--sidebar-width: var(--sidebar-mobile-width)}}@media (min-width: 1440px){#app{--sidebar-space: clamp( var(--sidebar-width), max(0px, calc((100vw - var(--content-width)) / 2 - 2rem)) , 100vw )}}.vp-copy-code-button{--copy-code-color: var(--code-ln-color);--copy-code-hover: var(--code-hl-bg-color)}.DocSearch-Button,.DocSearch{--docsearch-primary-color: var(--theme-color);--docsearch-text-color: var(--text-color);--docsearch-highlight-color: var(--theme-color);--docsearch-muted-color: var(--grey-light);--docsearch-container-background: rgb(9 10 17 / 80%);--docsearch-modal-background: var(--bg-color-float);--docsearch-searchbox-background: var(--bg-color-secondary);--docsearch-searchbox-focus-background: var(--bg-color);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--theme-color);--docsearch-hit-color: var(--text-color-light);--docsearch-hit-active-color: var(--bg-color);--docsearch-hit-background: var(--bg-color);--docsearch-hit-shadow: 0 1px 3px 0 var(--border-color);--docsearch-footer-background: var(--bg-color)}html[data-theme=dark] .DocSearch-Button,html[data-theme=dark] .DocSearch{--docsearch-logo-color: var(--text-color);--docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgb(3 4 9 / 30%);--docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow: inset 0 1px 0 0 rgb(73 76 106 / 50%), 0 -4px 8px 0 rgb(0 0 0 / 20%)}.vp-back-to-top-button{--back-to-top-color: var(--theme-color);--back-to-top-color-hover: var(--theme-color-light);--back-to-top-bg-color: var(--bg-color)}.vp-catalog-wrapper{--catalog-bg-color: var(--bg-color);--catalog-bg-secondary-color: var(--bg-color-light);--catalog-border-color: var(--border-color);--catalog-active-color: var(--theme-color);--catalog-hover-color: var(--theme-color-light)}.external-link-icon{--external-link-icon-color: var(--grey-light)}#nprogress{--nprogress-color: var(--theme-color)}body{--photo-swipe-bullet: var(--white);--photo-swipe-bullet-active: var(--theme-color);--pwa-text-color: var(--text-color);--pwa-bg-color: var(--bg-color);--pwa-border-color: var(--border-color);--pwa-btn-text-color: var(--bg-color);--pwa-btn-bg-color: var(--theme-color);--pwa-btn-hover-bg-color: var(--theme-color-light);--pwa-shadow-color: var(--card-shadow);--pwa-content-color: var(--grey-darker);--pwa-content-light-color: var(--grey-dark)}.language-modal-mask{--redirect-bg-color: var(--bg-color);--redirect-bg-color-light: var(--bg-color-light);--redirect-bg-color-lighter: var(--bg-color-tertiary);--redirect-text-color: var(--text-color);--redirect-primary-color: var(--theme-color);--redirect-primary-hover-color: var(--theme-color-light);--redirect-primary-text-color: var(--white)}.search-box{--search-bg-color: var(--bg-color);--search-accent-color: var(--theme-color);--search-text-color: var(--text-color);--search-border-color: var(--border-color);--search-item-text-color: var(--text-color-lighter);--search-item-focus-bg-color: var(--bg-color-secondary)}.waline-wrapper{--waline-bg-color: var(--bg-color);--waline-bg-color-light: var(--bg-color-secondary);--waline-text-color: var(--text-color);--waline-border: 1px solid var(--border-color);--waline-border-color: var(--border-color);--waline-theme-color: var(--theme-color);--waline-active-color: var(--theme-color-light)}html,body{margin:0;padding:0;background:#fff}html{font-size:16px;font-display:optional;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-tap-highlight-color:transparent}@media print{html{font-size:12pt}}body{min-height:100vh;color:#2c3e50}a{color:#3eaf7c;font-weight:500;text-decoration:none;overflow-wrap:break-word}kbd{display:inline-block;min-width:1em;margin-inline:.125rem;padding:.25em;border:1px solid #eee;border-radius:.25em;box-shadow:1px 1px 4px #00000026;line-height:1;letter-spacing:-.1em;text-align:center}code{margin:0;padding:.2rem .4rem;border-radius:5px;background:#7f7f7f1f;font-size:.85em;overflow-wrap:break-word}table code{padding:.1rem .4rem}p a code{color:#3eaf7c;font-weight:400}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.25;overflow-wrap:break-word}h1:focus-visible,h2:focus-visible,h3:focus-visible,h4:focus-visible,h5:focus-visible,h6:focus-visible{outline:none}h1{font-size:2rem}h2{padding-bottom:.3rem;border-bottom:1px solid #eaecef;font-size:1.65rem}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}h6{font-size:1rem}a.header-anchor{position:relative;color:inherit}a.header-anchor:hover:before{content:"¶";position:absolute;bottom:0;left:-.75em;color:var(--c-brand);font-size:.75em}a.header-anchor:focus-visible{outline:none}a.header-anchor:focus-visible:before{content:"¶";position:absolute;left:-.75em;color:var(--c-brand);outline:auto}p,ul,ol{line-height:1.6;overflow-wrap:break-word}@media print{p,ul,ol{line-height:1.5}}ul,ol{padding-inline-start:1.2em}blockquote{margin:1rem 0;padding:.25rem 0 .25rem 1rem;border-inline-start:.2rem solid #ddd;color:#666;font-size:1rem;overflow-wrap:break-word}blockquote>p{margin:0}hr{border:0;border-top:1px solid #eaecef}table{display:block;overflow-x:auto;margin:1rem 0;border-collapse:collapse}tr:nth-child(odd){background:#f6f8fa}th,td{padding:.6em 1em;border:1px solid #dfe2e5}pre{direction:ltr}@page{margin:2cm;font-size:12pt;size:a4}@media print{*,:after,:before{box-shadow:none!important;text-shadow:none!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}a{color:inherit;font-weight:inherit!important;font-size:inherit!important;text-decoration:underline}a[href^="http://"]:after,a[href^="https://"]:after{content:" (" attr(href) ") "}abbr[title]:after{content:" (" attr(title) ")"}pre{border:1px solid #eee;white-space:pre-wrap!important}pre>code{white-space:pre-wrap!important}blockquote{border-inline-start:.2rem solid #ddd;color:inherit}blockquote,pre{orphans:5;widows:5}img,tr,canvas{page-break-inside:avoid}}@font-face{font-weight:400;font-style:normal;font-family:Crimson;src:url(data:font/truetype;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTYr5mwEAAAyMAAAAHEdERUYAKQATAAAMbAAAAB5PUy8yVsJ0MgAAAVgAAABgY21hcBiKDzgAAAHcAAABWGdhc3D//wADAAAMZAAAAAhnbHlmr+DBdQAAA1AAAAdsaGVhZBZwt+8AAADcAAAANmhoZWEFawEuAAABFAAAACRobXR4BksA9gAAAbgAAAAibG9jYQlsC24AAAM0AAAAHG1heHAAEQBZAAABOAAAACBuYW1lLaFDVAAACrwAAAFrcG9zdAC1AHoAAAwoAAAAPAABAAAAAQAAqBd2H18PPPUACwQAAAAAANqqufwAAAAA2qq5/AAb/9wB4QMeAAAACAACAAAAAAAAAAEAAAMs/ywAXAH9AAAAAAHhAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAANAFkAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAH1AZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAIABgMAAAAAAAAAAAABEAAAAAAAAAAAAAAAUGZFZADAADAAOQMs/ywAXAMsANQAAAABAAAAAAMYAAAAAAAgAAEBpwAfAAAAAAFVAAAB/QAfAH0ALQA+ABsAPgAyACgAPgAxAAAAAAADAAAAAwAAABwAAQAAAAAAUgADAAEAAAAcAAQANgAAAAQABAABAAAAOf//AAAAL///AAAAAQAEAAAAAAADAAQABQAGAAcACAAJAAoACwAMAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAwQFBgcICQoLDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYAJgAmAGIAwAEeAZIBzgJAApYC2gNiA7YAAQAf/9wBhwMeABIAAAEGBwYHATAXFjM2NzY3ASYnJjcBgxwLCgH+zgMECxIKCgIBLgEDAwMDHhQFBgP85wMEAQgJBgMOAwMDEwAAAAIAH//9Ad0CkAAQACEAABMWFxYXNjc2NzQnJicGBwYHNyY3NjcWFxYXFAcGByYnJjcfATo6amo7OQE5OmxrOjkBXQIlJEE5IyIBIyJEOSQjAgFOkV5eBAReXoqJXl4EBF5eggJ0UlEDA09Qe3xVVgMDU1OEAAAAAAEAff/9AYACkQA+AAA3FAcGBwYHBiMGFQYXNjc2MzIXFhc2JzQnIicmJyY1JjURNjc2MSYnJicjBgcGBwYVFBUUFxYXNjc2NzIXFhXkAQEEBRgYDAMBBB4ZGhweGxofBAEDDBgZBQQBAQMEAQIDBAIFNTZCAgMDBA0XFw0LBQV3GBMVDAgEBAUKCgUCAQICAQIFCgoFBAQIDBUTGAGnLxkbBAYFAQIZGh4BAgECBQUEAwUHBwEICRYAAAAAAQAtAAAB0QKRADoAADcGFxYXITY3NjcmJyYjIgcGBwYHBisBNjc2NzY3NjUmJyYnBgcGBxQXFhc2NzY3FhcWFxYHBgcGBwYHLgEEAwMBYwURERADBwYFBAMDAg8VEx/LJkBAOhsQDwIxMkxSMjIHCAYGCSYmPTIfHwEBCgoeLkJBQg8EBQQCETAwKQICAgEBBCgUEylJSUYhJicsRDIzAgY1NRoEBQYBEyEhAwEjIjYlJCQtQlBQSAAAAAABAD7/+wG+ApEASgAANwYXFhcWFxYzNjc2NyYnJic2NzY3JicmIwYHBgcUFxYXNjc2NxYXFhcGBwYHBgcUFRQXNjc2NxYXFhcGBwYnIicmJyYnJiciBwYXPwEIBwUaHB0VZU5NBAMvLi8eIB4DAywsKzwrKxgEAwUIHR4wLRscAQMvLz8BAQYKEhEQNSYmAgImJSsWExQPCw0NFREMDQE7DgsLBQwFBgE8PWpMKSoGECQkMkAiIQIdHyUHBwcBCRscAwEbGSpCIyUOAgMCAwwIAwUEAQEoKD9XJSQBBQYODg8PAQ0NFQAAAgAb//oB4QKTACIAJQAANxQXFhchFRQXFjMyNzYjNTM2NzY1NCcmJyMRNCcmIwYHBgcBExEbAgMFASEJCRIdCAkBRgIBAQUEBTwFAwgHCQkG/vjmxgUGBgOwBQIBAwKzAgQDCBAMDQEBlAYGBgEICQf+cwEs/tQAAQA+//sBvgKTAEoAADcGFxYXFhcWMzY3NjcmJyYnIgcGBzY3NjczMjc2NzY3NjU0JyYnBgcGByMGBwYHFBcWMzY3NjMWFxYHBgcGJyInJicmJyYnIgcGFz8BCAcFGhwdFWVOTQQBMjJbFx8gFwoJCQlWKB0dFQ4JCAQDBQMdHSKXCREQEgMCBA4bGhNYJyUBAiYlKxYTFA8LDQ0VEQwNATsOCwsFDAUGATw9akU2NwMFBggrMC8uAgICExcZBgQCAgMBAwQBMVNUWAUFBAYFBAMxMTNZIyQBBQYODg8PAQ0NFQAAAgAy//oBzQKXACAAMwAANxQXFhc2NzY3NicmJyIHBgc2NzY3NCcmJwYHBgcGBwYXNyY3Njc2FxYXFgcGBwYHJicmNzM1NV5aOTsCAioqahoiIRsnWFhFAwIHQ0tMOTAZGQFbBAQaGxkXRB8fAQEfIDE9Hh4E511FRwQDPT1ZPEJBBQwLF4Y9PRMGCwwBEiwsPDZFRkkTHyAbCAcBAjAwREYsLQEFREVQAAAAAAEAKP/7AdUCiwApAAATFhcWMzI3Njc2NzYzIQYHBgcWFxYzMjcBNjc2NzQnJiMiBwYjIQYHBgcoAwYHAwYDAwELEBEdAQUJYWJXAQ8PDgcDAQ4LCQgBAQEEBhUVFv7JBgsNDAH6DQMCAQEFKRITFMjHjQcFBgMCPxYSEwoEAgMBAhkrKiAAAAADAD7/9wG/ApIAKABBAFgAADcGFxYXNjc2NyYnJicmJzQ3Njc2NyYnJiMGBwYHFhcWFxYVFAcGBwYHNyY3Njc2MzIzMhcyFxYXFhcGBwYHIicmNxMmNzY3FhcWFRQHBgcGByIjIicmJyY3PwE1M1ZQODgDAykpMQIBAyYlJQMCMC9HRjExAgIiIiMCAiMvLwNTBBQTKgEBAQECAQIBEjU1CAEdHjMrISICGAMYGSYvGxoTEx8CAQIBBAMfJCQBoU8tLQECMjFPOC4uGwIBAgEWJiU7SCYoAjEwQzopKhMBAgECEykpQAQsIiEbAQEBBywsQjUeHQEiI0QBZSMhIAECJiYvKh8gFAEBAhAfIEYAAAIAMf/6AcsClwAgADMAABMGFxYXMjc2NwYHBgcUFxYXNjc2NzY3NjUmJyYnBgcGBzcmNzY3FhcWFRQHBgcGJyYnJjc0AyopahoiIRsoV1hFAwIHQ0tMODEZGQE2NF5ZOjoBWgMfHzE9Hh4EGhoaF0QeHwUBy0dBQgUMCxeFPj0SBwsLAREsLD01RkVPV0dFBQQ8PU8UPCwtAQVFRUklIRsHCAECMDBPAAAADACWAAEAAAAAAAEABwAQAAEAAAAAAAIABwAoAAEAAAAAAAMABwBAAAEAAAAAAAQABwBYAAEAAAAAAAUAHgCeAAEAAAAAAAYABwDNAAMAAQQJAAEADgAAAAMAAQQJAAIADgAYAAMAAQQJAAMADgAwAAMAAQQJAAQADgBIAAMAAQQJAAUAPABgAAMAAQQJAAYADgC9AEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAFYAZQByAHMAaQBvAG4AIAAxAC4AMAA7ACAARgBvAG4AdABFAGQAaQB0AG8AcgAgACgAdgAxAC4AMAApAABWZXJzaW9uIDEuMDsgRm9udEVkaXRvciAodjEuMCkAAEMAcgBpAG0AcwBvAG4AAENyaW1zb24AAAACAAAAAAAAADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAABAAIAEwAUABUAFgAXABgAGQAaABsAHAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAwAAQAEAAAAAgAAAAAAAAABAAAAANWkJwgAAAAA2qq5/AAAAADaqrn8) format("truetype")}html,body{background:var(--bg-color)}:root{color-scheme:light}html[data-theme=dark]{color-scheme:dark}body{color:var(--text-color);font-family:var(--font-family)}@media (min-width: 1440px){body{font-size:17px}}a{color:var(--theme-color)}kbd{border-color:var(--border-color-dark);background:var(--bg-color-secondary);font-family:var(--font-family-mono)}code{font-family:var(--font-family-mono)}html[data-theme=dark] code{background:#333}p a code{color:var(--theme-color)}blockquote{border-color:#eee;color:#666}html[data-theme=dark] blockquote{border-color:#333}h1,h2,h3,h4,h5,h6{font-family:var(--font-family-heading)}@media (max-width: 419px){h1{font-size:1.9rem}}h2,hr{border-color:var(--border-color)}tr:nth-child(odd){background:var(--bg-color-secondary)}th,td{border-color:var(--border-color-dark)}@media print{@page{--text-color: #000 !important;--bg-color: #fff !important}div[class*=language-]{position:relative!important}}.theme-hope-content:not(.custom)>*:first-child{margin-top:0}.vp-breadcrumb{max-width:var(--content-width, 740px);margin-inline:auto;padding-inline:2.5rem;position:relative;z-index:2;padding-top:1rem;font-size:15px}@media (max-width: 959px){.vp-breadcrumb{padding-inline:1.5rem}}@media print{.vp-breadcrumb{max-width:unset}}@media (max-width: 959px){.vp-breadcrumb{font-size:14px}}@media (max-width: 419px){.vp-breadcrumb{padding-top:.5rem;font-size:12.8px}}@media print{.vp-breadcrumb{display:none}}.vp-breadcrumb .icon{margin-inline-end:.25em;font-size:1em}.vp-breadcrumb img.icon{vertical-align:-.125em;height:1em}.vp-breadcrumb a{display:inline-block;padding:0 .5em}.vp-breadcrumb a:before{position:relative;bottom:.125rem;margin-inline-end:.25em}.vp-breadcrumb a:hover{color:var(--theme-color)}.vp-breadcrumb ol{margin:0;padding-inline-start:0;list-style:none}.vp-breadcrumb li{display:inline-block;line-height:1.5}.vp-breadcrumb li:first-child a{padding-inline-start:0}.vp-breadcrumb li:last-child a{padding-inline-end:0}.vp-breadcrumb li.is-active a{color:var(--grey-light);cursor:default;pointer-events:none}.vp-breadcrumb li+li:before{content:"/";color:var(--grey-light)}.toggle-sidebar-wrapper{position:fixed;top:var(--navbar-height);bottom:0;inset-inline-start:var(--sidebar-space);z-index:100;display:flex;align-items:center;justify-content:center;font-size:2rem;transition:inset-inline-start var(--transform-transition)}@media (max-width: 719px){.toggle-sidebar-wrapper{display:none}}@media (min-width: 1440px){.toggle-sidebar-wrapper{display:none}}.toggle-sidebar-wrapper:hover{background:#7f7f7f0d;cursor:pointer}.toggle-sidebar-wrapper .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s}html[data-theme=dark] .toggle-sidebar-wrapper .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.toggle-sidebar-wrapper .arrow.down{transform:rotate(180deg)}html[dir=rtl] .toggle-sidebar-wrapper .arrow.down{transform:rotate(-180deg)}.toggle-sidebar-wrapper .arrow.end{transform:rotate(90deg)}html[dir=rtl] .toggle-sidebar-wrapper .arrow.end,.toggle-sidebar-wrapper .arrow.start{transform:rotate(-90deg)}html[dir=rtl] .toggle-sidebar-wrapper .arrow.start{transform:rotate(90deg)}.theme-container{display:flex;flex-direction:column;justify-content:space-between;min-height:100vh}.theme-container .vp-page{padding-top:var(--navbar-height);padding-inline-start:calc(var(--sidebar-space) + 2rem)}@media (max-width: 719px){.theme-container .vp-page{padding-inline:0}}@media (min-width: 1440px){.theme-container .vp-page{padding-inline-end:calc(100vw - var(--content-width) - var(--sidebar-space) - 6rem)}}.theme-container .vp-sidebar{top:var(--navbar-height)}.theme-container.no-navbar .vp-page{padding-top:0}.theme-container.no-navbar .vp-sidebar{top:0}@media (max-width: 719px){.theme-container.no-navbar .vp-sidebar{top:0}}@media (max-width: 719px){.theme-container.hide-navbar .vp-sidebar{top:0}}.theme-container.sidebar-collapsed .vp-page{padding-inline-start:0}.theme-container.sidebar-collapsed .vp-sidebar{box-shadow:none;transform:translate(-100%)}html[dir=rtl] .theme-container.sidebar-collapsed .vp-sidebar{transform:translate(100%)}.theme-container.sidebar-collapsed .toggle-sidebar-wrapper{inset-inline-start:0}.theme-container.no-sidebar .vp-page{padding-inline:0}@media (min-width: 1440px){.theme-container.no-sidebar.has-toc .vp-page{padding-inline-end:16rem}}.theme-container.no-sidebar .vp-toggle-sidebar-button,.theme-container.no-sidebar .toggle-sidebar-wrapper,.theme-container.no-sidebar .vp-sidebar{display:none}.theme-container.sidebar-open .vp-sidebar{box-shadow:2px 0 8px var(--card-shadow);transform:translate(0)}.fade-slide-y-enter-active{transition:all .3s ease!important}.fade-slide-y-leave-active{transition:all .3s cubic-bezier(1,.5,.8,1)!important}.fade-slide-y-enter-from,.fade-slide-y-leave-to{opacity:0;transform:translateY(10px)}.vp-feature-wrapper{position:relative}.vp-feature-bg{position:absolute;top:0;right:0;bottom:0;left:0;z-index:0;background-attachment:fixed;background-position:50%;background-size:cover}.vp-feature-bg.light{display:inline-block}.vp-feature-bg.dark,html[data-theme=dark] .vp-feature-bg.light{display:none}html[data-theme=dark] .vp-feature-bg.dark{display:inline-block}.vp-feature{position:relative;z-index:1;margin:0 auto;padding:1.5rem 1rem;color:var(--text-color-lighter);text-align:center}.vp-feature-bg+.vp-feature{color:#222}html[data-theme=dark] .vp-feature-bg+.vp-feature{color:#eee}.vp-feature-bg+.vp-feature .icon{color:inherit}.vp-feature-image{height:10rem;margin:0 auto}@media (max-width: 959px){.vp-feature-image{height:8rem}}.vp-feature-image.light{display:inline-block}.vp-feature-image.dark,html[data-theme=dark] .vp-feature-image.light{display:none}html[data-theme=dark] .vp-feature-image.dark{display:inline-block}.vp-feature-header{margin-bottom:1.5rem;border-bottom:none;font-size:3rem;font-family:var(--font-family);text-align:center}@media (max-width: 959px){.vp-feature-header{font-size:2.5rem}}@media (max-width: 719px){.vp-feature-header{font-size:2.25rem}}@media (max-width: 419px){.vp-feature-header{font-size:2rem}}.vp-feature-description{font-size:1.125rem}.vp-features{z-index:1;display:flex;flex-wrap:wrap;align-items:stretch;place-content:stretch center;margin:1rem 0;text-align:start}@media print{.vp-features{display:block}}.vp-features:first-child{border-top:1px solid var(--border-color)}.vp-feature-item{position:relative;display:block;flex-basis:calc(33% - 3rem);margin:.5rem;padding:1rem;border-radius:.5rem;color:inherit;transition:background var(--color-transition),box-shadow var(--color-transition),transform var(--transform-transition)}@media (min-width: 1440px){.vp-feature-item{flex-basis:calc(25% - 3rem)}}@media (max-width: 959px){.vp-feature-item{flex-basis:calc(50% - 3rem)}}@media (max-width: 719px){.vp-feature-item{flex-basis:100%;font-size:.95rem}}@media (max-width: 419px){.vp-feature-item{margin:.5rem 0;font-size:.9rem}}.vp-feature-item.link{cursor:pointer}@media print{.vp-feature-item.link{text-decoration:none}}.vp-feature-item .icon{display:inline-block;height:1.1em;margin-inline-end:.5rem;color:var(--theme-color);font-weight:400;font-size:1.1em}.vp-feature-item:hover{background-color:var(--bg-color-secondary);box-shadow:0 2px 12px 0 var(--card-shadow);transform:translate(-2px,-2px);transform:scale(1.05)}.vp-feature-bg+.vp-feature .vp-feature-item:hover{background-color:transparent}.vp-feature-item:only-child{flex-basis:100%}.vp-feature-item:first-child:nth-last-child(2),.vp-feature-item:nth-child(2):last-child{flex-basis:calc(50% - 3rem)}@media (max-width: 719px){.vp-feature-item:first-child:nth-last-child(2),.vp-feature-item:nth-child(2):last-child{flex-basis:100%}}.vp-feature-title{margin:.25rem 0 .5rem;font-weight:700;font-size:1.3rem;font-family:var(--font-family)}@media (max-width: 419px){.vp-feature-title{font-size:1.2rem}}.vp-feature-details{margin:0;line-height:1.4}.vp-footer-wrapper{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-evenly;padding-block:.75rem;padding-inline:calc(var(--sidebar-space) + 2rem) 2rem;border-top:1px solid var(--border-color);background:var(--bg-color);color:var(--grey-dark);text-align:center;transition:border-top-color var(--color-transition),background var(--color-transition),padding var(--transform-transition)}@media (max-width: 719px){.vp-footer-wrapper{padding-inline-start:2rem}}@media (min-width: 1440px){.vp-footer-wrapper{z-index:50;padding-inline-start:2rem}}@media print{.vp-footer-wrapper{margin:0!important;padding:0!important}}@media (max-width: 419px){.vp-footer-wrapper{display:block}}.no-sidebar .vp-footer-wrapper,.sidebar-collapsed .vp-footer-wrapper{padding-inline-start:2rem}.vp-footer{margin:.5rem 1rem;font-size:14px}@media print{.vp-footer{display:none}}.vp-copyright{margin:6px 0;font-size:13px}.vp-page:not(.not-found)+.vp-footer-wrapper{margin-top:-2rem}.vp-hero-info-wrapper{position:relative;display:flex;align-items:center;justify-content:center;margin-inline:auto}.vp-hero-info-wrapper.fullscreen{height:calc(100vh - var(--navbar-height))!important}.vp-hero-info{z-index:1;width:100%;padding-inline:2.5rem}@media (max-width: 959px){.vp-hero-info{padding-inline:1.5rem}}@media (min-width: 959px){.vp-hero-info{display:flex;align-items:center;justify-content:space-evenly}}.vp-hero-mask{position:absolute;top:0;right:0;bottom:0;left:0;z-index:0;background-position:50%;background-size:cover}.vp-hero-mask:after{content:" ";position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:block}.vp-hero-mask.light{display:block}html[data-theme=dark] .vp-hero-mask.light,.vp-hero-mask.dark{display:none}html[data-theme=dark] .vp-hero-mask.dark{display:block}.vp-hero-infos{z-index:1;margin:0 .5rem}.vp-hero-image{display:block;max-width:100%;max-height:18rem;margin:1rem}@media (max-width: 959px){.vp-hero-image{margin:2rem auto}}@media (max-width: 719px){.vp-hero-image{max-height:16rem;margin:1.5rem auto}}@media (max-width: 419px){.vp-hero-image{max-height:14rem}}.vp-hero-image.light{display:block}html[data-theme=dark] .vp-hero-image.light,.vp-hero-image.dark{display:none}html[data-theme=dark] .vp-hero-image.dark{display:block}#main-title{margin:.5rem 0;background:linear-gradient(120deg,var(--theme-color-light),var(--theme-color) 30%,#3e71af 100%);-webkit-background-clip:text;background-clip:text;font-weight:700;font-size:3.6rem;font-family:var(--font-family);line-height:1.5;-webkit-text-fill-color:transparent}@media (max-width: 719px){#main-title{margin:0}}@media (max-width: 959px){#main-title{font-size:2.5rem;text-align:center}}@media (max-width: 719px){#main-title{font-size:2.25rem;text-align:center}}@media (max-width: 419px){#main-title{margin:0 auto;font-size:2rem}}#main-description,.vp-hero-actions{margin:1.8rem 0}@media (max-width: 719px){#main-description,.vp-hero-actions{margin:1.5rem 0}}@media (max-width: 959px){#main-description,.vp-hero-actions{margin:1.5rem auto;text-align:center}}@media (max-width: 419px){#main-description,.vp-hero-actions{margin:1.2rem 0}}#main-description{max-width:35rem;color:var(--text-color-light);font-weight:500;font-size:1.6rem;line-height:1.3}@media (max-width: 719px){#main-description{font-size:1.4rem}}@media (max-width: 419px){#main-description{font-size:1.2rem}}.vp-hero-action{display:inline-block;overflow:hidden;min-width:4rem;margin:.5rem;padding:.5em 1.5rem;border-radius:2rem;background:var(--bg-color-secondary);color:var(--text-color);font-size:1.2rem;text-align:center;transition:color var(--color-transition),color var(--color-transition),transform var(--transform-transition)}@media (max-width: 719px){.vp-hero-action{padding:.5rem 1rem;font-size:1.1rem}}@media (max-width: 419px){.vp-hero-action{font-size:1rem}}@media print{.vp-hero-action{text-decoration:none}}.vp-hero-action:hover{background:var(--bg-color-tertiary)}.vp-hero-action.primary{border-color:var(--theme-color);background:var(--theme-color);color:var(--white)}.vp-hero-action.primary:hover{border-color:var(--theme-color-light);background:var(--theme-color-light)}.vp-project-home:not(.pure) .vp-hero-action:active{transform:scale(.96)}.vp-hero-action .icon{margin-inline-end:.25em}.vp-highlight-wrapper{position:relative;display:flex;align-items:center;justify-content:center}.vp-highlight-wrapper:nth-child(odd) .vp-highlight{flex-direction:row-reverse}.vp-highlight{z-index:1;display:flex;flex:1;align-items:center;justify-content:flex-end;max-width:var(--home-page-width);margin:0 auto;padding:1.5rem 2.5rem;color:#222}@media (max-width: 719px){.vp-highlight{display:block;padding-inline:1.5rem;text-align:center}}html[data-theme=dark] .vp-highlight{color:#eee}.vp-highlight-bg{position:absolute;top:0;right:0;bottom:0;left:0;z-index:0;background-attachment:fixed;background-position:50%;background-size:cover}.vp-highlight-bg.light{display:inline-block}.vp-highlight-bg.dark,html[data-theme=dark] .vp-highlight-bg.light{display:none}html[data-theme=dark] .vp-highlight-bg.dark{display:inline-block}.vp-highlight-image{width:12rem;margin:2rem 4rem}@media (max-width: 959px){.vp-highlight-image{width:10rem}}@media (max-width: 719px){.vp-highlight-image{width:8rem;margin:0 auto}}.vp-highlight-image.light{display:inline-block}.vp-highlight-image.dark,html[data-theme=dark] .vp-highlight-image.light{display:none}html[data-theme=dark] .vp-highlight-image.dark{display:inline-block}.vp-highlight-info-wrapper{display:flex;flex:1;justify-content:center;padding:2rem}@media (max-width: 719px){.vp-highlight-info-wrapper{padding:1rem 0}}.vp-highlight-info-wrapper:only-child{flex:1 0 100%}.vp-highlight-info{text-align:start}.vp-highlight-header{margin-bottom:1.5rem;border-bottom:none;font-size:3rem;font-family:var(--font-family)}@media (max-width: 959px){.vp-highlight-header{font-size:2.5rem}}@media (max-width: 719px){.vp-highlight-header{font-size:2.25rem;text-align:center}}@media (max-width: 419px){.vp-highlight-header{font-size:2rem}}.vp-highlight-description{font-size:1.125rem}.vp-highlights{margin-inline-start:-1.25em;padding-inline-start:0}.vp-highlight-item-wrapper{padding:.5em .5em .5em 1.75em;border-radius:.5rem;list-style:none}.vp-highlight-item-wrapper.link{cursor:pointer}.vp-highlight-item-wrapper:hover{background-color:var(--bg-color-secondary);box-shadow:0 2px 12px 0 var(--card-shadow);transition:transform var(--transform-transition);transform:translate(-2px,-2px)}.vp-highlight-bg+.vp-highlight .vp-highlight-item-wrapper:hover{background-color:transparent}.vp-highlight-item-wrapper::marker{font-weight:700}.vp-highlight-item{display:list-item;color:inherit;list-style:initial}@media print{.vp-highlight-item{text-decoration:none}}.vp-highlight-title{margin:0;font-weight:600;font-size:1.125rem;font-family:var(--font-family)}.vp-highlight-title .icon{margin-inline-end:.25em;font-size:1em}.vp-highlight-title img.icon{vertical-align:-.125em;height:1em}.vp-highlight-details{margin:.5rem 0 0}.vp-project-home{--content-width: var(--home-page-width);display:block;flex:1;padding-top:var(--navbar-height)}@media screen{.vp-project-home .vp-hero-info-wrapper:not(.fullscreen) .vp-hero-info{max-width:var(--home-page-width)}}@media screen{.vp-project-home .vp-feature{max-width:var(--home-page-width)}}.vp-project-home .theme-hope-content{padding-bottom:1.5rem!important}.vp-project-home .theme-hope-content:empty{padding:0!important}.not-found-hint{padding:2rem}.not-found-hint .error-code{margin:0;font-weight:700;font-size:4rem;line-height:4rem}.not-found-hint .error-title{font-weight:700}.not-found-hint .error-hint{margin:0;padding:12px 0;font-weight:600;font-size:20px;line-height:20px;letter-spacing:2px}.vp-page.not-found{display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:border-box;width:100vw;max-width:var(--home-page-width);margin:0 auto;padding:calc(var(--navbar-height) + 1rem) 1rem 1rem!important;text-align:center}.vp-page.not-found .action-button{display:inline-block;box-sizing:border-box;margin:.25rem;padding:.75rem 1rem;border-width:0;border-bottom:1px solid var(--theme-color-dark);border-radius:3rem;background:var(--theme-color);color:var(--white);outline:none;font-size:1rem;transition:background var(--color-transition)}.vp-page.not-found .action-button:hover{background:var(--theme-color-light);cursor:pointer}.vp-page-nav{display:flex;flex-wrap:wrap;max-width:var(--content-width, 740px);min-height:2rem;margin-inline:auto;margin-top:0;padding-block:.5rem;padding-inline:2rem;border-top:1px solid var(--border-color)}@media (max-width: 959px){.vp-page-nav{padding-inline:1rem}}@media print{.vp-page-nav{display:none}}.vp-page-nav .nav-link{display:inline-block;flex-grow:1;margin:.25rem;padding:.25rem .5rem;border:1px solid var(--border-color);border-radius:.25rem}.vp-page-nav .nav-link:hover{background:var(--bg-color-secondary)}.vp-page-nav .nav-link .hint{color:var(--grey-light);font-size:.875rem;line-height:2}.vp-page-nav .nav-link .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s;font-size:.75rem}html[data-theme=dark] .vp-page-nav .nav-link .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.vp-page-nav .nav-link .arrow.down{transform:rotate(180deg)}html[dir=rtl] .vp-page-nav .nav-link .arrow.down{transform:rotate(-180deg)}.vp-page-nav .nav-link .arrow.end{transform:rotate(90deg)}html[dir=rtl] .vp-page-nav .nav-link .arrow.end,.vp-page-nav .nav-link .arrow.start{transform:rotate(-90deg)}html[dir=rtl] .vp-page-nav .nav-link .arrow.start{transform:rotate(90deg)}.vp-page-nav .prev{text-align:start}.vp-page-nav .prev .icon{margin-inline-end:.25em;font-size:1em}.vp-page-nav .prev img.icon{vertical-align:-.125em;height:1em}.vp-page-nav .next{text-align:end}.vp-page-nav .next .icon{margin-inline-start:.25em;font-size:1em}.vp-page-nav .next img.icon{vertical-align:-.125em;height:1em}.vp-page-title{max-width:var(--content-width, 740px);margin-inline:auto;padding-inline:2.5rem;position:relative;z-index:1;padding-top:1rem;padding-bottom:0}@media (max-width: 959px){.vp-page-title{padding-inline:1.5rem}}@media print{.vp-page-title{max-width:unset}}@media print{.vp-page-title{padding-inline:0!important}}@media (max-width: 959px){.vp-page-title{padding-top:.5rem}}.vp-page-title h1{margin-top:calc(0px - var(--navbar-height))!important;margin-bottom:1rem;padding-top:var(--navbar-height)!important;font-size:2.2rem}@media (max-width: 959px){.vp-page-title h1{margin-bottom:.5rem}}.vp-page-title h1 .icon{margin-inline-end:.25em;color:var(--theme-color);font-size:.9em}.vp-page-title h1 img.icon{vertical-align:-.125em;height:1em}.theme-hope-content:not(.custom){padding-top:0!important}.theme-hope-content:not(.custom) h1:first-child,.theme-hope-content:not(.custom) h2:first-child,.theme-hope-content:not(.custom) h3:first-child,.theme-hope-content:not(.custom) h4:first-child,.theme-hope-content:not(.custom) h5:first-child,.theme-hope-content:not(.custom) h6:first-child{margin-top:calc(.5rem - var(--navbar-height))!important;padding-top:var(--navbar-height)!important}.theme-hope-content:not(.custom)>h1:first-child{display:none}.vp-page{display:block;flex-grow:1;padding-bottom:2rem;transition:padding var(--transform-transition)}@media print{.vp-page{min-height:auto!important;margin:0!important;padding:0!important}}.page-cover{width:var(--content-width);margin-inline:auto}@media (max-width: 719px){.page-cover{width:100%}}.page-cover img{-o-object-fit:cover;object-fit:cover;width:100%;max-height:25vh;border-radius:.5rem}@media (max-width: 719px){.page-cover img{border-radius:0}}#vp-comment{max-width:var(--content-width, 740px);margin:0 auto;padding:2rem 2.5rem}@media (max-width: 959px){#vp-comment{padding:1.5rem}}@media (max-width: 419px){#vp-comment{padding:1rem 1.5rem}}@media print{#vp-comment{max-width:unset}}.vp-skip-link{top:.25rem;inset-inline-start:.25rem;z-index:999;padding:.65rem 1.5rem;border-radius:.5rem;background:var(--bg-color);color:var(--theme-color);box-shadow:var(--card-shadow);font-weight:700;font-size:.9em;text-decoration:none}@media print{.vp-skip-link{display:none}}.vp-skip-link:focus{clip:auto;width:auto;height:auto;clip-path:none}.theme-hope-content pre{overflow:auto;margin:.85rem 0;padding:1rem;border-radius:6px;line-height:1.375}.theme-hope-content pre code{padding:0;border-radius:0;background:transparent!important;color:var(--code-color);font-family:var(--font-family-mono);text-align:left;white-space:pre;word-spacing:normal;word-wrap:normal;word-break:normal;overflow-wrap:unset;-webkit-hyphens:none;hyphens:none;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media print{.theme-hope-content pre code{white-space:pre-wrap}}.theme-hope-content .line-number{font-family:var(--font-family-mono)}div[class*=language-]{position:relative;border-radius:6px;background:var(--code-bg-color);font-size:16px}@media (max-width: 419px){.theme-hope-content>div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}div[class*=language-]:before{content:attr(data-title);position:absolute;top:0;right:1em;z-index:3;color:var(--code-line-color);font-size:.75rem}div[class*=language-] pre{position:relative;z-index:1;scrollbar-gutter:stable}div[class*=language-] .highlight-lines{position:absolute;top:0;bottom:0;left:0;width:100%;padding:1rem 0;line-height:1.375;-webkit-user-select:none;-moz-user-select:none;user-select:none}div[class*=language-] .highlight-line{background:var(--code-highlight-line-color)}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;top:0;bottom:0;left:0;z-index:2;width:var(--line-numbers-width);border-right:1px solid var(--code-highlight-line-color);border-radius:6px 0 0 6px}@media (max-width: 419px){div[class*=language-].line-numbers-mode:after{border-radius:0}}@media print{div[class*=language-].line-numbers-mode:after{display:none}}div[class*=language-].line-numbers-mode .highlight-line{position:relative}div[class*=language-].line-numbers-mode .highlight-line:before{content:" ";position:absolute;top:0;left:0;z-index:3;display:block;width:var(--line-numbers-width);height:100%}div[class*=language-].line-numbers-mode pre{vertical-align:middle;margin-left:var(--line-numbers-width);padding-left:.5rem}@media print{div[class*=language-].line-numbers-mode pre{margin-left:0;padding-left:1rem}}div[class*=language-].line-numbers-mode .line-numbers{position:absolute;top:0;bottom:0;left:0;display:flex;flex-direction:column;width:var(--line-numbers-width);padding:1rem 0;color:var(--code-line-color);counter-reset:line-number;text-align:center}@media print{div[class*=language-].line-numbers-mode .line-numbers{display:none}}div[class*=language-].line-numbers-mode .line-number{position:relative;z-index:4;display:flex;flex:1;align-items:center;justify-content:center;-webkit-user-select:none;-moz-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-number:before{content:counter(line-number);display:block;font-size:.8em;line-height:1;counter-increment:line-number}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}html[data-theme=light] #app{--code-color: #383a42;--code-line-color: rgba(56, 58, 66, .67);--code-bg-color: #ecf4fa;--code-border-color: #c3def3;--code-highlight-line-color: #d8e9f6}html[data-theme=light] code[class*=language-],html[data-theme=light] pre[class*=language-]{-moz-tab-size:2;-o-tab-size:2;tab-size:2}html[data-theme=light] code[class*=language-]::-moz-selection,html[data-theme=light] code[class*=language-] ::-moz-selection,html[data-theme=light] pre[class*=language-]::-moz-selection,html[data-theme=light] pre[class*=language-] ::-moz-selection{background:#e5e5e6;color:inherit}html[data-theme=light] code[class*=language-]::selection,html[data-theme=light] code[class*=language-] ::selection,html[data-theme=light] pre[class*=language-]::selection,html[data-theme=light] pre[class*=language-] ::selection{background:#e5e5e6;color:inherit}html[data-theme=light] .token.comment,html[data-theme=light] .token.prolog,html[data-theme=light] .token.cdata{color:#a0a1a7}html[data-theme=light] .token.doctype,html[data-theme=light] .token.punctuation,html[data-theme=light] .token.entity{color:#383a42}html[data-theme=light] .token.attr-name,html[data-theme=light] .token.class-name,html[data-theme=light] .token.boolean,html[data-theme=light] .token.constant,html[data-theme=light] .token.number,html[data-theme=light] .token.atrule{color:#b76b01}html[data-theme=light] .token.keyword{color:#a626a4}html[data-theme=light] .token.property,html[data-theme=light] .token.tag,html[data-theme=light] .token.symbol,html[data-theme=light] .token.deleted,html[data-theme=light] .token.important{color:#e45649}html[data-theme=light] .token.selector,html[data-theme=light] .token.string,html[data-theme=light] .token.char,html[data-theme=light] .token.builtin,html[data-theme=light] .token.inserted,html[data-theme=light] .token.regex,html[data-theme=light] .token.attr-value,html[data-theme=light] .token.attr-value>.token.punctuation{color:#50a14f}html[data-theme=light] .token.variable,html[data-theme=light] .token.operator,html[data-theme=light] .token.function{color:#4078f2}html[data-theme=light] .token.url{color:#0184bc}html[data-theme=light] .token.attr-value>.token.punctuation.attr-equals,html[data-theme=light] .token.special-attr>.token.attr-value>.token.value.css{color:#383a42}html[data-theme=light] .language-css .token.selector{color:#e45649}html[data-theme=light] .language-css .token.property{color:#383a42}html[data-theme=light] .language-css .token.function,html[data-theme=light] .language-css .token.url>.token.function{color:#0184bc}html[data-theme=light] .language-css .token.url>.token.string.url{color:#50a14f}html[data-theme=light] .language-css .token.important,html[data-theme=light] .language-css .token.atrule .token.rule,html[data-theme=light] .language-javascript .token.operator{color:#a626a4}html[data-theme=light] .language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#ca1243}html[data-theme=light] .language-json .token.operator{color:#383a42}html[data-theme=light] .language-json .token.null.keyword{color:#b76b01}html[data-theme=light] .language-markdown .token.url,html[data-theme=light] .language-markdown .token.url>.token.operator,html[data-theme=light] .language-markdown .token.url-reference.url>.token.string{color:#383a42}html[data-theme=light] .language-markdown .token.url>.token.content{color:#4078f2}html[data-theme=light] .language-markdown .token.url>.token.url,html[data-theme=light] .language-markdown .token.url-reference.url{color:#0184bc}html[data-theme=light] .language-markdown .token.blockquote.punctuation,html[data-theme=light] .language-markdown .token.hr.punctuation{color:#a0a1a7;font-style:italic}html[data-theme=light] .language-markdown .token.code-snippet{color:#50a14f}html[data-theme=light] .language-markdown .token.bold .token.content{color:#b76b01}html[data-theme=light] .language-markdown .token.italic .token.content{color:#a626a4}html[data-theme=light] .language-markdown .token.strike .token.content,html[data-theme=light] .language-markdown .token.strike .token.punctuation,html[data-theme=light] .language-markdown .token.list.punctuation,html[data-theme=light] .language-markdown .token.title.important>.token.punctuation{color:#e45649}html[data-theme=light] .token.bold{font-weight:700}html[data-theme=light] .token.comment,html[data-theme=light] .token.italic{font-style:italic}html[data-theme=light] .token.entity{cursor:help}html[data-theme=light] .token.namespace{opacity:.8}html[data-theme=dark] #app{--code-color: #abb2bf;--code-line-color: rgba(171, 178, 191, .67);--code-bg-color: #282c34;--code-border-color: #343e51;--code-highlight-line-color: #2f3542}html[data-theme=dark] code[class*=language-],html[data-theme=dark] pre[class*=language-]{text-shadow:0 1px rgba(0,0,0,.3);-moz-tab-size:2;-o-tab-size:2;tab-size:2}@media print{html[data-theme=dark] code[class*=language-],html[data-theme=dark] pre[class*=language-]{text-shadow:none}}html[data-theme=dark] code[class*=language-]::-moz-selection,html[data-theme=dark] code[class*=language-] ::-moz-selection,html[data-theme=dark] pre[class*=language-]::-moz-selection,html[data-theme=dark] pre[class*=language-] ::-moz-selection{background:#3e4451;color:inherit;text-shadow:none}html[data-theme=dark] code[class*=language-]::selection,html[data-theme=dark] code[class*=language-] ::selection,html[data-theme=dark] pre[class*=language-]::selection,html[data-theme=dark] pre[class*=language-] ::selection{background:#3e4451;color:inherit;text-shadow:none}html[data-theme=dark] .token.comment,html[data-theme=dark] .token.prolog,html[data-theme=dark] .token.cdata{color:#5c6370}html[data-theme=dark] .token.doctype,html[data-theme=dark] .token.punctuation,html[data-theme=dark] .token.entity{color:#abb2bf}html[data-theme=dark] .token.attr-name,html[data-theme=dark] .token.class-name,html[data-theme=dark] .token.boolean,html[data-theme=dark] .token.constant,html[data-theme=dark] .token.number,html[data-theme=dark] .token.atrule{color:#d19a66}html[data-theme=dark] .token.keyword{color:#c678dd}html[data-theme=dark] .token.property,html[data-theme=dark] .token.tag,html[data-theme=dark] .token.symbol,html[data-theme=dark] .token.deleted,html[data-theme=dark] .token.important{color:#e06c75}html[data-theme=dark] .token.selector,html[data-theme=dark] .token.string,html[data-theme=dark] .token.char,html[data-theme=dark] .token.builtin,html[data-theme=dark] .token.inserted,html[data-theme=dark] .token.regex,html[data-theme=dark] .token.attr-value,html[data-theme=dark] .token.attr-value>.token.punctuation{color:#98c379}html[data-theme=dark] .token.variable,html[data-theme=dark] .token.operator,html[data-theme=dark] .token.function{color:#61afef}html[data-theme=dark] .token.url{color:#56b6c2}html[data-theme=dark] .token.attr-value>.token.punctuation.attr-equals,html[data-theme=dark] .token.special-attr>.token.attr-value>.token.value.css{color:#abb2bf}html[data-theme=dark] .language-css .token.selector{color:#e06c75}html[data-theme=dark] .language-css .token.property{color:#abb2bf}html[data-theme=dark] .language-css .token.function,html[data-theme=dark] .language-css .token.url>.token.function{color:#56b6c2}html[data-theme=dark] .language-css .token.url>.token.string.url{color:#98c379}html[data-theme=dark] .language-css .token.important,html[data-theme=dark] .language-css .token.atrule .token.rule,html[data-theme=dark] .language-javascript .token.operator{color:#c678dd}html[data-theme=dark] .language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#be5046}html[data-theme=dark] .language-json .token.operator{color:#abb2bf}html[data-theme=dark] .language-json .token.null.keyword{color:#d19a66}html[data-theme=dark] .language-markdown .token.url,html[data-theme=dark] .language-markdown .token.url>.token.operator,html[data-theme=dark] .language-markdown .token.url-reference.url>.token.string{color:#abb2bf}html[data-theme=dark] .language-markdown .token.url>.token.content{color:#61afef}html[data-theme=dark] .language-markdown .token.url>.token.url,html[data-theme=dark] .language-markdown .token.url-reference.url{color:#56b6c2}html[data-theme=dark] .language-markdown .token.blockquote.punctuation,html[data-theme=dark] .language-markdown .token.hr.punctuation{color:#5c6370;font-style:italic}html[data-theme=dark] .language-markdown .token.code-snippet{color:#98c379}html[data-theme=dark] .language-markdown .token.bold .token.content{color:#d19a66}html[data-theme=dark] .language-markdown .token.italic .token.content{color:#c678dd}html[data-theme=dark] .language-markdown .token.strike .token.content,html[data-theme=dark] .language-markdown .token.strike .token.punctuation,html[data-theme=dark] .language-markdown .token.list.punctuation,html[data-theme=dark] .language-markdown .token.title.important>.token.punctuation{color:#e06c75}html[data-theme=dark] .token.bold{font-weight:700}html[data-theme=dark] .token.comment,html[data-theme=dark] .token.italic{font-style:italic}html[data-theme=dark] .token.entity{cursor:help}html[data-theme=dark] .token.namespace{opacity:.8}.sr-only{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px;padding:0;border-width:0;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media print{.theme-hope-content{margin:0!important;padding-inline:0!important}}.theme-hope-content.custom{margin:0;padding:0}.theme-hope-content:not(.custom){max-width:var(--content-width, 740px);margin:0 auto;padding:2rem 2.5rem;padding-top:0}@media (max-width: 959px){.theme-hope-content:not(.custom){padding:1.5rem}}@media (max-width: 419px){.theme-hope-content:not(.custom){padding:1rem 1.5rem}}@media print{.theme-hope-content:not(.custom){max-width:unset}}.theme-hope-content:not(.custom)>h1,.theme-hope-content:not(.custom)>h2,.theme-hope-content:not(.custom)>h3,.theme-hope-content:not(.custom)>h4,.theme-hope-content:not(.custom)>h5,.theme-hope-content:not(.custom)>h6{margin-top:calc(.5rem - var(--navbar-height));margin-bottom:.5rem;padding-top:calc(1rem + var(--navbar-height));outline:none}.theme-container.no-navbar .theme-hope-content:not(.custom)>h1,.theme-container.no-navbar .theme-hope-content:not(.custom)>h2,.theme-container.no-navbar .theme-hope-content:not(.custom)>h3,.theme-container.no-navbar .theme-hope-content:not(.custom)>h4,.theme-container.no-navbar .theme-hope-content:not(.custom)>h5,.theme-container.no-navbar .theme-hope-content:not(.custom)>h6{margin-top:1.5rem;padding-top:0}.theme-hope-content:not(.custom)>p,.theme-hope-content:not(.custom)>ul p,.theme-hope-content:not(.custom)>ol p{text-align:justify;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto}@media (max-width: 419px){.theme-hope-content:not(.custom)>p,.theme-hope-content:not(.custom)>ul p,.theme-hope-content:not(.custom)>ol p{text-align:start}}@media print{.theme-hope-content:not(.custom)>p,.theme-hope-content:not(.custom)>ul p,.theme-hope-content:not(.custom)>ol p{text-align:start}}.theme-hope-content a:not(.header-anchor):hover{text-decoration:underline}.theme-hope-content img{max-width:100%}::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}html[data-theme=light]::view-transition-old(root),html[data-theme=dark]::view-transition-new(root){z-index:1}html[data-theme=light]::view-transition-new(root),html[data-theme=dark]::view-transition-old(root){z-index:99999}@media (min-width: 1280px){.chart-wrapper::-webkit-scrollbar,.flowchart-wrapper::-webkit-scrollbar,.mermaid-wrapper::-webkit-scrollbar{width:8px;height:8px}.chart-wrapper::-webkit-scrollbar-track-piece,.flowchart-wrapper::-webkit-scrollbar-track-piece,.mermaid-wrapper::-webkit-scrollbar-track-piece{border-radius:8px;background:#0000001a}}html[dir=rtl] a.header-anchor:before{right:-.75em}#docsearch-container{min-width:145.7px!important}@media (max-width: 959px){#docsearch-container{min-width:36px!important}}.DocSearch.DocSearch-Button{margin-left:0}@media (max-width: 959px){.DocSearch.DocSearch-Button{min-width:36px!important}}.DocSearch .DocSearch-Button-Placeholder{display:inline-block;padding:4px 12px 4px 6px;font-size:14px}@media (max-width: 719px){.DocSearch .DocSearch-Button-Placeholder{display:none}}.DocSearch .DocSearch-Search-Icon{width:1.25em;height:1.25em}@media (max-width: 959px){.DocSearch .DocSearch-Button-Keys{display:none}}.DocSearch .DocSearch-Button-Key{background:var(--bg-color);box-shadow:none}:root{scrollbar-width:thin}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track-piece{border-radius:6px;background:#0000001a}::-webkit-scrollbar-thumb{border-radius:6px;background:var(--theme-color)}::-webkit-scrollbar-thumb:active{background:var(--theme-color-light)}@media (max-width: 719px){.hide-in-mobile{display:none!important}}@media (max-width: 959px){.hide-in-pad{display:none!important}}.page-author-item{display:inline-block;margin:0 4px;font-weight:400;overflow-wrap:break-word}.page-category-info{flex-wrap:wrap}.page-category-item{display:inline-block;margin:.125em .25em;padding:0 .25em;border-radius:.25em;background:var(--bg-color-secondary);color:var(--text-color-light);font-weight:700;font-size:.75rem;line-height:2;transition:background var(--color-transition),color var(--color-transition)}@media print{.page-category-item{padding:0;font-weight:400}.page-category-item:after{content:", "}.page-category-item:last-of-type:after{content:""}}.page-category-item.clickable>span:hover{color:var(--theme-color);cursor:pointer}.page-category-item.category0{background:#fde5e7;color:#ec2f3e}html[data-theme=dark] .page-category-item.category0{background:#340509;color:#ba111f}.page-category-item.category0:hover{background:#f9bec3}html[data-theme=dark] .page-category-item.category0:hover{background:#53080e}.page-category-item.category1{background:#ffeee8;color:#fb7649}html[data-theme=dark] .page-category-item.category1{background:#441201;color:#f54205}.page-category-item.category1:hover{background:#fed4c6}html[data-theme=dark] .page-category-item.category1:hover{background:#6d1d02}.page-category-item.category2{background:#fef5e7;color:#f5b041}html[data-theme=dark] .page-category-item.category2{background:#3e2703;color:#e08e0b}.page-category-item.category2:hover{background:#fce6c4}html[data-theme=dark] .page-category-item.category2:hover{background:#633f05}.page-category-item.category3{background:#eafaf1;color:#55d98d}html[data-theme=dark] .page-category-item.category3{background:#0c331c;color:#29b866}.page-category-item.category3:hover{background:#caf3db}html[data-theme=dark] .page-category-item.category3:hover{background:#12522d}.page-category-item.category4{background:#e6f9ee;color:#36d278}html[data-theme=dark] .page-category-item.category4{background:#092917;color:#219552}.page-category-item.category4:hover{background:#c0f1d5}html[data-theme=dark] .page-category-item.category4:hover{background:#0f4224}.page-category-item.category5{background:#e1fcfc;color:#16e1e1}html[data-theme=dark] .page-category-item.category5{background:#042929;color:#0e9595}.page-category-item.category5:hover{background:#b4f8f8}html[data-theme=dark] .page-category-item.category5:hover{background:#064242}.page-category-item.category6{background:#e4f0fe;color:#2589f6}html[data-theme=dark] .page-category-item.category6{background:#021b36;color:#0862c3}.page-category-item.category6:hover{background:#bbdafc}html[data-theme=dark] .page-category-item.category6:hover{background:#042c57}.page-category-item.category7{background:#f7f1fd;color:#bb8ced}html[data-theme=dark] .page-category-item.category7{background:#2a0b4b;color:#9851e4}.page-category-item.category7:hover{background:#eadbfa}html[data-theme=dark] .page-category-item.category7:hover{background:#431277}.page-category-item.category8{background:#fdeaf5;color:#ef59ab}html[data-theme=dark] .page-category-item.category8{background:#400626;color:#e81689}.page-category-item.category8:hover{background:#facbe5}html[data-theme=dark] .page-category-item.category8:hover{background:#670a3d}.page-original-info{position:relative;display:inline-block;vertical-align:middle;overflow:hidden;padding:0 .5em;border:.5px solid var(--grey-dark);border-radius:.75em;background:var(--bg-color);font-size:.75em;line-height:1.5!important}.page-info{display:flex;flex-wrap:wrap;align-items:center;place-content:stretch flex-start;color:var(--grey-dark);font-size:14px}@media print{.page-info{display:flex!important}}.page-info>span{display:flex;align-items:center;max-width:100%;margin-inline-end:.5em;line-height:2}@media (min-width: 1440px){.page-info>span{font-size:1.1em}}@media (max-width: 419px){.page-info>span{margin-inline-end:.3em;font-size:.875em}}@media print{.page-info>span{display:flex!important}}.page-info .icon{position:relative;display:inline-block;vertical-align:middle;width:1em;height:1em;margin-inline-end:.25em}.page-info a{color:inherit}.page-info a:hover,.page-info a:active{color:var(--theme-color)}.vp-page-meta{max-width:var(--content-width, 740px);margin-inline:auto;padding-inline:2.5rem;display:flex;flex-wrap:wrap;justify-content:space-between;overflow:auto;padding-top:.75rem;padding-bottom:.75rem}@media (max-width: 959px){.vp-page-meta{padding-inline:1.5rem}}@media print{.vp-page-meta{max-width:unset}}@media print{.vp-page-meta{margin:0!important;padding-inline:0!important}}@media (max-width: 719px){.vp-page-meta{display:block}}.vp-page-meta .vp-meta-item{flex-grow:1}.vp-page-meta .vp-meta-item .vp-meta-label{font-weight:500}.vp-page-meta .vp-meta-item .vp-meta-label:not(a){color:var(--text-color-lighter)}.vp-page-meta .vp-meta-item .vp-meta-info{color:var(--grey-dark);font-weight:400}.vp-page-meta .git-info{text-align:end}.vp-page-meta .edit-link{margin-top:.25rem;margin-bottom:.25rem;margin-inline-end:.5rem;font-size:14px}@media print{.vp-page-meta .edit-link{display:none}}.vp-page-meta .edit-link .icon{position:relative;bottom:-.125em;width:1em;height:1em;margin-inline-end:.25em}.vp-page-meta .update-time,.vp-page-meta .contributors{margin-top:.25rem;margin-bottom:.25rem;font-size:14px}@media (max-width: 719px){.vp-page-meta .update-time,.vp-page-meta .contributors{font-size:13px;text-align:start}}.print-button{border-width:0;background:transparent;cursor:pointer;box-sizing:content-box;width:1rem;height:1rem;padding:.5rem;border-radius:.25em;color:inherit;font-size:1rem;transform:translateY(.25rem)}@media print{.print-button{display:none}}.page-tag-info{flex-wrap:wrap}.page-tag-item{position:relative;display:inline-block;vertical-align:middle;overflow:hidden;min-width:1.5rem;margin:.125rem;padding:.125rem .25rem .125rem .625rem;background:var(--bg-color-secondary);background:linear-gradient(135deg,transparent .75em,var(--bg-color-secondary) 0) top,linear-gradient(45deg,transparent .75em,var(--bg-color-secondary) 0) bottom;background-size:100% 52%!important;background-repeat:no-repeat!important;color:var(--text-color-light);font-weight:700;font-size:.625rem;line-height:1.5;text-align:center;transition:background var(--color-transition),color var(--color-transition)}@media print{.page-tag-item{padding:0;font-weight:400}.page-tag-item:after{content:", "}.page-tag-item:last-of-type:after{content:""}}.page-tag-item.clickable:hover{cursor:pointer}.page-tag-item.tag0{background:#fde5e7;background:linear-gradient(135deg,transparent .75em,#fde5e7 0) top,linear-gradient(45deg,transparent .75em,#fde5e7 0) bottom;color:#ec2f3e}html[data-theme=dark] .page-tag-item.tag0{background:#340509;background:linear-gradient(135deg,transparent .75em,#340509 0) top,linear-gradient(45deg,transparent .75em,#340509 0) bottom;color:#ba111f}.page-tag-item.tag0.clickable:hover{background:#f9bec3;background:linear-gradient(135deg,transparent .75em,#f9bec3 0) top,linear-gradient(45deg,transparent .75em,#f9bec3 0) bottom}html[data-theme=dark] .page-tag-item.tag0.clickable:hover{background:#53080e;background:linear-gradient(135deg,transparent .75em,#53080e 0) top,linear-gradient(45deg,transparent .75em,#53080e 0) bottom}.page-tag-item.tag1{background:#ffeee8;background:linear-gradient(135deg,transparent .75em,#ffeee8 0) top,linear-gradient(45deg,transparent .75em,#ffeee8 0) bottom;color:#fb7649}html[data-theme=dark] .page-tag-item.tag1{background:#441201;background:linear-gradient(135deg,transparent .75em,#441201 0) top,linear-gradient(45deg,transparent .75em,#441201 0) bottom;color:#f54205}.page-tag-item.tag1.clickable:hover{background:#fed4c6;background:linear-gradient(135deg,transparent .75em,#fed4c6 0) top,linear-gradient(45deg,transparent .75em,#fed4c6 0) bottom}html[data-theme=dark] .page-tag-item.tag1.clickable:hover{background:#6d1d02;background:linear-gradient(135deg,transparent .75em,#6d1d02 0) top,linear-gradient(45deg,transparent .75em,#6d1d02 0) bottom}.page-tag-item.tag2{background:#fef5e7;background:linear-gradient(135deg,transparent .75em,#fef5e7 0) top,linear-gradient(45deg,transparent .75em,#fef5e7 0) bottom;color:#f5b041}html[data-theme=dark] .page-tag-item.tag2{background:#3e2703;background:linear-gradient(135deg,transparent .75em,#3e2703 0) top,linear-gradient(45deg,transparent .75em,#3e2703 0) bottom;color:#e08e0b}.page-tag-item.tag2.clickable:hover{background:#fce6c4;background:linear-gradient(135deg,transparent .75em,#fce6c4 0) top,linear-gradient(45deg,transparent .75em,#fce6c4 0) bottom}html[data-theme=dark] .page-tag-item.tag2.clickable:hover{background:#633f05;background:linear-gradient(135deg,transparent .75em,#633f05 0) top,linear-gradient(45deg,transparent .75em,#633f05 0) bottom}.page-tag-item.tag3{background:#eafaf1;background:linear-gradient(135deg,transparent .75em,#eafaf1 0) top,linear-gradient(45deg,transparent .75em,#eafaf1 0) bottom;color:#55d98d}html[data-theme=dark] .page-tag-item.tag3{background:#0c331c;background:linear-gradient(135deg,transparent .75em,#0c331c 0) top,linear-gradient(45deg,transparent .75em,#0c331c 0) bottom;color:#29b866}.page-tag-item.tag3.clickable:hover{background:#caf3db;background:linear-gradient(135deg,transparent .75em,#caf3db 0) top,linear-gradient(45deg,transparent .75em,#caf3db 0) bottom}html[data-theme=dark] .page-tag-item.tag3.clickable:hover{background:#12522d;background:linear-gradient(135deg,transparent .75em,#12522d 0) top,linear-gradient(45deg,transparent .75em,#12522d 0) bottom}.page-tag-item.tag4{background:#e6f9ee;background:linear-gradient(135deg,transparent .75em,#e6f9ee 0) top,linear-gradient(45deg,transparent .75em,#e6f9ee 0) bottom;color:#36d278}html[data-theme=dark] .page-tag-item.tag4{background:#092917;background:linear-gradient(135deg,transparent .75em,#092917 0) top,linear-gradient(45deg,transparent .75em,#092917 0) bottom;color:#219552}.page-tag-item.tag4.clickable:hover{background:#c0f1d5;background:linear-gradient(135deg,transparent .75em,#c0f1d5 0) top,linear-gradient(45deg,transparent .75em,#c0f1d5 0) bottom}html[data-theme=dark] .page-tag-item.tag4.clickable:hover{background:#0f4224;background:linear-gradient(135deg,transparent .75em,#0f4224 0) top,linear-gradient(45deg,transparent .75em,#0f4224 0) bottom}.page-tag-item.tag5{background:#e1fcfc;background:linear-gradient(135deg,transparent .75em,#e1fcfc 0) top,linear-gradient(45deg,transparent .75em,#e1fcfc 0) bottom;color:#16e1e1}html[data-theme=dark] .page-tag-item.tag5{background:#042929;background:linear-gradient(135deg,transparent .75em,#042929 0) top,linear-gradient(45deg,transparent .75em,#042929 0) bottom;color:#0e9595}.page-tag-item.tag5.clickable:hover{background:#b4f8f8;background:linear-gradient(135deg,transparent .75em,#b4f8f8 0) top,linear-gradient(45deg,transparent .75em,#b4f8f8 0) bottom}html[data-theme=dark] .page-tag-item.tag5.clickable:hover{background:#064242;background:linear-gradient(135deg,transparent .75em,#064242 0) top,linear-gradient(45deg,transparent .75em,#064242 0) bottom}.page-tag-item.tag6{background:#e4f0fe;background:linear-gradient(135deg,transparent .75em,#e4f0fe 0) top,linear-gradient(45deg,transparent .75em,#e4f0fe 0) bottom;color:#2589f6}html[data-theme=dark] .page-tag-item.tag6{background:#021b36;background:linear-gradient(135deg,transparent .75em,#021b36 0) top,linear-gradient(45deg,transparent .75em,#021b36 0) bottom;color:#0862c3}.page-tag-item.tag6.clickable:hover{background:#bbdafc;background:linear-gradient(135deg,transparent .75em,#bbdafc 0) top,linear-gradient(45deg,transparent .75em,#bbdafc 0) bottom}html[data-theme=dark] .page-tag-item.tag6.clickable:hover{background:#042c57;background:linear-gradient(135deg,transparent .75em,#042c57 0) top,linear-gradient(45deg,transparent .75em,#042c57 0) bottom}.page-tag-item.tag7{background:#f7f1fd;background:linear-gradient(135deg,transparent .75em,#f7f1fd 0) top,linear-gradient(45deg,transparent .75em,#f7f1fd 0) bottom;color:#bb8ced}html[data-theme=dark] .page-tag-item.tag7{background:#2a0b4b;background:linear-gradient(135deg,transparent .75em,#2a0b4b 0) top,linear-gradient(45deg,transparent .75em,#2a0b4b 0) bottom;color:#9851e4}.page-tag-item.tag7.clickable:hover{background:#eadbfa;background:linear-gradient(135deg,transparent .75em,#eadbfa 0) top,linear-gradient(45deg,transparent .75em,#eadbfa 0) bottom}html[data-theme=dark] .page-tag-item.tag7.clickable:hover{background:#431277;background:linear-gradient(135deg,transparent .75em,#431277 0) top,linear-gradient(45deg,transparent .75em,#431277 0) bottom}.page-tag-item.tag8{background:#fdeaf5;background:linear-gradient(135deg,transparent .75em,#fdeaf5 0) top,linear-gradient(45deg,transparent .75em,#fdeaf5 0) bottom;color:#ef59ab}html[data-theme=dark] .page-tag-item.tag8{background:#400626;background:linear-gradient(135deg,transparent .75em,#400626 0) top,linear-gradient(45deg,transparent .75em,#400626 0) bottom;color:#e81689}.page-tag-item.tag8.clickable:hover{background:#facbe5;background:linear-gradient(135deg,transparent .75em,#facbe5 0) top,linear-gradient(45deg,transparent .75em,#facbe5 0) bottom}html[data-theme=dark] .page-tag-item.tag8.clickable:hover{background:#670a3d;background:linear-gradient(135deg,transparent .75em,#670a3d 0) top,linear-gradient(45deg,transparent .75em,#670a3d 0) bottom}.vp-toc-placeholder{margin-inline:auto;padding-inline:2.5rem;position:sticky;top:calc(var(--navbar-height) + .5rem);z-index:99;display:none;max-width:var(--content-width, 740px)}@media (max-width: 959px){.vp-toc-placeholder{padding-inline:1.5rem}}@media print{.vp-toc-placeholder{max-width:unset}}@media (max-width: 719px){.hide-navbar .vp-toc-placeholder{top:.5rem}}@media (min-width: 1440px){.vp-toc-placeholder{top:calc(var(--navbar-height) + 2rem)}}@media print{.vp-toc-placeholder{display:none!important}}.vp-toc-placeholder+.theme-hope-content:not(.custom){padding-top:0}.has-toc .vp-toc-placeholder{display:block}#toc{margin-bottom:1rem;border-radius:8px;background:var(--bg-color-secondary)}@media (min-width: 1440px){#toc{position:absolute;inset-inline-start:calc(100% + 1rem);min-width:10rem;max-width:15rem;margin-bottom:0;border-radius:0;background:transparent}}.vp-toc-header{padding:.5rem 1rem;font-weight:600}@media (min-width: 1440px){.vp-toc-header{padding-top:0;font-size:.875rem}}.vp-toc-header .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s}html[data-theme=dark] .vp-toc-header .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.vp-toc-header .arrow.down{transform:rotate(180deg)}html[dir=rtl] .vp-toc-header .arrow.down{transform:rotate(-180deg)}.vp-toc-header .arrow.end{transform:rotate(90deg)}html[dir=rtl] .vp-toc-header .arrow.end,.vp-toc-header .arrow.start{transform:rotate(-90deg)}html[dir=rtl] .vp-toc-header .arrow.start{transform:rotate(90deg)}@media (min-width: 1440px){.vp-toc-header .arrow{display:none}}.vp-toc-header .print-button{display:none}@media (min-width: 1440px){.vp-toc-header .print-button{display:inline-block}}.vp-toc-wrapper{position:relative;overflow:hidden auto;height:0;max-height:8rem;margin:0 .5rem;padding-inline-start:8px;text-overflow:ellipsis;white-space:nowrap;transition:height .5s;scroll-behavior:smooth}@media (min-width: 1440px){.vp-toc-wrapper{height:auto;max-height:75vh}}.vp-toc-wrapper.open{height:auto;margin-top:.5rem;padding-bottom:.5rem}.vp-toc-wrapper::-webkit-scrollbar-track-piece{background:transparent}.vp-toc-wrapper::-webkit-scrollbar{width:3px}.vp-toc-wrapper::-webkit-scrollbar-thumb:vertical{background:#ddd}html[data-theme=dark] .vp-toc-wrapper::-webkit-scrollbar-thumb:vertical{background:#333}.vp-toc-wrapper:before{content:" ";position:absolute;top:0;bottom:0;inset-inline-start:0;z-index:-1;width:2px;background:var(--border-color)}.vp-toc-list{position:relative;margin:0;padding:0}.vp-toc-marker{position:absolute;top:0;inset-inline-start:0;z-index:2;display:none;width:2px;height:1.7rem;background:var(--theme-color);transition:top var(--transform-transition)}@media (min-width: 1440px){.vp-toc-marker{display:block}}.vp-toc-link{position:relative;display:block;overflow:hidden;max-width:100%;color:var(--grey-light);line-height:inherit;text-overflow:ellipsis;white-space:nowrap}.vp-toc-link.level2{padding-inline-start:0px;font-size:14px}.vp-toc-link.level3{padding-inline-start:8px;font-size:13px}.vp-toc-link.level4{padding-inline-start:16px;font-size:12px}.vp-toc-link.level5{padding-inline-start:24px;font-size:11px}.vp-toc-link.level6{padding-inline-start:32px;font-size:10px}.vp-toc-item{position:relative;box-sizing:border-box;height:1.7rem;list-style:none;line-height:1.7rem}@media (min-width: 1440px){.vp-toc-item{padding:0 .5rem}}.vp-toc-item:hover>.vp-toc-link{color:var(--theme-color)}.vp-toc-item.active>.vp-toc-link{color:var(--theme-color);font-weight:700}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper:not(:hover) .arrow{transform:rotate(-180deg)}.dropdown-wrapper .dropdown-title{border-width:0;background:transparent;cursor:pointer;padding:0 .25rem;color:var(--grey-dark);font-weight:500;font-size:inherit;font-family:inherit;line-height:inherit;cursor:inherit}.dropdown-wrapper .dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .icon{margin-inline-end:.25em;font-size:1em}.dropdown-wrapper .dropdown-title .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s;font-size:1.2em}html[data-theme=dark] .dropdown-wrapper .dropdown-title .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.dropdown-wrapper .dropdown-title .arrow.down{transform:rotate(180deg)}html[dir=rtl] .dropdown-wrapper .dropdown-title .arrow.down{transform:rotate(-180deg)}.dropdown-wrapper .dropdown-title .arrow.end{transform:rotate(90deg)}html[dir=rtl] .dropdown-wrapper .dropdown-title .arrow.end,.dropdown-wrapper .dropdown-title .arrow.start{transform:rotate(-90deg)}html[dir=rtl] .dropdown-wrapper .dropdown-title .arrow.start{transform:rotate(90deg)}.dropdown-wrapper ul{margin:0;padding:0;list-style-type:none}.dropdown-wrapper .nav-dropdown{position:absolute;top:100%;inset-inline-end:0;overflow-y:auto;box-sizing:border-box;min-width:6rem;max-height:calc(100vh - var(--navbar-height));margin:0;padding:.5rem .75rem;border:1px solid var(--grey14);border-radius:.5rem;background:var(--bg-color);box-shadow:2px 2px 10px var(--card-shadow);text-align:start;white-space:nowrap;opacity:0;visibility:hidden;transition:all .18s ease-out;transform:scale(.9)}.dropdown-wrapper:hover .nav-dropdown,.dropdown-wrapper.open .nav-dropdown{z-index:2;opacity:1;visibility:visible;transform:none}.dropdown-wrapper .nav-link{position:relative;display:block;margin-bottom:0;border-bottom:none;color:var(--grey-dark);font-weight:400;font-size:.875rem;line-height:1.7rem;transition:color var(--color-transition)}.dropdown-wrapper .nav-link:hover,.dropdown-wrapper .nav-link.active{color:var(--theme-color)}.dropdown-wrapper .dropdown-subtitle{margin:0;padding:.5rem .25rem 0;color:var(--grey-light);font-weight:600;font-size:.75rem;line-height:2;text-transform:uppercase}.dropdown-wrapper .dropdown-subitem-wrapper{padding:0 0 .25rem}.dropdown-wrapper .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .dropdown-item:last-child .dropdown-subtitle{padding-top:0}.dropdown-wrapper .dropdown-item:last-child .dropdown-subitem-wrapper{padding-bottom:0}.nav-screen-dropdown-title{border-width:0;background:transparent;position:relative;display:flex;align-items:center;width:100%;padding:0;color:var(--grey-dark);font-size:inherit;font-family:inherit;text-align:start;cursor:pointer}.nav-screen-dropdown-title:hover,.nav-screen-dropdown-title.active{color:var(--text-color)}.nav-screen-dropdown-title .title{flex:1}.nav-screen-dropdown-title .arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s}html[data-theme=dark] .nav-screen-dropdown-title .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.nav-screen-dropdown-title .arrow.down{transform:rotate(180deg)}html[dir=rtl] .nav-screen-dropdown-title .arrow.down{transform:rotate(-180deg)}.nav-screen-dropdown-title .arrow.end{transform:rotate(90deg)}html[dir=rtl] .nav-screen-dropdown-title .arrow.end,.nav-screen-dropdown-title .arrow.start{transform:rotate(-90deg)}html[dir=rtl] .nav-screen-dropdown-title .arrow.start{transform:rotate(90deg)}.nav-screen-dropdown{overflow:hidden;margin:.5rem 0 0;padding:0;list-style:none;transition:transform .1s ease-out;transform:scaleY(1);transform-origin:top}.nav-screen-dropdown.hide{height:0;margin:0;transform:scaleY(0)}.nav-screen-dropdown .nav-link{position:relative;display:block;padding-inline-start:.5rem;font-weight:400;line-height:2}.nav-screen-dropdown .nav-link:hover,.nav-screen-dropdown .nav-link.active{color:var(--theme-color)}.nav-screen-dropdown .nav-link .icon{font-size:1em}.nav-screen-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.nav-screen-dropdown .dropdown-subtitle{margin:0;padding-inline-start:.25rem;color:var(--grey-light);font-weight:600;font-size:.75rem;line-height:2;text-transform:uppercase}.nav-screen-dropdown .dropdown-subtitle .nav-link{padding:0}.nav-screen-dropdown .dropdown-subitem-wrapper{margin:0;padding:0;list-style:none}.nav-screen-dropdown .dropdown-subitem{padding-inline-start:.5rem;font-size:.9em}.nav-screen-links{display:none;padding-bottom:.75rem}@media (max-width: 719px){.nav-screen-links{display:block}}.nav-screen-links .navbar-links-item{position:relative;display:block;padding:12px 4px 11px 0;border-bottom:1px solid var(--border-color);font-size:16px;line-height:1.5rem}.nav-screen-links .nav-link{display:inline-block;width:100%;color:var(--grey-dark);font-weight:400}.nav-screen-links .nav-link:hover{color:var(--text-color)}.nav-screen-links .nav-link.active{color:var(--theme-color)}.vp-nav-screen-container{max-width:320px;margin:0 auto;padding:2rem 0 4rem}#nav-screen{position:fixed;inset:var(--navbar-height) 0 0 0;z-index:150;display:none;overflow-y:auto;padding:0 2rem;background:var(--bg-color)}@media (max-width: 719px){#nav-screen{display:block}}#nav-screen.fade-enter-active,#nav-screen.fade-leave-active{transition:opacity .25s}#nav-screen.fade-enter-active .vp-nav-screen-container,#nav-screen.fade-leave-active .vp-nav-screen-container{transition:transform .25s ease}#nav-screen.fade-enter-from,#nav-screen.fade-leave-to{opacity:0}#nav-screen.fade-enter-from .vp-nav-screen-container,#nav-screen.fade-leave-to .vp-nav-screen-container{transform:translateY(-8px)}#nav-screen .icon{margin-inline-end:.25em;font-size:1em}#nav-screen img.icon{vertical-align:-.125em;height:1em}.vp-outlook-wrapper{display:flex;justify-content:space-around}.vp-nav-logo{vertical-align:top;height:var(--navbar-line-height);margin-inline-end:.8rem}.vp-nav-logo.light{display:inline-block}.vp-nav-logo.dark,html[data-theme=dark] .vp-nav-logo.light{display:none}html[data-theme=dark] .vp-nav-logo.dark{display:inline-block}.vp-site-name{position:relative;color:var(--text-color);font-size:1.25rem}@media (max-width: 719px){.vp-site-name{overflow:hidden;width:calc(100vw - 9.4rem);text-overflow:ellipsis;white-space:nowrap}}.vp-brand:hover .vp-site-name{color:var(--theme-color)}.vp-navbar .vp-nav-links{display:flex;align-items:center;font-size:.875rem}.vp-navbar .vp-nav-item{position:relative;margin:0 .25rem;line-height:2rem}.vp-navbar .vp-nav-item:first-child{margin-inline-start:0}.vp-navbar .vp-nav-item:last-child{margin-inline-end:0}.vp-navbar .vp-nav-item>.nav-link{color:var(--grey-dark)}.vp-navbar .vp-nav-item>.nav-link:after{content:" ";position:absolute;inset:auto 50% 0;height:2px;border-radius:1px;background:var(--theme-color-light);visibility:hidden;transition:inset .2s ease-in-out}.vp-navbar .vp-nav-item>.nav-link.active{color:var(--theme-color)}.vp-navbar .vp-nav-item>.nav-link:hover:after,.vp-navbar .vp-nav-item>.nav-link.active:after{inset:auto 0 0;visibility:visible}.vp-navbar{--navbar-line-height: calc( var(--navbar-height) - var(--navbar-vertical-padding) * 2 );position:fixed;inset:0 0 auto;z-index:175;display:flex;align-items:center;justify-content:space-between;box-sizing:border-box;height:var(--navbar-height);padding:var(--navbar-vertical-padding) var(--navbar-horizontal-padding);background:var(--navbar-bg-color);box-shadow:0 2px 8px var(--card-shadow);line-height:var(--navbar-line-height);white-space:nowrap;transition:transform var(--transform-transition);-webkit-backdrop-filter:saturate(150%) blur(12px);backdrop-filter:saturate(150%) blur(12px)}@media print{.vp-navbar{display:none}}.hide-navbar .vp-navbar.auto-hide{transform:translateY(-100%)}.vp-navbar .nav-link{padding:0 .25rem;color:var(--grey-dark)}.vp-navbar .nav-link.active{color:var(--theme-color)}.vp-navbar .nav-link .icon{margin-inline-end:.25em;font-size:1em}.vp-navbar .nav-link img.icon{vertical-align:-.125em;height:1em}.vp-navbar.hide-icon .vp-nav-links .icon{display:none!important}.vp-navbar-start,.vp-navbar-end,.vp-navbar-center{display:flex;flex:1;align-items:center}.vp-navbar-start>*,.vp-navbar-end>*,.vp-navbar-center>*{position:relative;margin:0 .25rem!important}.vp-navbar-start>*:first-child,.vp-navbar-end>*:first-child,.vp-navbar-center>*:first-child{margin-inline-start:0!important}.vp-navbar-start>*:last-child,.vp-navbar-end>*:last-child,.vp-navbar-center>*:last-child{margin-inline-end:0!important}.vp-navbar-start{justify-content:start}.vp-navbar-center{justify-content:center}.vp-navbar-end{justify-content:end}.vp-navbar .vp-action{margin:0!important}.vp-navbar .vp-action-link{display:inline-block;margin:auto;padding:6px;color:var(--grey-dark);line-height:1}.vp-navbar .vp-action-link:hover,.vp-navbar .vp-action-link:active{color:var(--theme-color)}.vp-toggle-navbar-button{border-width:0;background:transparent;cursor:pointer;position:relative;display:none;align-items:center;justify-content:center;padding:6px}@media screen and (max-width: 719px){.vp-toggle-navbar-button{display:flex}}.vp-toggle-navbar-button>span{position:relative;overflow:hidden;width:16px;height:14px}.vp-toggle-navbar-button .vp-top,.vp-toggle-navbar-button .vp-middle,.vp-toggle-navbar-button .vp-bottom{position:absolute;width:16px;height:2px;background:var(--grey-dark);transition:top .25s,background .5s,transform .25s}.vp-toggle-navbar-button .vp-top{top:0;left:0;transform:translate(0)}.vp-toggle-navbar-button .vp-middle{top:6px;left:0;transform:translate(8px)}.vp-toggle-navbar-button .vp-bottom{top:12px;left:0;transform:translate(4px)}.vp-toggle-navbar-button:hover .vp-top{top:0;left:0;transform:translate(4px)}.vp-toggle-navbar-button:hover .vp-middle{top:6;left:0;transform:translate(0)}.vp-toggle-navbar-button:hover .vp-bottom{top:12px;left:0;transform:translate(8px)}.vp-toggle-navbar-button.is-active .vp-top{top:6px;transform:translate(0) rotate(225deg)}.vp-toggle-navbar-button.is-active .vp-middle{top:6px;transform:translate(16px)}.vp-toggle-navbar-button.is-active .vp-bottom{top:6px;transform:translate(0) rotate(135deg)}.vp-toggle-navbar-button.is-active:hover .vp-top,.vp-toggle-navbar-button.is-active:hover .vp-middle,.vp-toggle-navbar-button.is-active:hover .vp-bottom{background:var(--theme-color);transition:top .25s,background .25s,transform .25s}.vp-toggle-sidebar-button{border-width:0;background:transparent;cursor:pointer;display:none;vertical-align:middle;box-sizing:content-box;width:1rem;height:1rem;padding:.5rem;font:unset;transition:transform .2s ease-in-out}@media screen and (max-width: 719px){.vp-toggle-sidebar-button{display:block;padding-inline-end:var(--navbar-mobile-horizontal-padding)}}.vp-toggle-sidebar-button:before,.vp-toggle-sidebar-button:after,.vp-toggle-sidebar-button .icon{display:block;width:100%;height:2px;border-radius:.05em;background:var(--grey-dark);transition:transform .2s ease-in-out}.vp-toggle-sidebar-button:before{content:" ";margin-top:.125em}.sidebar-open .vp-toggle-sidebar-button:before{transform:translateY(.34rem) rotate(135deg)}.vp-toggle-sidebar-button:after{content:" ";margin-bottom:.125em}.sidebar-open .vp-toggle-sidebar-button:after{transform:translateY(-.34rem) rotate(-135deg)}.vp-toggle-sidebar-button .icon{margin:.2em 0}.sidebar-open .vp-toggle-sidebar-button .icon{transform:scale(0)}.appearance-title{display:block;margin:0;padding:0 .25rem;color:var(--grey-light);font-weight:600;font-size:.75rem;line-height:2}#appearance-switch{border-width:0;background:transparent;vertical-align:middle;padding:6px;color:var(--grey-dark);cursor:pointer;transition:color var(--color-transition)}#appearance-switch:hover{color:var(--theme-color)}#appearance-switch .icon{width:1.25rem;height:1.25rem}.outlook-button{border-width:0;background:transparent;cursor:pointer;position:relative;padding:.375rem;color:var(--grey-dark)}.outlook-button .icon{vertical-align:middle;width:1.25rem;height:1.25rem}.outlook-dropdown{position:absolute;top:100%;inset-inline-end:0;overflow-y:auto;box-sizing:border-box;min-width:100px;margin:0;padding:.5rem .75rem;border:1px solid var(--grey14);border-radius:.25rem;background:var(--bg-color);box-shadow:2px 2px 10px var(--card-shadow);text-align:start;white-space:nowrap;opacity:0;visibility:hidden;transition:all .18s ease-out;transform:scale(.8)}.outlook-dropdown>*:not(:last-child){padding-bottom:.5rem;border-bottom:1px solid var(--grey14)}.outlook-button:hover .outlook-dropdown,.outlook-button.open .outlook-dropdown{z-index:2;opacity:1;visibility:visible;transform:scale(1)}.theme-color-title{display:block;margin:0;padding:0 .25rem;color:var(--grey-light);font-weight:600;font-size:.75rem;line-height:2}#theme-color-picker{display:flex;margin:0;padding:0;list-style-type:none;font-size:14px}#theme-color-picker li span{display:inline-block;vertical-align:middle;width:15px;height:15px;margin:0 2px;border-radius:2px}#theme-color-picker li span.theme-color,#theme-color-picker li span.theme-color html[data-theme=dark]{background:#3eaf7c}@media print{.full-screen-wrapper{display:none}}.full-screen-title{display:block;margin:0;padding:0 .25rem;color:var(--grey-light);font-weight:600;font-size:.75rem;line-height:2}.full-screen,.cancel-full-screen{border-width:0;background:transparent;vertical-align:middle;padding:.375rem;color:var(--grey-dark);cursor:pointer}.full-screen:hover,.cancel-full-screen:hover{color:var(--theme-color)}.full-screen .icon,.cancel-full-screen .icon{width:1.25rem;height:1.25rem}.enter-fullscreen-icon:hover,.cancel-fullscreen-icon{color:var(--theme-color)}.cancel-fullscreen-icon:hover{color:var(--grey-dark)}.vp-sidebar-header{display:flex;align-items:center;overflow:hidden;box-sizing:border-box;width:calc(100% - 1rem);margin:0;margin-inline:.5rem;padding:.25rem .5rem;border-width:0;border-radius:.375rem;background:transparent;color:var(--text-color);font-size:1.1em;line-height:1.5;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:color .15s ease;transform:rotate(0)}.vp-sidebar-header.open{color:inherit}.vp-sidebar-header.clickable:hover{background:var(--bg-color-secondary)}.vp-sidebar-header.clickable.exact{border-inline-start-color:var(--theme-color);color:var(--theme-color)}.vp-sidebar-header.clickable.exact a{color:inherit}.vp-sidebar-header .vp-sidebar-title{flex:1}.vp-sidebar-header .vp-arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s;font-size:1.5em}html[data-theme=dark] .vp-sidebar-header .vp-arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.vp-sidebar-header .vp-arrow.down{transform:rotate(180deg)}html[dir=rtl] .vp-sidebar-header .vp-arrow.down{transform:rotate(-180deg)}.vp-sidebar-header .vp-arrow.end{transform:rotate(90deg)}html[dir=rtl] .vp-sidebar-header .vp-arrow.end,.vp-sidebar-header .vp-arrow.start{transform:rotate(-90deg)}html[dir=rtl] .vp-sidebar-header .vp-arrow.start{transform:rotate(90deg)}button.vp-sidebar-header{outline:none;font-weight:inherit;font-family:inherit;line-height:inherit;text-align:start;cursor:pointer}.vp-sidebar-link{display:inline-block;box-sizing:border-box;width:calc(100% - 1rem);margin-inline:.5rem;padding:.25rem .5rem;border-radius:.375rem;color:var(--text-color);font-weight:400;font-size:1em;line-height:1.5}.vp-sidebar-link:hover{background:var(--bg-color-secondary)}.vp-sidebar-link.active{background:var(--theme-color-mask);color:var(--theme-color);font-weight:500}.vp-sidebar-link.active .icon{color:var(--theme-color)}.vp-sidebar-group:not(.collapsible) .vp-sidebar-header:not(.clickable){color:inherit;cursor:auto}.vp-sidebar-group .vp-sidebar-group{padding-inline-start:.75rem}.vp-sidebar-group .vp-sidebar-group .vp-sidebar-header{font-size:1em}.vp-sidebar-group .vp-sidebar-link{padding-inline-start:1.25rem}.vp-sidebar-links,.vp-sidebar-links ul{margin:0;padding:0}.vp-sidebar-links li{list-style-type:none}.vp-sidebar>.vp-sidebar-links{padding:1.5rem 0}@media (max-width: 719px){.vp-sidebar>.vp-sidebar-links{padding:1rem 0}}.vp-sidebar>.vp-sidebar-links>li>.vp-sidebar-link{font-size:1.1em}.vp-sidebar>.vp-sidebar-links>li:not(:first-child){margin-top:.5rem}.vp-sidebar{position:fixed;top:0;bottom:0;inset-inline-start:0;z-index:1;overflow-y:auto;width:var(--sidebar-width);margin:0;padding-inline-start:calc(var(--sidebar-space) - var(--sidebar-width));background:var(--sidebar-bg-color);box-shadow:2px 0 8px var(--card-shadow);font-size:.94rem;transition:padding var(--transform-transition),transform var(--transform-transition);-webkit-backdrop-filter:saturate(150%) blur(12px);backdrop-filter:saturate(150%) blur(12px);scrollbar-color:var(--theme-color) var(--border-color);scrollbar-width:thin}@media (max-width: 959px){.vp-sidebar{font-size:.86em}}@media (max-width: 719px){.vp-sidebar{z-index:125;box-shadow:none;transform:translate(-100%)}html[dir=rtl] .vp-sidebar{transform:translate(100%)}}@media (min-width: 1440px){.vp-sidebar{padding-bottom:3rem;box-shadow:none;font-size:1rem}}@media print{.vp-sidebar{display:none}}.vp-sidebar a{display:inline-block;color:var(--text-color);font-weight:400}.vp-sidebar .icon{margin-inline-end:.25em;font-size:1em}.vp-sidebar img.icon{vertical-align:-.125em;height:1em}.vp-sidebar.hide-icon .icon{display:none!important}.vp-sidebar-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9;background:#00000026}.vp-sidebar-mask.fade-enter-active,.vp-sidebar-mask.fade-leave-active{transition:opacity .25s}.vp-sidebar-mask.fade-enter-from,.vp-sidebar-mask.fade-leave-to{opacity:0}.search-pro-result-wrapper{scrollbar-color:var(--vp-tc) var(--vp-brc);scrollbar-width:thin}@media (max-width: 419px){.search-pro-result-wrapper{font-size:14px}}.search-pro-result-wrapper::-webkit-scrollbar{width:6px;height:6px}.search-pro-result-wrapper::-webkit-scrollbar-track-piece{border-radius:6px;background:#0000001a}.search-pro-result-wrapper::-webkit-scrollbar-thumb{border-radius:6px;background:var(--vp-tc)}.search-pro-result-wrapper::-webkit-scrollbar-thumb:active{background:var(--vp-tcl)}.search-pro-result-wrapper mark{border-radius:.25em;line-height:1}.search-pro-result-list{margin:0;padding:0}.search-pro-result-list-item{display:block;list-style:none}.search-pro-result-title{position:sticky;top:-2px;z-index:10;margin:-4px;margin-bottom:.25rem;padding:4px;background:var(--vp-bg);color:var(--vp-tc);font-weight:600;font-size:.85em;line-height:2rem;text-indent:.5em}.search-pro-result-item.active .search-pro-result-title{color:var(--vp-tc)}.search-pro-result-type{display:block;width:1rem;height:1rem;margin-inline-start:-.5rem;padding:.5rem;color:var(--vp-tc)}.search-pro-remove-icon{border-width:0;background:transparent;cursor:pointer;box-sizing:content-box;height:1.5rem;padding:0;border-radius:50%;color:var(--vp-tc);font-size:1rem}.search-pro-remove-icon svg{width:1.5rem;height:1.5rem}.search-pro-remove-icon:hover{background:#8080804d}.search-pro-result-content{display:flex;flex-grow:1;flex-direction:column;align-items:stretch;justify-content:center;line-height:1.5}.search-pro-result-content .content-header{margin-bottom:.25rem;border-bottom:1px solid var(--vp-brcd);font-size:.9em}.search-pro-result-item{display:flex;align-items:center;margin:.5rem 0;padding:.5rem .75rem;border-radius:.25rem;background:var(--vp-bgl);color:inherit;box-shadow:0 1px 3px 0 var(--card-shadow);font-weight:400;white-space:pre-wrap;word-wrap:break-word}.search-pro-result-item strong{color:var(--vp-tc)}.search-pro-result-item:hover,.search-pro-result-item.active{background-color:var(--vp-tcl);color:var(--white);cursor:pointer}.search-pro-result-item:hover .search-pro-result-type,.search-pro-result-item:hover .search-pro-remove-icon,.search-pro-result-item:hover strong,.search-pro-result-item.active .search-pro-result-type,.search-pro-result-item.active .search-pro-remove-icon,.search-pro-result-item.active strong{color:var(--white)} diff --git "a/assets/\346\224\257\344\273\230\345\256\23510-BcbPWYKj.jpg" "b/assets/\346\224\257\344\273\230\345\256\23510-BcbPWYKj.jpg" new file mode 100644 index 00000000..7b2266aa Binary files /dev/null and "b/assets/\346\224\257\344\273\230\345\256\23510-BcbPWYKj.jpg" differ diff --git "a/assets/\346\224\257\344\273\230\345\256\23520-ChMdJN1i.jpg" "b/assets/\346\224\257\344\273\230\345\256\23520-ChMdJN1i.jpg" new file mode 100644 index 00000000..bf59ae49 Binary files /dev/null and "b/assets/\346\224\257\344\273\230\345\256\23520-ChMdJN1i.jpg" differ diff --git "a/assets/\346\224\257\344\273\230\345\256\23588.88-cwJg-qu6.jpg" "b/assets/\346\224\257\344\273\230\345\256\23588.88-cwJg-qu6.jpg" new file mode 100644 index 00000000..368fed97 Binary files /dev/null and "b/assets/\346\224\257\344\273\230\345\256\23588.88-cwJg-qu6.jpg" differ diff --git a/image/index.html b/image/index.html new file mode 100644 index 00000000..b283e065 --- /dev/null +++ b/image/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Image | 现代C++并发编程教程 + + + + + + + + + diff --git "a/image/\346\215\220\350\265\240/index.html" "b/image/\346\215\220\350\265\240/index.html" new file mode 100644 index 00000000..884c9561 --- /dev/null +++ "b/image/\346\215\220\350\265\240/index.html" @@ -0,0 +1,40 @@ + + + + + + + + + + 现代C++并发编程教程 + + + + + +
    跳至主要內容

    小于 1 分钟

    cppcppcpp

      我们会收集捐赠者进行感谢,所以请您捐赠了可以选择备注,或者联系我,或者直接在捐赠初始记录名单open in new window中进行评论。

    + + + diff --git "a/image/\347\216\260\344\273\243C++\345\271\266\345\217\221\347\274\226\347\250\213\346\225\231\347\250\213.png" "b/image/\347\216\260\344\273\243C++\345\271\266\345\217\221\347\274\226\347\250\213\346\225\231\347\250\213.png" new file mode 100644 index 00000000..174295c8 Binary files /dev/null and "b/image/\347\216\260\344\273\243C++\345\271\266\345\217\221\347\274\226\347\250\213\346\225\231\347\250\213.png" differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..0b78f23b --- /dev/null +++ b/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 现代C++并发编程教程 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    现代C++并发编程教程

    大约 1 分钟

    cpp

    现代C++并发编程教程

    本仓库用来存放 B 站课程《现代 C++ 并发编程教程》的教案、代码。

    不管是否购买课程,任何组织和个人遵守 CC BY-NC-ND 4.0open in new window 协议均可随意使用学习。

    捐赠open in new windowissuesopen in new windowpropen in new window 均会在致谢列表中铭记您的贡献


      国内的 C++ 并发编程的教程并不稀少,不管是书籍、博客、视频。然而大多数是粗糙的、不够准确、复杂的。而我们想以更加现代简单准确的方式进行教学。

      我们在教学中可能常常为您展示部分标准库源码,自己手动实现一些库,这是必须的,希望您是已经较为熟练使用模板(如果没有,可以先学习 现代C++模板教程open in new window)。阅读源码可以帮助我们更轻松的理解标准库设施的使用与原理。

      本教程假设开发者的最低水平为:C++11 + STL + template

      虽强调现代,但不用担心,我们几乎是从头教学,即使你从来没使用过 C++ 进行多线程编程,也不成问题。

      我们希望您的编译器版本和标准尽可能的高,我们的代码均会测试三大编译器 gcc、clang、msvc。需要更高的标准会进行强调。

    + + + diff --git "a/md/01\345\237\272\346\234\254\346\246\202\345\277\265.html" "b/md/01\345\237\272\346\234\254\346\246\202\345\277\265.html" new file mode 100644 index 00000000..04bae6fc --- /dev/null +++ "b/md/01\345\237\272\346\234\254\346\246\202\345\277\265.html" @@ -0,0 +1,40 @@ + + + + + + + + + + 基本概念 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    基本概念

    大约 3 分钟

    基本概念

    前言

      在我们谈起“并发编程”,其实可以直接简单理解为“多线程编程”,我知道你或许有疑问:“那多进程呢?” C++ 语言层面没有进程的概念,并发支持库也不涉及多进程,所以在本教程中,不用在意。

      我们完全使用标准 C++ 进行教学。

    并发

    并发,指两个或两个以上的独立活动同时发生。

    并发在生活中随处可见,我们可以一边走路一边说话,也可以两只手同时做不同的动作,又或者一边看电视一边吃零食。

    在计算机中的并发

    计算机中的并发有两种方式:

    1. 多核机器的真正并行

    2. 单核机器的任务切换

      在早期,一些单核机器,它要想并发,执行多个任务,那就只能是任务切换,任务切换会给你一种“好像这些任务都在同时执行”的假象。只有硬件上是多核的,才能进行真正的并行,也就是真正的”同时执行任务“。

      在现在,我们日常使用的机器,基本上是二者都有。我们现在的 CPU 基本都是多核,而操作系统调度基本也一样有任务切换,因为要执行的任务非常之多,CPU 是很快的,但是核心却没有那么多,不可能每一个任务都单独给一个核心。大家可以打开自己电脑的任务管理器看一眼,进程至少上百个,线程更是上千。这基本不可能每一个任务分配一个核心,都并行,而且也没必要。正是任务切换使得这些后台任务可以运行,这样系统使用者就可以同时运行文字处理器、编译器、编辑器和 Web 浏览器。

    并发与并行

    事实上,对于这两个术语,并没有非常公认的说法。

    1. 有些人认为二者毫无关系,指代的东西完全不同。

    2. 有些人认为二者大多数时候是相同的,只是用于描述一些东西的时候关注点不同。

    我喜欢第二种,那我们就讲第二种。

    对多线程来说,这两个概念大部分是重叠的。对于很多人来说,它们没有什么区别。 这两个词是用来描述硬件同时执行多个任务的方式:

    • “并行”更加注重性能。使用硬件提高数据处理速度时,会讨论程序的并行性。

    • 当关注重点在于任务分离或任务响应时,会讨论程序的并发性。

    这两个术语存在的目的,就是为了区别多线程中不同的关注点。

    总结

      概念从来不是我们的重点,尤其是某些说法准确性也一般,假设开发者对操作系统等知识有基本了解。

      我们也不打算特别介绍什么 C++ 并发库的历史发展、什么时候你该使用多线程、什么时候不该使用多线程... 类似问题应该是看你自己的,而我们回到代码上即可。

    + + + diff --git "a/md/02\344\275\277\347\224\250\347\272\277\347\250\213.html" "b/md/02\344\275\277\347\224\250\347\272\277\347\250\213.html" new file mode 100644 index 00000000..7edb45e5 --- /dev/null +++ "b/md/02\344\275\277\347\224\250\347\272\277\347\250\213.html" @@ -0,0 +1,435 @@ + + + + + + + + + + 使用线程 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    使用线程

    大约 30 分钟

    使用线程

    在标准 C++ 中,std::threadopen in new window 可以指代线程,使用线程也就是使用 std::thread 类。

    Hello World

    在我们初学 C++ 的时候应该都写过这样一段代码:

    #include <iostream>
    +
    +int main(){
    +    std::cout << "Hello World!" << std::endl;
    +}
    +

    这段代码将"Hello World!"写入到标准输出流,换行并刷新open in new window

    我们可以启动一个线程来做这件事情:

    #include <iostream>
    +#include <thread>  // 引入线程支持头文件
    +
    +void hello(){     // 定义一个函数用作打印任务
    +    std::cout << "Hello World" << std::endl;
    +}
    +
    +int main(){
    +    std::thread t{ hello };
    +    t.join();
    +}
    +

    std::thread t{ hello }; 创建了一个线程对象 t,将 hello 作为它的可调用(Callable)open in new window对象,在新线程中执行。线程对象关联了一个线程资源,我们无需手动控制,在线程对象构造成功,就自动在新线程开始执行函数 hello

    t.join(); 等待线程对象 t 关联的线程执行完毕,否则将一直堵塞。这里的调用是必须的,否则 std::thread 的析构函数将调用 std::terminate()open in new window 无法正确析构。

    这是因为我们创建线程对象 t 的时候就关联了一个活跃的线程,调用 join() 就是确保线程对象关联的线程已经执行完毕,然后会修改对象的状态,让 std::thread::joinable()open in new window 返回 false,表示线程对象目前没有关联活跃线程。std::thread 的析构函数,正是通过 joinable() 判断线程对象目前是否有关联活跃线程,如果为 true,那么就当做有关联活跃线程,会调用 std::terminate()


    如你所见,std::thread 高度封装,其成员函数也很少,我们可以轻易的创建线程执行任务,不过,它的用法也还远不止如此,我们慢慢介绍。

    当前环境支持并发线程数

    使用 hardware_concurrencyopen in new window 可以获得我们当前硬件支持的并发线程数量,它是 std::thread 的静态成员函数。

    #include <iostream>
    +#include <thread>
    + 
    +int main(){
    +    unsigned int n = std::thread::hardware_concurrency();
    +    std::cout << "支持 " << n << " 个并发线程。\n";
    +}
    +

    本节其实是要普及一下计算机常识,一些古老的书籍比如 csapp 应该也会提到“超线程技术open in new window”。

    英特尔® 超线程技术是一项硬件创新,允许在每个内核上运行多个线程。更多的线程意味着可以并行完成更多的工作。

    AMD 超线程技术被称为 SMT(Simultaneous Multi-Threading),它与英特尔的技术实现有所不同,不过使用类似。

    举个例子:一款 4 核心 8 线程的 CPU,这里的 8 线程其实是指所谓的逻辑处理器,也意味着这颗 CPU 最多可并行执行 8 个任务。

    我们的 hardware_concurrency() 获取的值自然也会是 8

    当然了,都 2024 年了,我们还得考虑一个问题:“ 英特尔从 12 代酷睿开始,为其处理器引入了全新的“大小核”混合设计架构”。

    比如我的 CPU i7 13700H 它是 14 核心,20 线程,有 6 个能效核,6 个性能核。不过我们说了,物理核心这个通常不看重,hardware_concurrency() 输出的值会为 20。

    • 在进行多线程编程的时候,我们可以参考此值确定我们要创建的线程数量

    我们可以举个简单的例子运用这个值:

    template<typename ForwardIt>
    +auto sum(ForwardIt first, ForwardIt last){
    +    using value_type = std::iter_value_t<ForwardIt>;
    +    std::size_t num_threads = std::thread::hardware_concurrency();
    +    std::ptrdiff_t distance = std::distance(first, last);
    +
    +    if(distance > 1024000){
    +        // 计算每个线程处理的元素数量
    +        std::size_t chunk_size = distance / num_threads;
    +        std::size_t remainder = distance % num_threads;
    +
    +        // 存储每个线程的结果
    +        std::vector<value_type>results(num_threads);
    +
    +        // 存储关联线程的线程对象
    +        std::vector<std::thread> threads;
    +
    +        // 创建并启动线程
    +        auto start = first;
    +        for (std::size_t i = 0; i < num_threads; ++i) {
    +            auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0));
    +            threads.emplace_back([start, end, &results, i] {
    +                results[i] = std::accumulate(start, end, value_type{});
    +            });
    +            start = end; // 开始迭代器不断向前
    +        }
    +
    +        // 等待所有线程执行完毕
    +        for (auto& thread : threads)
    +            thread.join();
    +
    +        // 汇总线程的计算结果
    +        value_type total_sum = std::accumulate(results.begin(), results.end(), value_type{});
    +        return total_sum;
    +    }
    +
    +    value_type total_sum = std::accumulate(first, last, value_type{});
    +    return total_sum;
    +}
    +

    运行open in new window测试。

    我们写了这样一个求和函数 sum,接受两个迭代器计算它们范围中对象的和。

    我们先获取了迭代器所指向的值的类型,定义了一个别名 value_type,我们这里使用到的 std::iter_value_topen in new window 是 C++20 引入的,返回类型推导open in new window是 C++14 引入。如果希望代码可以在 C++11 的环境运行也可以自行修改为:

    template<typename ForwardIt>
    +typename std::iterator_traits<ForwardIt>::value_type sum(ForwardIt first, ForwardIt last);
    +

    运行open in new window测试。

    num_threads 是当前硬件支持的并发线程的值。std::distanceopen in new window 用来计算 first 到 last 的距离,也就是我们要进行求和的元素个数了。

    我们这里的设计比较简单,毕竟是初学,所以只对元素个数大于 1024000 的进行多线程求和,而小于这个值的则直接使用标准库函数 std::accumulateopen in new window 求和即可。

    多线程求和只需要介绍三个地方

    1. chunk_size 是每个线程分配的任务,但是这是可能有余数的,比如 10 个任务分配三个线程,必然余 1。但是我们也需要执行这个任务,所以还定义了一个对象 remainder ,它存储的就是余数。

    2. auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0)); 这行代码是获取当前线程的执行范围,其实也就是要 chunk_size 再加上我们的余数 remainder 。这里写了一个三目运算符是为了进行分配任务,比如:

      假设有 3 个线程执行,并且余数是 2。那么,每个线程的处理情况如下:

      • i = 0 时,由于 0 < 2,所以这个线程会多分配一个元素。
      • i = 1 时,同样因为 1 < 2,这个线程也会多分配一个元素。
      • i = 2 时,由于 2 >= 2,所以这个线程只处理平均数量的元素。

      这确保了剩余的 2 个元素被分配给了前两个线程,而第三个线程只处理了平均数量的元素。这样就确保了所有的元素都被正确地分配给了各个线程进行处理。

    3. auto start = first; 在创建线程执行之前先定义了一个开始迭代器。在传递给线程执行的lambda表达式中,最后一行是:start = end; 这是为了让迭代器一直向前。

    由于求和不涉及数据竞争之类的问题,所以我们甚至可以在刚讲完 Hello World 就手搓了一个“并行求和”的简单的模板函数。主要的难度其实在于对 C++ 的熟悉程度,而非对线程类 std::thread 的使用了,这里反而是最简单的,无非是用容器存储线程对象管理,最后进行 join() 罢了。

    本节代码只是为了学习,而且只是百万数据通常没必要多线程,上亿的话差不多。如果你需要多线程求和,可以使用 C++17 引入的求和算法 std::reduceopen in new window 并指明执行策略open in new window。它的效率接近我们实现的 sum 的两倍,当前环境核心越多数据越多,和单线程效率差距越明显。

    线程管理

    在 C++ 标准库中,只能管理与 std::thread 关联的线程,类 std::thread 的对象就是指代线程的对象,我们说“线程管理”,其实也就是管理 std::thread 对象。

    启动新线程

    使用 C++ 线程库启动线程,就是构造 std::thread 对象。

    当然了,如果是默认构造之类的,那么 std::thread 线程对象没有关联线程的,自然也不会启动线程执行任务。

    std::thread t; //  构造不表示线程的新 std::thread 对象
    +

    我们上一节的示例是传递了一个函数给 std::thread 对象,函数会在新线程中执行。std::thread 支持的形式还有很多,只要是可调用(Callable)open in new window对象即可,比如重载了 operator() 的类对象(也可以直接叫函数对象)。

    class Task{
    +public:
    +    void operator()()const {
    +        std::cout << "operator()()const\n";
    +    }
    +};
    +

    我们显然没办法直接像函数使用函数名一样,使用“类名”,函数名可以隐式转换到指向它的函数指针,而类名可不会直接变成对象,我们想使用 Task 自然就得构造对象了

    std::thread t{ Task{} };
    +t.join();
    +

    直接创建临时对象即可,可以简化代码并避免引入不必要的局部对象。

    不过有件事情需要注意,当我们使用函数对象用于构造 std::thread 的时候,如果你传入的是一个临时对象,且使用的都是 “()”小括号初始化,那么编译器会将此语法解析为函数声明

    std::thread t( Task() ); // 函数声明
    +

    这被编译器解析为函数声明,是一个返回类型为 std::thread,函数名为 t,接受一个返回 Task 的空参的函数指针类型,也就是 Task(*)()

    之所以我们看着抽象是因为这里的形参是无名的,且写了个函数类型。

    我们用一个简单的示例为你展示:

    void h(int(int));         //#1 声明
    +void h(int (*p)(int)){}   //#2 定义
    +

    即使我还没有为你讲述概念,我相信你也发现了,#1 和 #2 的区别无非是,#1 省略了形参的名称,还有它的形参是函数类型而不是函数指针类型,没有 *

    在确定每个形参的类型后,类型是 “T 的数组”或某个函数类型 T 的形参会调整为具有类型“指向 T 的指针”文档open in new window

    显然,int(int) 是一个函数类型,它被调整为了一个指向这个函数类型的指针类型。

    那么回到我们最初的:

    std::thread t( Task() );                    // #1 函数声明
    +std::thread t( Task (*p)() ){ return {}; }  // #2 函数定义
    +

    #2我们写出了函数形参名称 p,再将函数类型写成函数指针类型,事实上完全等价。我相信,这样,也就足够了。

    所以总而言之,建议使用 {} 进行初始化,这是好习惯,大多数时候它是合适的。

    C++11 引入的 Lambda 表达式,同样可以作为构造 std::thread 的参数,因为 Lambda 本身就是生成open in new window了一个函数对象,它自身就是类类型open in new window

    #include <iostream>
    +#include <thread>
    +
    +int main(){
    +    std::thread thread{ [] {std::cout << "Hello World!\n"; } };
    +    thread.join();
    +}
    +

    启动线程后(也就是构造 std::thread 对象)我们必须在线程对象的生存期结束之前,即 std::thread::~threadopen in new window 调用之前,决定它的执行策略,是 join()open in new window(合并)还是 detach()open in new window(分离)。

    我们先前使用的就是 join(),我们聊一下 detach(),当 std::thread 线程对象调用了 detach(),那么就是线程对象放弃了对线程资源的所有权,不再管理此线程,允许此线程独立的运行,在线程退出时释放所有分配的资源。

    放弃了对线程资源的所有权,也就是线程对象没有关联活跃线程了,此时 joinable 为 false

    在单线程的代码中,对象销毁之后再去访问,会产生未定义行为open in new window,多线程增加了这个问题发生的几率。

    比如函数结束,那么函数局部对象的生存期都已经结束了,都被销毁了,此时线程函数还持有函数局部对象的指针或引用。

    #include <iostream>
    +#include <thread>
    +
    +struct func {
    +    int& m_i;
    +    func(int& i) :m_i{ i } {}
    +    void operator()(int n)const {
    +        for (int i = 0; i <= n; ++i) {
    +            m_i += i;           // 可能悬空引用
    +        }
    +    }
    +};
    +
    +int main(){
    +    int n = 0;
    +    std::thread my_thread{ func{n},100 };
    +    my_thread.detach();        // 分离,不等待线程结束
    +}                              // 分离的线程可能还在运行
    +
    1. 主线程(main)创建局部对象 n、创建线程对象 my_thread 启动线程,执行任务 func{n},局部对象 n 的引用被子线程持有。传入 100 用于调用 func 的 operator(int)。

    2. my_thread.detach();,joinable() 为 false。线程分离,线程对象不再持有线程资源,线程独立的运行。

    3. 主线程不等待,此时分离的子线程可能没有执行完毕,但是主线程(main)已经结束,局部对象 n 生存期结束,被销毁,而此时子线程还持有它的引用,访问悬空引用,造成未定义行为。my_thread 已经没有关联线程资源,正常析构,没有问题。

    解决方法很简单,将 detach() 替换为 join()。

    通常非常不推荐使用 detach(),因为程序员必须确保所有创建的线程正常退出,释放所有获取的资源并执行其它必要的清理操作。这意味着通过调用 detach() 放弃线程的所有权不是一种选择,因此 join 应该在所有场景中使用。 一些老式特殊情况不聊。

    另外提示一下,也不要想着 detach() 之后,再次调用 join()

    my_thread.detach();
    +// todo..
    +my_thread.join();
    +// 函数结束
    +

    认为这样可以确保被分离的线程在这里阻塞执行完?

    我们前面聊的很清楚了,detach() 是线程分离,线程对象放弃了线程资源的所有权,此时我们的 my_thread 它现在根本没有关联任何线程。调用 join() 是:“阻塞当前线程直至 *this 所标识的线程结束其执行”,我们的线程对象都没有线程,堵塞什么?执行什么呢?

    简单点说,必须是 std::thread 的 joinable() 为 true 即线程对象有活跃线程,才能调用 join() 和 detach()。

    顺带的,我们还得处理线程运行后的异常问题,举个例子:你在一个函数中构造了一个 std::thread 对象,线程开始执行,函数继续执行下面别的代码,但是如果抛出了异常呢?下面我的 join() 就会被跳过

    std::thread my_thread{func{n},10};
    +//todo.. 抛出异常的代码
    +my_thread.join();
    +

    避免程序被抛出的异常所终止,在异常处理过程中调用 join(),从而避免线程对象析构产生问题。

    struct func; // 复用之前
    +void f(){
    +    int n = 0;
    +    std::thread t{ func{n},10 };
    +    try{
    +        // todo.. 一些当前线程可能抛出异常的代码
    +        f2();
    +    }
    +    catch (...){
    +        t.join(); // 1
    +        throw;
    +    }
    +    t.join();    // 2
    +}
    +

    我知道你可能有很多疑问,我们既然 catch 接住了异常,为什么还要 throw?以及为什么我们要两个 join()?

    这两个问题其实也算一个问题,如果代码里抛出了异常,就会跳转到 catch 的代码中,执行 join() 确保线程正常执行完成,线程对象可以正常析构。然而此时我们必须再次 throw 抛出异常,因为你要是不抛出,那么你不是还得执行一个 t.join()?显然逻辑不对,自然抛出。

    至于这个函数产生的异常,由调用方进行处理,我们只是确保函数 f 中创建的线程正常执行完成,其局部对象正常析构释放。测试代码open in new window

    我知道你可能会想到:“我在 try 块中最后一行写一个 t.join() ,这样如果前面的代码没有抛出异常,就能正常的调用 join(),如果抛出了异常,那就调用 catch 中的 t.join() 根本不需要最外部 2 那里的 join(),也不需要再次 throw 抛出异常”

    void f(){
    +    int n = 0;
    +    std::thread t{ func{n},10 };
    +    try{
    +        // todo.. 一些当前线程可能抛出异常的代码
    +        f2();
    +        t.join(); // try 最后一行调用 join()
    +    }
    +    catch (...){
    +        t.join(); // 如果抛出异常,就在 这里调用 join()
    +    }
    +}
    +

    你是否觉得这样也可以?也没问题?简单的测试open in new window运行的确没问题。

    但是这是不对的,你要注意我们的注释:“一些当前线程可能抛出异常的代码”,而不是 f2(),我们的 try catch 只是为了让线程对象关联的线程得以正确执行完毕,以及线程对象正确析构。并没有处理什么其他的东西,不掩盖错误,try块中的代码抛出了异常,catch` 接住了,我们理所应当再次抛出。

    RAII

    资源获取即初始化open in new window”(RAII,Resource Acquisition Is Initialization)。

    简单的说是:构造函数申请资源,析构函数释放资源,让对象的生命周期和资源绑定。当异常抛出时,C++ 会自动调用栈上所有对象的析构函数。

    我们可以提供一个类,在析构函数中使用 join() 确保线程执行完成,线程对象正常析构。

    class thread_guard{
    +    std::thread& m_t;
    +public:
    +    explicit thread_guard(std::thread& t) :m_t{ t } {}
    +    ~thread_guard(){
    +        std::puts("析构");     // 打印 不用在乎
    +        if (m_t.joinable()) { // 没有关联活跃线程
    +            m_t.join();
    +        }
    +    }
    +    thread_guard(const thread_guard&) = delete;
    +    thread_guard& operator=(const thread_guard&) = delete;
    +};
    +void f(){
    +    int n = 0;
    +    std::thread t{ func{n},10 };
    +    thread_guard g(t);
    +    f2(); // 可能抛出异常
    +}
    +

    函数 f 执行完毕,局部对象就要逆序销毁了。因此,thread_guard 对象 g 是第一个被销毁的,调用析构函数即使函数 f2() 抛出了一个异常,这个销毁依然会发生(前提是你捕获了这个异常)。这确保了线程对象 t 所关联的线程正常的执行完毕以及线程对象的正常析构。测试代码open in new window

    如果异常被抛出但未被捕获那么就会调用 std::terminateopen in new window。是否对未捕获的异常进行任何栈回溯由实现定义。(简单的说就是不一定会调用析构)

    我们的测试代码是捕获了异常的,为了观测,看到它一定打印“析构”。

    在 thread_guard 的析构函数中,我们要判断 std::thread 线程对象现在是否有关联的活跃线程,如果有,我们才会执行 join(),阻塞当前线程直到线程对象关联的线程执行完毕。如果不想等待线程结束可以使用 detach() ,但是这让 std::thread 对象失去了线程资源的所有权,难以掌控,具体如何,看情况分析。

    拷贝赋值和拷贝构造定义为 =delete 可以防止编译器隐式生成,同时会阻止open in new window移动构造函数和移动赋值运算符的隐式定义。这样的话,对 thread_guard 对象进行拷贝或赋值等操作会引发一个编译错误。

    不允许这些操作主要在于:这是个管理类,而且顾名思义,它就应该只是单纯的管理线程对象仅此而已,只保有一个引用,单纯的做好 RAII 的事情就行,允许其他操作没有价值。

    严格来说其实这里倒也不算 RAII,因为 thread_guard 的构造函数其实并没有申请资源,只是保有了线程对象的引用,在析构的时候进行了 join() 。

    传递参数

    向可调用对象或函数传递参数很简单,我们前面也都写了,只需要将这些参数作为 std::thread 的构造参数即可。需要注意的是,这些参数会拷贝到新线程的内存空间中,即使函数中的参数是引用,依然实际是拷贝

    void f(int, const int& a);
    +
    +int n = 1;
    +std::thread t(f, 3, n);
    +

    线程对象 t 的构造没有问题,可以通过编译,但是这个 n 实际上并没有按引用传递,而是拷贝了,我们可以打印地址来验证我们的猜想。

    void f(int, const int& a) { // a 并非引用了局部对象 n
    +    std::cout << &a << '\n'; 
    +}
    +
    +int main() {
    +    int n = 1;
    +    std::cout << &n << '\n';
    +    std::thread t(f, 3, n);
    +    t.join();
    +}
    +

    运行代码open in new window,打印的地址截然不同。

    可以通过编译,但通常这不符合我们的需求,因为我们的函数中的参数是引用,我们自然希望能引用调用方传递的参数,而不是拷贝。如果我们的 f 的形参类型不是 const 的引用,则会产生一个编译错误open in new window

    想要解决这个问题很简单,我们可以使用标准库的设施 std::refopen in new windowstd::cref 函数模板。

    void f(int, int& a) {
    +    std::cout << &a << '\n'; 
    +}
    +
    +int main() {
    +    int n = 1;
    +    std::cout << &n << '\n';
    +    std::thread t(f, 3, std::ref(n));
    +    t.join();
    +}
    +

    运行代码open in new window,打印地址完全相同。

    我们来解释一下,“ref” 其实就是 “reference”(引用)的缩写,意思也很简单,返回“引用”,当然了,不是真的返回引用,它们返回一个包装类 std::reference_wrapperopen in new window,顾名思义,这个类就是包装引用对象类模板,将对象包装,可以隐式转换为被包装对象的引用。

    cref”呢?,这个“c”就是“const”,就是返回了 std::reference_wrapper<const T>。我们不详细介绍他们的实现,你简单认为reference_wrapper可以隐式转换为被包装对象的引用即可,

    int n = 0;
    +std::reference_wrapper<int> r = std::ref(n);
    +int& p = r; // r 隐式转换为 n 的引用 此时 p 引用的就是 n
    +
    int n = 0;
    +std::reference_wrapper<const int> r = std::cref(n);
    +const int& p = r; // r 隐式转换为 n 的 const 的引用 此时 p 引用的就是 n
    +

    如果对他们的实现感兴趣,可以观看视频open in new window


    以上代码void f(int, int&) 如果不使用 std::ref 并不会和前面 void f(int, const int&) 一样只是多了拷贝,而是会产生编译错误open in new window,这是因为 std::thread 内部会将保有的参数副本转换为右值表达式进行传递,这是为了那些只支持移动的类型,左值引用没办法引用右值表达式,所以产生编译错误。

    struct move_only {
    +    move_only() { std::puts("默认构造"); }
    +    move_only(const move_only&) = delete;
    +    move_only(move_only&&)noexcept {
    +        std::puts("移动构造");
    +    }
    +};
    +
    +void f(move_only){}
    +
    +int main(){
    +    move_only obj;
    +    std::thread t{ f,std::move(obj) };
    +    t.join();
    +}
    +

    运行open in new window测试。

    没有 std::ref 自然是会保有一个副本,所以有两次移动构造,一次是被 std::thread 构造函数中初始化副本,一次是调用函数 f

    如果还有不理解,不用担心,记住,这一切的问题都会在后面的 std::thread 的构造-源码解析 解释清楚。


    成员函数指针open in new window也是可调用open in new window(Callable)的 ,可以传递给 std::thread 作为构造参数,让其关联的线程执行成员函数。

    struct X{
    +    void task_run(int)const;
    +};
    +
    + X x;
    + int n = 0;
    + std::thread t{ &X::task_run,&x,n };
    + t.join();
    +

    传入成员函数指针、与其配合使用的对象、调用成员函数的参数,构造线程对象 t,启动线程。

    如果你是第一次见到成员指针,那么我们稍微聊一下,&X::task_run 是一个整体,它们构成了成员指针,&类名::非静态成员

    成员指针必须和对象一起使用,这是唯一标准用法,成员指针不可以转换到函数指针单独使用,即使是非静态成员函数没有使用任何数据成员。

    我们还可以使用模板函数 std::bindopen in new window与成员指针一起使用

    std::thread t{ std::bind(&X::task_run, &x ,n) };
    +

    不过需要注意,std::bind 也是默认拷贝open in new window的,即使我们的成员函数形参类型为引用:

    struct X {
    +    void task_run(int& a)const{
    +        std::cout << &a << '\n';
    +    }
    +};
    +
    +X x;
    +int n = 0;
    +std::cout << &n << '\n';
    +std::thread t{ std::bind(&X::task_run,&x,n) };
    +t.join();
    +

    除非给参数 n 加上 std::ref,就是按引用open in new window传递了:

    std::thread t{ std::bind(&X::task_run,&x,std::ref(n)) };
    +

    void f(const std::string&);
    +std::thread t{ f,"hello" };
    +

    代码创建了一个调用 f("hello") 的线程。注意,函数 f 实际需要的是一个 std::string 类型的对象作为参数,但这里使用的是字符串字面量,我们要明白“A的引用只能引用A,或者以任何形式转换到A”,字符串字面量的类型是 const char[N] ,它会退化成指向它的const char* 指针,被线程对象保存。在调用 f 的时候,这个指针可以通过 std::string 的转换构造函数,构造出一个临时的 std::string 对象,就能成功调用。

    字符串字面量具有静态存储期open in new window,指向它的指针这当然没问题了,不用担心生存期的问题,但是如果是指向“动态”对象的指针,就要特别注意了:

    void f(const std::string&);
    +void test(){
    +    char buffer[1024]{};
    +    //todo.. code
    +    std::thread t{ f,buffer };
    +    t.detach();
    +}
    +

    以上代码可能导致一些问题,buffer 是一个数组对象,作为 std::thread 构造参数的传递的时候会decay-copyopen in new window (确保实参在按值传递时会退化) 隐式转换为了指向这个数组的指针

    我们要特别强调,std::thread 构造是代表“启动线程”,而不是调用我们传递的可调用对象。

    std::thread 的构造函数中调用了创建线程的函数(windows 下可能为 _beginthreadexopen in new window),它将我们传入的参数,f、buffer ,传递给这个函数,在新线程中执行函数 f。也就是说,调用和执行 f(buffer) 并不是说要在 std::thread 的构造函数中,而是在创建的新线程中,具体什么时候执行,取决于操作系统的调度,所以完全有可能函数 test 先执行完,而新线程此时还没有进行 f(buffer) 的调用,转换为std::string,那么 buffer 指针就悬空了,会导致问题。解决方案:

    1. detach() 替换为 join()

      void f(const std::string&);
      +void test(){
      +    char buffer[1024]{};
      +    //todo.. code
      +    std::thread t{ f,buffer };
      +    t.join();
      +}
      +
    2. 显式将 buffer 转换为 std::string

      void f(const std::string&);
      +void test(){
      +    char buffer[1024]{};
      +    //todo.. code
      +    std::thread t{ f,std::string(buffer) };
      +    t.detach();
      +}
      +

    std::this_thread

    这个命名空间包含了管理当前线程的函数。

    1. yieldopen in new window 建议实现重新调度各执行线程。
    2. get_idopen in new window 返回当前线程 id。
    3. sleep_foropen in new window 使当前线程停止执行指定时间。
    4. sleep_untilopen in new window 使当前线程执行停止到指定的时间点。

    它们之中最常用的是 get_id,其次是 sleep_for,再然后 yieldsleep_until 较少。

    • 使用 get_id 打印open in new window主线程和子线程的 ID。

      int main() {
      +    std::cout << std::this_thread::get_id() << '\n';
      +
      +    std::thread t{ [] {
      +        std::cout << std::this_thread::get_id() << '\n';
      +    } };
      +    t.join();
      +}
      +
    • 使用 sleep_for 延时。当 Sleep 之类的就行,但是它需要接受的参数不同,是 std::chrono 命名空间中的时间对象。

      int main() {
      +    std::this_thread::sleep_for(std::chrono::seconds(3));
      +}
      +

      主线程延时 3 秒,这个传入了一个临时对象 seconds ,它是模板 std::chrono::durationopen in new window 的别名,以及还有很多其他的时间类型,都基于这个类。说实话挺麻烦的,如果您支持 C++14,建议使用时间字面量open in new window,在 std::chrono_literalsopen in new window 命名空间中。我们可以改成下面这样:

      using namespace std::chrono_literals;
      +
      +int main() {
      +    std::this_thread::sleep_for(3s);
      +}
      +

      简单直观。

    • yield 减少 CPU 的占用。

      while (!isDone()){
      +    std::this_thread::yield();
      +}
      +

      线程需要等待某个操作完成,如果你直接用一个循环不断判断这个操作是否完成就会使得这个线程占满 CPU 时间,这会造成资源浪费。此时可以判断操作是否完成,如果还没完成就调用 yield 交出 CPU 时间片让其他线程执行,过一会儿再来判断是否完成,这样这个线程占用 CPU 时间会大大减少。

    • 使用 sleep_until 让当前线程延迟到具体的时间。我们延时 5 秒就是。

      int main() {
      +    // 获取当前时间点
      +    auto now = std::chrono::system_clock::now();
      +
      +    // 设置要等待的时间点为当前时间点之后的5秒
      +    auto wakeup_time = now + 5s;
      +
      +    // 输出当前时间
      +    auto now_time = std::chrono::system_clock::to_time_t(now);
      +    std::cout << "Current time:\t\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S") << std::endl;
      +
      +    // 输出等待的时间点
      +    auto wakeup_time_time = std::chrono::system_clock::to_time_t(wakeup_time);
      +    std::cout << "Waiting until:\t\t" << std::put_time(std::localtime(&wakeup_time_time), "%H:%M:%S") << std::endl;
      +
      +    // 等待到指定的时间点
      +    std::this_thread::sleep_until(wakeup_time);
      +
      +    // 输出等待结束后的时间
      +    now = std::chrono::system_clock::now();
      +    now_time = std::chrono::system_clock::to_time_t(now);
      +    std::cout << "Time after waiting:\t" << std::put_time(std::localtime(&now_time), "%H:%M:%S") << std::endl;
      +}
      +

      sleep_until 本身设置使用很简单,是打印时间格式、设置时区麻烦。运行结果open in new window

    介绍了一下 std::this_thread 命名空间中的四个函数的基本用法,我们后续会经常看到这些函数的使用,不用着急。

    std::thread 转移所有权

    传入可调用对象以及参数,构造 std::thread 对象,启动线程,而线程对象拥有了线程的所有权,线程是一种系统资源,所以可称作“线程资源”。

    std::thread 不可复制。两个 std::thread 不可表示一个线程,std::thread 对线程资源是独占所有权。移动就是转移它的线程资源的所有权给别的 std::thread 对象。

    int main() {
    +    std::thread t{ [] {
    +        std::cout << std::this_thread::get_id() << '\n';
    +    } };
    +    std::cout << t.joinable() << '\n'; // 线程对象 t 当前关联了活跃线程 打印 1
    +    std::thread t2{ std::move(t) };    // 将 t 的线程资源的所有权移交给 t2
    +    std::cout << t.joinable() << '\n'; // 线程对象 t 当前没有关联活跃线程 打印 0
    +    //t.join(); // Error! t 没有线程资源
    +    t2.join();  // t2 当前持有线程资源
    +}
    +

    这段代码通过移动构造转移了线程对象 t 的线程资源所有权到 t2,这里虽然有两个 std::thread 对象,但是从始至终只有一个线程资源,让持有线程资源的 t2 对象最后调用 join() 堵塞让其线程执行完毕。tt2 都能正常析构。

    我们还可以使用移动赋值来转移线程资源的所有权:

    int main() {
    +    std::thread t;      // 默认构造,没有关联活跃线程
    +    std::cout << t.joinable() << '\n'; // 0
    +    std::thread t2{ [] {} };
    +    t = std::move(t2); // 转移线程资源的所有权到 t
    +    std::cout << t.joinable() << '\n'; // 1
    +    t.join();
    +    
    +    t2 = std::thread([] {});
    +    t2.join();
    +}
    +

    我们只需要介绍 t2 = std::thread([] {}) ,临时对象是右值表达式,不用调用 std::move,这里相当于是将临时的 std::thread 对象所持有的线程资源转移给 t2t2 再调用 join() 正常析构。

    函数返回 std::thread 对象:

    std::thread f(){
    +    std::thread t{ [] {} };
    +    return t;
    +}
    +
    +int main(){
    +    std::thread rt = f();
    +    rt.join();
    +}
    +

    这段代码可以通过编译open in new window,你是否感到奇怪?我们在函数 f() 中创建了一个局部的 std::thread 对象,启动线程,然后返回它。

    这里的 return t 重载决议[1]选择到了移动构造,将 t 线程资源的所有权转移给函数调用 f() 返回的临时 std::thread 对象中,然后这个临时对象再用来初始化 rt ,临时对象是右值表达式,这里一样选择到移动构造,将临时对象的线程资源所有权移交给 rt。此时 rt 具有线程资源的所有权,由它调用 join() 正常析构。

    如果标准达到 C++17,RVO 保证这里少一次移动构造的开销(临时对象初始化 rt 的这次)。

    所有权也可以在函数内部传递

    void f(std::thread t){
    +    t.join();
    +}
    +
    +int main(){
    +    std::thread t{ [] {} };
    +    f(std::move(t));
    +    f(std::thread{ [] {} });
    +}
    +

    std::move 将 t 转换为了一个右值表达式,初始化函数f 形参 t,选择到了移动构造转移线程资源的所有权,在函数中调用 t.join() 后正常析构。std::thread{ [] {} } 构造了一个临时对象,本身就是右值表达式,初始化函数f 形参 t,移动构造转移线程资源的所有权到 tt.join() 后正常析构。

    本节内容总体来说是很简单的,如果你有些地方无法理解,那只有一种可能,“对移动语义不了解”,不过这也不是问题,在后续我们详细介绍 std::thread 构造函数的源码即可,不用着急。

    std::thread 的构造-源码解析

    我们上一个大节讲解了线程管理,也就是 std::thread 的管理,其中的重中之重就是它的构造,传递参数。我们用源码实现为各位从头讲解。

    了解其实现,才能更好的使用它。

    实现 joining_thread

    这个类和 std::thread 的区别就是析构函数会自动 join 。如果您好好的学习了上一节的内容,阅读了 std::thread 的源码,以下内容不会对您构成任何的难度。

    我们存储一个 std::thread 作为底层数据成员,稍微注意一下构造函数和赋值运算符的实现即可。

    class joining_thread {
    +    std::thread t;
    +public:
    +    joining_thread()noexcept = default;
    +    template<typename Callable, typename... Args>
    +    explicit joining_thread(Callable&& func, Args&&...args) :
    +        t{ std::forward<Callable>(func), std::forward<Args>(args)... } {}
    +    explicit joining_thread(std::thread t_)noexcept : t{ std::move(t_) } {}
    +    joining_thread(joining_thread&& other)noexcept : t{ std::move(other.t) } {}
    +
    +    joining_thread& operator=(std::thread&& other)noexcept {
    +        if (joinable()) { // 如果当前有活跃线程,那就先执行完
    +            join();
    +        }
    +        t = std::move(other);
    +        return *this;
    +    }
    +    ~joining_thread() {
    +        if (joinable()) {
    +            join();
    +        }
    +    }
    +    void swap(joining_thread& other)noexcept {
    +        t.swap(other.t);
    +    }
    +    std::thread::id get_id()const noexcept {
    +        return t.get_id();
    +    }
    +    bool joinable()const noexcept {
    +        return t.joinable();
    +    }
    +    void join() {
    +        t.join();
    +    }
    +    void detach() {
    +        t.detach();
    +    }
    +    std::thread& data()noexcept {
    +        return t;
    +    }
    +    const std::thread& data()const noexcept {
    +        return t;
    +    }
    +};
    +

    简单使用open in new window一下:

    int main(){
    +    std::cout << std::this_thread::get_id() << '\n';
    +    joining_thread thread{[]{
    +            std::cout << std::this_thread::get_id() << '\n';
    +    } };
    +    joining_thread thread2{ std::move(thread) };
    +}
    +

    使用容器管理线程对象,等待线程执行结束

    void do_work(std::size_t id){
    +    std::cout << id << '\n';
    +}
    +
    +int main(){
    +    std::vector<std::thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i){
    +        threads.emplace_back(do_work, i); // 产生线程
    +    }
    +    for(auto& thread:threads){
    +        thread.join();                   // 对每个线程对象调用 join()
    +    }
    +}
    +

    运行测试open in new window

    线程对象代表了线程,管理线程对象也就是管理线程,这个 vector 对象管理 10 个线程,保证他们的执行、退出。

    使用我们这节实现的 joining_thread 则不需要最后的循环 join()

    int main(){
    +    std::vector<joining_thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i){
    +        threads.emplace_back(do_work, i);
    +    }
    +}
    +

    运行测试open in new window

    如果你自己编译了这些代码,相信你注意到了,打印的是乱序的,没什么规律,而且重复运行的结果还不一样,这是正常现象。多线程执行就是如此,无序且操作可能被打断。使用互斥量可以解决这些问题,这也就是下一章节的内容了。

    总结

    本章节的内容围绕着:“使用线程”,也就是"使用 std::thread"展开, std::thread 是我们学习 C++ 并发支持库的重中之重,本章的内容并不少见,但是却是少有的准确与完善。即使你早已学习过乃至使用 C++ 标准库进行多线程编程已经很久,我相信本章也一定可以让你收获良多。

    如果是第一次学习,有还不够理解的地方,则一定要多思考,或记住,以后多看。

    我尽量讲的简单与通俗易懂。学完本章,你大概率还无法在实际环境使用多线程提升程序效率,至少也要学习到使用互斥量,保护共享数据,才可实际使用。


    1. 重载决议open in new window简单来说就是编译器必须要根据规则选择最合适的函数重载进行调用↩︎

    + + + diff --git "a/md/03\345\205\261\344\272\253\346\225\260\346\215\256.html" "b/md/03\345\205\261\344\272\253\346\225\260\346\215\256.html" new file mode 100644 index 00000000..d61ba371 --- /dev/null +++ "b/md/03\345\205\261\344\272\253\346\225\260\346\215\256.html" @@ -0,0 +1,442 @@ + + + + + + + + + + 共享数据 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    共享数据

    大约 32 分钟

    共享数据

    本章节主要内容:

    • 多线程共享数据的问题

    • 使用互斥量保护共享数据

    • 保护共享数据的其它方案

    在上一章内容,我们对于线程的基本使用和管理,可以说已经比较了解了,甚至深入阅读了部分的 std::thread 源码。所以如果你好好学习了上一章,本章也完全不用担心。

    我们本章,就要开始聊共享数据的那些事。

    条件竞争

    在多线程的情况下,每个线程都抢着完成自己的任务。在大多数情况下,即使会改变执行顺序,也是良性竞争,这是无所谓的。比如两个线程都要往标准输出输出一段字符,谁先谁后并不会有什么太大影响。

    void f() { std::cout << "❤️\n"; }
    +void f2() { std::cout << "😢\n"; }
    +
    +int main(){
    +    std::thread t{ f };
    +    std::thread t2{ f2 };
    +    t.join();
    +    t2.join();
    +}
    +

    std::coutopen in new window 的单个 operator<< 调用是线程安全的,不会被打断。即:同步的 C++ 流保证是线程安全的(从多个线程输出的单独字符可能交错,但无数据竞争)

    只有在涉及多线程修改相同共享数据的时候,才会导致“恶性的条件竞争”。

    std::vector<int>v;
    +
    +void f() { v.emplace_back(1); }
    +void f2() { v.erase(v.begin()); }
    +
    +int main() {
    +    std::thread t{ f };
    +    std::thread t2{ f2 };
    +    t.join();
    +    t2.join();
    +    std::cout << v.size() << '\n';
    +}
    +

    比如这段代码就是典型的恶性条件竞争,两个线程共享一个 vector,并对它进行修改。可能导致许多问题,比如 f2 先执行,此时 vector 还没有元素,导致抛出异常。又或者 f 执行了一半,调用了 f2(),等等。

    当然了,也有可能先执行 f,然后执行 f2,最后打印了 0,程序老老实实执行完毕。

    但是我们显然不能寄希望于这种操作系统的调度。

    而且即使不是一个添加元素,一个删除元素,全是 emplace_back 添加元素,也一样会有问题,由于 std::vector 不是线程安全的容器,因此当多个线程同时访问并修改 v 时,可能会发生未定义的行为open in new window。具体来说,当两个线程同时尝试向 v 中添加元素时,但是 emplace_back 函数却是可以被打断的,执行了一半,又去执行另一个线程。可能会导致数据竞争,从而引发未定义的结果。

    当某个表达式的求值写入某个内存位置,而另一求值读或修改同一内存位置时,称这些表达式冲突拥有两个冲突的求值的程序就有数据竞争,除非

    • 两个求值都在同一线程上,或者在同一信号处理函数中执行,或
    • 两个冲突的求值都是原子操作(见 std::atomic),或
    • 一个冲突的求值发生早于 另一个(见 std::memory_order)

    如果出现数据竞争,那么程序的行为未定义。

    标量类型等都同理,有数据竞争未定义行为open in new window

    int cnt = 0;
    +auto f = [&]{cnt++;};
    +std::thread t1{f}, t2{f}, t3{f}; // 未定义行为
    +

    使用互斥量

    互斥量(Mutex),又称为互斥锁,是一种用来保护临界区[1]的特殊对象,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:

    1. 如果互斥锁是锁定的, 通常说某个特定的线程正持有这个互斥锁

    2. 如果没有线程持有这个互斥量,那么这个互斥量就处于解锁状态


    概念从来不是我们的重点,用一段对比代码为你直观的展示互斥量的作用:

    void f() {
    +    std::cout << std::this_thread::get_id() << '\n';
    +}
    +
    +int main() {
    +    std::vector<std::thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i)
    +        threads.emplace_back(f);
    +
    +    for (auto& thread : threads)
    +        thread.join();
    +}
    +

    这段代码你多次运行open in new window它会得到毫无规律和格式的结果,我们可以使用互斥量open in new window解决这个问题:

    #include <mutex> // 必要标头
    +std::mutex m;
    +
    +void f() {
    +    m.lock();
    +    std::cout << std::this_thread::get_id() << '\n';
    +    m.unlock();
    +}
    +
    +int main() {
    +    std::vector<std::thread>threads;
    +    for (std::size_t i = 0; i < 10; ++i)
    +        threads.emplace_back(f);
    +
    +    for (auto& thread : threads)
    +        thread.join();
    +}
    +

    当多个线程执行函数 f 的时候,只有一个线程能成功调用 lock() 给互斥量上锁,其他所有的线程 lock() 的调用将阻塞执行,直至获得锁。第一个调用 lock() 的线程得以继续往下执行,执行我们的 std::cout 输出语句,不会有任何其他的线程打断这个操作。直到线程执行 unlock(),就解锁了互斥量。

    那么其他线程此时也就能再有一个成功调用 lock...

    至于到底哪个线程才会成功调用,这个是由操作系统调度决定的。

    看一遍描述就可以了,简而言之,被 lock()unlock() 包含在其中的代码是线程安全的,同一时间只有一个线程执行,不会被其它线程的执行所打断。

    std::lock_guard

    不过一般不推荐这样显式的 lock()unlock(),我们可以使用 C++11 标准库引入的“管理类” std::lock_guardopen in new window

    void f() {
    +    std::lock_guard<std::mutex>lc{ m };
    +    std::cout << std::this_thread::get_id() << '\n';
    +}
    +

    那么问题来了,std::lock_guard 是如何做到的呢?它是怎么实现的呢?首先顾名思义,这是一个“管理类”模板,用来管理互斥量的上锁与解锁,我们来看它在 MSVC STLopen in new window 的实现:

    _EXPORT_STD template <class _Mutex>
    +class _NODISCARD_LOCK lock_guard { // class with destructor that unlocks a mutex
    +public:
    +    using mutex_type = _Mutex;
    +
    +    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
    +        _MyMutex.lock();
    +    }
    +
    +    lock_guard(_Mutex& _Mtx, adopt_lock_t) noexcept // strengthened
    +        : _MyMutex(_Mtx) {} // construct but don't lock
    +
    +    ~lock_guard() noexcept {
    +        _MyMutex.unlock();
    +    }
    +
    +    lock_guard(const lock_guard&)            = delete;
    +    lock_guard& operator=(const lock_guard&) = delete;
    +
    +private:
    +    _Mutex& _MyMutex;
    +};
    +

    这段代码极其简单,首先管理类,自然不可移动不可复制,我们定义复制构造与复制赋值为弃置函数open in new window,同时阻止open in new window了移动等函数的隐式定义。

    它只保有一个私有数据成员,一个引用,用来引用互斥量。

    构造函数中初始化这个引用,同时上锁,析构函数中解锁,这是一个非常典型的 RAII 式的管理。

    同时它还提供一个有额外std::adopt_lock_topen in new window参数的构造函数 ,如果使用这个构造函数,则构造函数不会上锁。

    所以有的时候你可能会看到一些这样的代码:

    void f(){
    +    //code..
    +    {
    +        std::lock_guard<std::mutex>lc{ m };
    +        // 涉及共享资源的修改的代码...
    +    }
    +    //code..
    +}
    +

    使用 {} 创建了一个块作用域,限制了对象 lc 的生存期,进入作用域构造 lock_guard 的时候上锁(lock),离开作用域析构的时候解锁(unlock)。

    • 我们要尽可能的让互斥量上锁的粒度小,只用来确保必须的共享资源的线程安全。

    “粒度”通常用于描述锁定的范围大小,较小的粒度意味着锁定的范围更小,因此有更好的性能和更少的竞争。

    我们举一个例子:

    std::mutex m;
    +
    +void add_to_list(int n, std::list<int>& list) {
    +    std::vector<int> numbers(n + 1);
    +    std::iota(numbers.begin(), numbers.end(), 0);
    +    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
    +
    +    {
    +        std::lock_guard<std::mutex>lc{ m };
    +        list.push_back(sum);
    +    }
    +}
    +void print_list(const std::list<int>& list){
    +    std::lock_guard<std::mutex>lc{ m };
    +    for(const auto& i : list){
    +        std::cout << i << ' ';
    +    }
    +    std::cout << '\n';
    +}
    +
    std::list<int> list;
    +std::thread t1{ add_to_list,i,std::ref(list) };
    +std::thread t2{ add_to_list,i,std::ref(list) };
    +std::thread t3{ print_list,std::cref(list) };
    +std::thread t4{ print_list,std::cref(list) };
    +t1.join();
    +t2.join();
    +t3.join();
    +t4.join();
    +

    完整代码测试open in new window

    先看 add_to_list,只有 list.push_back(sum) 涉及到了对共享数据的修改,需要进行保护,我们用 {} 包起来了。

    假设有线程 A、B执行函数 add_to_list() :线程 A 中的 numbers、sum 与线程 B 中的,不是同一个,希望大家分清楚,自然不存在数据竞争,也不需要上锁。线程 A、B执行完了前面求 0-n 的计算,只有一个线程能在 lock_guard 的构造函数中成功调用 lock() 给互斥量上锁。假设线程 A 成功调用 lock(),那么线程 B 的 lock() 调用就阻塞了,必须等待线程 A 执行完里面的代码,然后作用域结束,调用 lock_guard 的析构函数,解锁 unlock(),此时线程 B 就可以进去执行了,避免了数据竞争,不存在一个对象同时被多个线程修改。

    函数 print_list() 就更简单了,打印 list,给整个函数上锁,同一时刻只能有一个线程执行。

    我们的使用代码是多个线程执行这两个函数,两个函数共享了一个锁,这样确保了当执行函数 print_list() 打印的时候,list 的状态是确定的。打印函数 print_listadd_to_list 函数的修改操作同一时间只能有一个线程在执行。print_list() 不可能看到正在被add_to_list() 修改的 list。

    至于到底哪个函数哪个线程会先执行,执行多少次,这些都由操作系统调度决定,也完全有可能连续 4 次都是执行函数 print_list 的线程成功调用 lock,会打印出了一样的值,这都很正常。


    C++17 添加了一个新的特性,类模板实参推导open in new windowstd::lock_guard 可以根据传入的参数自行推导,而不需要写明模板类型参数:

    std::mutex m;
    +std::lock_guard lc{ m }; // std::lock_guard<std::mutex>
    +

    并且 C++17 还引入了一个新的“管理类”:std::scoped_lockopen in new window,它相较于 lock_guard的区别在于,它可以管理多个互斥量。不过对于处理一个互斥量的情况,它和 lock_guard 几乎完全相同。

    std::mutex m;
    +std::scoped_lock lc{ m }; // std::scoped_lock<std::mutex>
    +

    我们在后续管理多个互斥量,会详细了解这个类。

    try_lock

    try_lock 是互斥量中的一种尝试上锁的方式。与常规的 lock 不同,try_lock 会尝试上锁,但如果锁已经被其他线程占用,则不会阻塞当前线程,而是立即返回

    它的返回类型是 bool ,如果上锁成功就返回 true,失败就返回 false

    这种方法在多线程编程中很有用,特别是在需要保护临界区的同时,又不想线程因为等待锁而阻塞的情况下。

    std::mutex mtx;
    +
    +void threadFunction(int id) {
    +    // 尝试加锁
    +    if (mtx.try_lock()) {
    +        std::cout << "线程:" << id << " 获得锁" << std::endl;
    +        // 临界区代码
    +        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟临界区操作
    +        mtx.unlock(); // 解锁
    +        std::cout << "线程:" << id << " 释放锁" << std::endl;
    +    } else {
    +        std::cout << "线程:" << id << " 获取锁失败 处理步骤" << std::endl;
    +    }
    +}
    +

    如果有两个线程运行这段代码,必然有一个线程无法成功上锁,要走 else 的分支。

    std::thread t1(threadFunction, 1);
    +std::thread t2(threadFunction, 2);
    +
    +t1.join();
    +t2.join();
    +

    运行open in new window测试。

    保护共享数据

    互斥量主要也就是为了保护共享数据,上一节的使用互斥量也已经为各位展示了一些。

    然而使用互斥量来保护共享数据也并不是在函数中加上一个 std::lock_guard 就万事大吉了。有的时候只需要一个指针或者引用,就能让这种保护形同虚设

    class Data{
    +    int a{};
    +    std::string b{};
    +public:
    +    void do_something(){
    +        // 修改数据成员等...
    +    }
    +};
    +
    +class Data_wrapper{
    +    Data data;
    +    std::mutex m;
    +public:
    +    template<class Func>
    +    void process_data(Func func){
    +        std::lock_guard<std::mutex>lc{m};
    +        func(data);  // 受保护数据传递给函数
    +    }
    +};
    +
    +Data* p = nullptr;
    +
    +void malicious_function(Data& protected_data){
    +    p = &protected_data; // 受保护的数据被传递
    +}
    +
    +Data_wrapper d;
    +
    +void foo(){
    +    d.process_data(malicious_function);  // 传递了一个恶意的函数
    +    p->do_something();                   // 在无保护的情况下访问保护数据
    +}
    +

    成员函数模板 process_data 看起来一点问题也没有,使用 std::lock_guard 对数据做了保护,但是调用方传递了 malicious_function 这样一个恶意的函数,使受保护数据传递给外部,可以在没有被互斥量保护的情况下调用 do_something()

    我们传递的函数就不该是涉及外部副作用的,就应该是单纯的在受互斥量保护的情况下老老实实调用 do_something() 操作受保护的数据。

    • 简而言之:切勿将受保护数据的指针或引用传递到互斥量作用域之外,不然保护将形同虚设

    process_data 的确算是没问题,用户非要做这些事情也是防不住的,我们只是告诉各位可能的情况。

    死锁:问题与解决

    试想一下,有一个玩具,这个玩具有两个部分,必须同时拿到两部分才能玩。比如一个遥控汽车,需要遥控器和玩具车才能玩。有两个小孩,他们都想玩这个玩具。当其中一个小孩拿到了遥控器和玩具车时,就可以尽情玩耍。当另一个小孩也想玩,他就得等待另一个小孩玩完才行。再试想,遥控器和玩具车被放在两个不同的地方,并且两个小孩都想要玩,并且一个拿到了遥控器,另一个拿到了玩具车。问题就出现了,除非其中一个孩子决定让另一个先玩,他把自己的那个部分给另一个小孩。但如果他们都不愿意,那么这个遥控汽车就谁都没有办法玩。

    我们当然不在乎小孩抢玩具,我们要聊的是线程对锁的竞争:两个线程需要对它们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个线程的互斥量解锁。因为它们都在等待对方释放互斥量,没有线程工作。 这种情况就是死锁。

    • 多个互斥量才可能遇到死锁问题

    避免死锁的一般建议是让两个互斥量以相同的顺序上锁,总在互斥量 B 之前锁住互斥量 A,就通常不会死锁。反面示例

    std::mutex m1,m2;
    +std::size_t n{};
    +
    +void f(){
    +    std::lock_guard<std::mutex>lc1{ m1 };
    +    std::lock_guard<std::mutex>lc2{ m2 };;
    +    ++n;
    +}
    +void f2() {
    +    std::lock_guard<std::mutex>lc1{ m2 };
    +    std::lock_guard<std::mutex>lc2{ m1 };
    +    ++n;
    +}
    +

    ff2 因为互斥量上锁顺序不同,就有死锁风险。函数 f 先锁定 m1,然后再尝试锁定 m2,而函数 f2 先锁定 m2 再锁定 m1 。如果两个线程同时运行,它们就可能会彼此等待对方释放其所需的锁,从而造成死锁。

    简而言之,有可能函数 f 锁定了 m1,函数 f2 锁定了 m2,函数 f 要往下执行,给 m2 上锁,所以在等待 f2 解锁 m2,然而函数 f2 也在等待函数 f 解锁 m1 它才能往下执行。所以死锁。测试代码open in new window


    但是有的时候即使固定锁顺序,依旧会产生问题。当有多个互斥量保护同一个类的对象时,对于相同类型的两个不同对象进行数据的交换操作,为了保证数据交换的正确性,就要避免其它线程修改,确保每个对象的互斥量都锁住自己要保护的区域。如果按照前面的的选择一个固定的顺序上锁解锁,则毫无意义,比如:

    struct X{
    +    X(const std::string& str) :object{ str } {}
    +
    +    friend void swap(X& lhs, X& rhs);
    +private:
    +    std::string object;
    +    std::mutex m;
    +};
    +
    +void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::lock_guard<std::mutex>lock1{ lhs.m }; 
    +    std::lock_guard<std::mutex>lock2{ rhs.m }; 
    +    swap(lhs.object, rhs.object);
    +}
    +

    考虑用户调用的时候将参数交换,就会产生死锁

    X a{ "🤣" }, b{ "😅" };
    +std::thread t{ [&] {swap(a, b); } };  // 1
    +std::thread t2{ [&] {swap(b, a); } }; // 2
    +

    1 执行的时候,先上锁 a 的互斥量,再上锁 b 的互斥量。

    2 执行的时候,先上锁 b 的互斥量,再上锁 a 的互斥量。

    完全可能线程 A 执行 1 的时候上锁了 a 的互斥量,线程 B 执行 2 上锁了 b 的互斥量。线程 A 往下执行需要上锁 b 的互斥量,线程 B 则要上锁 a 的互斥量执行完毕才能解锁,哪个都没办法往下执行,死锁测试代码open in new window

    其实也就是回到了第一个示例的问题。

    C++ 标准库有很多办法解决这个问题,可以使用 std::lockopen in new window ,它能一次性锁住多个互斥量,并且没有死锁风险。修改 swap 代码后如下:

    void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::lock(lhs.m, rhs.m);    // 给两个互斥量上锁
    +    std::lock_guard<std::mutex>lock1{ lhs.m,std::adopt_lock }; 
    +    std::lock_guard<std::mutex>lock2{ rhs.m,std::adopt_lock }; 
    +    swap(lhs.object, rhs.object);
    +}
    +

    因为前面已经使用了 std::lock 上锁,所以后的 std::lock_guard 构造都额外传递了一个 std::adopt_lock 参数,让其选择到不上锁的构造函数。函数退出也能正常解锁。

    std::locklhs.mrhs.m 上锁时若抛出异常,则在重抛前对任何已锁的对象调用 unlock() 解锁,也就是 std::lock 要么将互斥量都上锁,要么一个都不锁。

    C++17 新增了 std::scoped_lockopen in new window ,提供此函数的 RAIIopen in new window 包装,通常它比裸调用 std::lock 更好。

    所以我们前面的代码可以改写为:

    void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::scoped_lock guard{ lhs.m,rhs.m };
    +    swap(lhs.object, rhs.object);
    +}
    +

    对此类有兴趣或任何疑问,建议阅读std::scoped_lock 的源码实现与解析

    使用 std::scoped_lock 可以将所有 std::lock 替换掉,减少错误发生。

    然而它们的帮助都是有限的,一切最终都是依靠开发者使用与管理。

    死锁是多线程编程中令人相当头疼的问题,并且死锁经常是不可预见,甚至难以复现,因为在大部分时间里,程序都能正常完成工作。我们可以通过一些简单的规则,约束开发者的行为,帮助写出“无死锁”的代码

    • 避免嵌套锁

      线程获取一个锁时,就别再获取第二个锁。每个线程只持有一个锁,自然不会产生死锁。如果必须要获取多个锁,使用 std::lock

    • 避免在持有锁时调用外部代码

      这个建议是很简单的:因为代码是外部提供的,所以没办法确定外部要做什么。外部程序可能做任何事情,包括获取锁。在持有锁的情况下,如果用外部代码要获取一个锁,就会违反第一个指导意见,并造成死锁(有时这是无法避免的)。当写通用代码时(比如保护共享数据中的 Date 类)。这不是接口设计者可以处理的,只能寄希望于调用方传递的代码是能正常执行的。

    • 使用固定顺序获取锁

      如同第一个示例那样,固定的顺序上锁就不存在问题。

    std::unique_lock 灵活的锁

    std::unique_lockopen in new window 是 C++11 引入的一种通用互斥包装器,它相比于 std::lock_guard 更加的灵活。当然,它也更加的复杂,尤其它还可以与我们下一章要讲的条件变量open in new window一起使用。使用它可以将之前使用 std::lock_guardswap 改写一下:

    void swap(X& lhs, X& rhs) {
    +    if (&lhs == &rhs) return;
    +    std::unique_lock<std::mutex>lock1{ lhs.m, std::defer_lock };
    +    std::unique_lock<std::mutex>lock2{ rhs.m, std::defer_lock };
    +    std::lock(lock1, lock2);
    +    swap(lhs.object, rhs.object);
    +    ++n;
    +}
    +

    解释这段代码最简单的方式就是直接展示标准库的源码,首先,我们要了解 std::defer_lock 是“假设调用方线程已拥有互斥体的所有权”。没有所有权自然构造函数就不会上锁,但不止如此。我们还要先知道 std::unique_lock 保有的数据成员(都以 MSVC STLopen in new window 为例):

    private:
    +    _Mutex* _Pmtx = nullptr;
    +    bool _Owns    = false;
    +

    如你所见很简单,一个互斥量的指针,还有一个就是表示对象是否拥有互斥量所有权的 bool 类型的对象 _Owns 了。我们前面代码会调用构造函数:

    unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
    +    : _Pmtx(_STD addressof(_Mtx)), _Owns(false) {} // construct but don't lock
    +

    如你所见,只是初始化了数据成员而已,注意,这个构造函数没有给互斥量上锁,且 _Ownsfalse 表示没有互斥量所有权。并且 std::unique_lock 是有 lock()open in new windowtry_lock()open in new windowunlock()open in new window 成员函数的,所以可以直接传递给 std::lock、 进行调用。这里还需要提一下 lock() 成员函数的代码:

    void lock() { // lock the mutex
    +    _Validate();
    +    _Pmtx->lock();
    +    _Owns = true;
    +}
    +

    如你所见,正常上锁,并且把 _Owns 设置为 true,即表示当前对象拥有互斥量的所有权。那么接下来看析构函数:

    ~unique_lock() noexcept {
    +    if (_Owns) {
    +        _Pmtx->unlock();
    +    }
    +}
    +

    必须得是当前对象拥有互斥量的所有权析构函数才会调用 unlock() 解锁互斥量。我们的代码因为调用了 lock ,所以 _Owns 设置为 true ,函数结束的时候会解锁互斥量。


    设计挺奇怪的对吧,这个所有权语义。其实上面的代码还不够简单直接,我们再举个例子:

    std::mutex m;
    +
    +int main() {
    +    std::unique_lock<std::mutex>lock{ m,std::adopt_lock };
    +    lock.lock();
    +}
    +

    这段代码运行会抛出异常open in new window,原因很简单,因为 std::adopt_lock 只是不上锁,但是有所有权,即 _Owns 设置为 true 了,当运行 lock() 成员函数的时候,调用了 _Validate() 进行检测,也就是:

    void _Validate() const { // check if the mutex can be locked
    +    if (!_Pmtx) {
    +        _Throw_system_error(errc::operation_not_permitted);
    +    }
    +
    +    if (_Owns) {
    +        _Throw_system_error(errc::resource_deadlock_would_occur);
    +    }
    +}
    +

    满足第二个 if,因为 _Ownstrue 所以抛出异常,别的标准库也都有类似设计open in new window。很诡异的设计对吧,正常。除非我们写成:

    lock.mutex()->lock();
    +

    也就是说 std::unique_lock 要想调用 lock() 成员函数,必须是当前没有所有权

    所以正常的用法其实是,先上锁了互斥量,然后传递 std::adopt_lock 构造 std::unique_lock 对象表示拥有互斥量的所有权,即可在析构的时候正常解锁。如下:

    std::mutex m;
    +
    +int main() {
    +    m.lock();
    +    std::unique_lock<std::mutex>lock{ m,std::adopt_lock };
    +}
    +

    简而言之:

    • 使用 std::defer_lock 构造函数不上锁,要求构造之后上锁
    • 使用 std::adopt_lock 构造函数不上锁,要求在构造之前互斥量上锁
    • 默认构造会上锁,要求构造函数之前和构造函数之后都不能再次上锁

    我们前面提到了 std::unique_lock 更加灵活,那么灵活在哪?很简单,它拥有 lock()unlock() 成员函数,所以我们能写出如下代码:

    void f() {
    +    //code..
    +    
    +    std::unique_lock<std::mutex>lock{ m };
    +
    +    // 涉及共享资源的修改的代码...
    +
    +    lock.unlock(); // 解锁并释放所有权,析构函数不会再 unlock()
    +
    +    //code..
    +}
    +

    而不是像之前 std::lock_guard 一样使用 {}

    另外再聊一聊开销吧,其实倒也还好,多了一个 bool ,内存对齐,x64 环境也就是 16 字节。这都不是最重要的,主要是复杂性和需求,通常建议优先 std::lock_guard,当它无法满足你的需求或者显得代码非常繁琐,那么可以考虑使用 std::unique_lock

    在不同作用域传递互斥量

    首先我们要明白,互斥量满足互斥体 (Mutex)open in new window的要求,不可复制不可移动。所谓的在不同作用域传递互斥量,其实只是传递了它们的指针或者引用罢了。可以利用各种类来进行传递,比如前面提到的 std::unique_lock

    std::unique_lock 可以获取互斥量的所有权,而互斥量的所有权可以通过移动操作转移给其他的 std::unique_lock 对象。有些时候,这种转移(就是调用移动构造)是自动发生的,比如当函数返回open in new window std::unique_lock 对象。另一种情况就是得显式使用 std::moveopen in new window

    请勿对移动语义和转移所有权抱有错误的幻想,我们说的无非是调用 std::unique_lock 的移动构造罢了:

    _NODISCARD_CTOR_LOCK unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) {
    +    _Other._Pmtx = nullptr;
    +    _Other._Owns = false;
    +}
    +

    将数据成员赋给新对象,原来的置空,这就是所谓的 所有权”转移,切勿被词语迷惑。

    std::unique_lock 是只能移动不可复制的类,它移动即标志其管理的互斥量的所有权转移了。

    一种可能的使用是允许函数去锁住一个互斥量,并将互斥量的所有权转移到调用者上,所以调用者可以在这个锁保护的范围内执行代码。

    std::unique_lock<std::mutex>get_lock(){
    +    extern std::mutex some_mutex;
    +    std::unique_lock<std::mutex>lk{ some_mutex };
    +    return lk;
    +
    +}
    +void process_data(){
    +    std::unique_lock<std::mutex>lk{ get_lock() };
    +    // 执行一些任务...
    +}
    +

    return lk 这里会调用移动构造,将互斥量的所有权转移给调用方, process_data 函数结束的时候会解锁互斥量。

    我相信你可能对 extern std::mutex some_mutex 有疑问,其实不用感到奇怪,这是一个互斥量的声明,可能别的翻译单元(或 dll 等)有它的定义,成功链接上。我们前面也说了:“所谓的在不同作用域传递互斥量,其实只是传递了它们的指针或者引用罢了”,所以要特别注意互斥量的生存期open in new window

    extern 说明符只能搭配变量声明和函数声明(除了类成员或函数形参)。它指定外部链接,而且技术上不影响存储期,但它不能用来定义自动存储期的对象,故所有 extern 对象都具有静态或线程存储期open in new window

    如果你简单写一个 std::mutex some_mutex 那么函数 process_data 中的 lk 会持有一个悬垂指针。

    举一个使用 extern std::mutex 的完整运行示例open in new window。当然,其实理论上你 new std::mutex 也是完全可行...... 🤣🤣

    std::unique_lock 是灵活的,同样允许在对象销毁之前就解锁互斥量,调用 unlock() 成员函数即可,不再强调。

    保护共享数据的初始化过程

    保护共享数据并非必须使用互斥量,互斥量只是其中一种常见的方式而已,对于一些特殊的场景,也有专门的保护方式,比如对于共享数据的初始化过程的保护。我们通常就不会用互斥量,这会造成很多的额外开销

    我们不想为各位介绍其它乱七八糟的各种保护初始化的方式,我们只介绍三种:双检锁(错误)使用 std::call_once静态局部变量初始化在 C++11 是线程安全

    1. 双检锁(错误)线程不安全

      void f(){
      +    if(!ptr){      // 1
      +        std::lock_guard<std::mutex>lk{ m };
      +        if(!ptr){  // 2
      +            ptr.reset(new some);  // 3
      +        }
      +    }
      +    ptr->do_something();  // 4
      +}
      +

      ① 是查看指针是否为空,空才需要初始化,才需要获取锁。指针为空,当获取锁后会再检查一次指针②(这就是双重检查),避免另一线程在第一次检查后再做初始化,并且让当前线程获取锁。

      然而这显然没用,因为有潜在的条件竞争。未被锁保护的读取操作①没有与其他线程里被锁保护的写入操作③进行同步,因此就会产生条件竞争。

      简而言之:一个线程知道另一个线程已经在执行③,但是此时还没有创建 some 对象,而只是分配内存对指针写入。那么这个线程在①的时候就不会进入,直接执行了 ptr->do_something()④,得不到正确的结果,因为对象还没构造。

      如果你觉得难以理解,那就记住 ptr.reset(new some); 并非是不可打断不可交换的固定指令。

      这种错误写法在一些单例中也非常的常见。如果你的同事或上司写出此代码,一般不建议指出,因为不见得你能教会他们,不要“没事找事”,只要不影响自己即可。

    2. C++ 标准委员会也认为处理此问题很重要,所以标准库提供了 std::call_onceopen in new windowstd::once_flagopen in new window 来处理这种情况。比起锁住互斥量并显式检查指针,每个线程只需要使用 std::call_once 就可以。使用 std::call_once 比显式使用互斥量消耗的资源更少,特别是当初始化完成之后

      std::shared_ptr<some>ptr;
      +std::mutex m;
      +std::once_flag resource_flag;
      +
      +void init_resource(){
      +    ptr.reset(new some);
      +}
      +
      +void foo(){
      +    std::call_once(resource_flag, init_resource); // 线程安全的一次初始化
      +    ptr->do_something();
      +}
      +

      以上代码 std::once_flag 对象是命名空间作用域声明,如果你有需要,它也可以是类的成员。用于搭配 std::call_once 使用,保证线程安全的一次初始化。std::call_once 只需要接受可调用 (Callable)open in new window对象即可,也不要求一定是函数。

      初始化”,自然是一次。但是 std::call_once 也有一些例外情况(比如异常)会让传入的可调用对象被多次调用,即“多次”初始化:

      std::once_flag flag;
      +int n = 0;
      +
      +void f(){
      +    std::call_once(flag, [] {
      +        ++n;
      +        std::cout << "第" << n << "次调用\n";
      +        throw std::runtime_error("异常");
      +    });
      +}
      +
      +int main(){
      +    try{
      +        f();
      +    }
      +    catch (std::exception&){}
      +    
      +    try{
      +        f();
      +    }
      +    catch (std::exception&){}
      +}
      +

      测试链接open in new window。正常情况会保证传入的可调用对象只调用一次,即初始化只有一次。异常之类的是例外。

    3. 静态局部变量初始化在 C++11 是线程安全

      class my_class;
      +my_class& get_my_class_instance(){
      +    static my_class instance;  // 线程安全的初始化过程 初始化严格发生一次
      +}
      +

      多线程可以安全的调用 get_my_class_instance 函数,不用为数据竞争而担心。此方式也在单例中多见,是简单合理的做法。


    其实还有不少其他的做法或者反例,但是觉得没必要再聊了,因为本文不是详尽的文档,而是“教程”。

    保护不常更新的数据结构

    试想一下,你有一个数据结构存储了用户的设置信息,每次用户打开程序的时候,都要进行读取,且运行时很多地方都依赖这个数据结构需要读取,所以为了效率,我们使用了多线程读写。这个数据结构很少进行改变,而我们知道,多线程读取,是没有数据竞争的,是安全的,但是有些时候又不可避免的有修改和读取都要工作的时候,所以依然必须得使用互斥量进行保护。

    然而使用 std::mutex 的开销是过大的,它不管有没有发生数据竞争(也就是就算全是读的情况)也必须是老老实实上锁解锁,只有一个线程可以运行。如果你学过其它语言或者操作系统,相信这个时候就已经想到了:“读写锁open in new window”。

    C++ 标准库自然为我们提供了: std::shared_timed_mutexopen in new window(C++14)、 std::shared_mutexopen in new window(C++17)。它们的区别简单来说,前者支持更多的操作方式,后者有更高的性能优势。

    std::shared_mutex 同样支持 std::lock_guardstd::unique_lock。和 std::mutex 做的一样,保证写线程的独占访问。而那些无需修改数据结构的读线程,可以使用 std::shared_lock<std::shared_mutex>open in new window 获取访问权,多个线程可以一起读取。

    class Settings {
    +private:
    +    std::map<std::string, std::string> data_;
    +    mutable std::shared_mutex mutex_; // “M&M 规则”:mutable 与 mutex 一起出现
    +
    +public:
    +    void set(const std::string& key, const std::string& value) {
    +        std::lock_guard<std::shared_mutex> lock(mutex_);
    +        data_[key] = value;
    +    }
    +
    +    std::string get(const std::string& key) const {
    +        std::shared_lock<std::shared_mutex> lock(mutex_);
    +        auto it = data_.find(key);
    +        return (it != data_.end()) ? it->second : ""; // 如果没有找到键返回空字符串
    +    }
    +};
    +

    完整代码open in new window测试open in new window链接。标准输出可能交错,但无数据竞争。

    std::shared_timed_mutex 具有 std::shared_mutex 的所有功能,并且额外支持超时功能。所以以上代码可以随意更换这两个互斥量。

    std::recursive_mutex

    线程对已经上锁的 std::mutex 再次上锁是错误的,这是未定义行为open in new window。然而在某些情况下,一个线程会尝试在释放一个互斥量前多次获取,所以提供了std::recursive_mutex

    std::recursive_mutex 是 C++ 标准库提供的一种互斥量类型,它允许同一线程多次锁定同一个互斥量,而不会造成死锁。当同一线程多次对同一个 std::recursive_mutex 进行锁定时,只有在解锁与锁定次数相匹配时,互斥量才会真正释放。但它并不影响不同线程对同一个互斥量进行锁定的情况。不同线程对同一个互斥量进行锁定时,会按照互斥量的规则进行阻塞

    #include <iostream>
    +#include <thread>
    +#include <mutex>
    +
    +std::recursive_mutex mtx;
    +
    +void recursive_function(int count) {
    +    // 递归函数,每次递归都会锁定互斥量
    +    mtx.lock();
    +    std::cout << "Locked by thread: " << std::this_thread::get_id() << ", count: " << count << std::endl;
    +    if (count > 0) {
    +        recursive_function(count - 1); // 递归调用
    +    }
    +    mtx.unlock(); // 解锁互斥量
    +}
    +
    +int main() {
    +    std::thread t1(recursive_function, 3);
    +    std::thread t2(recursive_function, 2);
    +
    +    t1.join();
    +    t2.join();
    +}
    +

    运行open in new window测试。

    我们重点的强调了一下这两个成员函数的这个概念,其实也很简单,总而言之就是 unlock 必须和 lock 的调用次数一样,才会真正解锁互斥量。

    同样的,我们也可以使用 std::lock_guardstd::unique_lock 帮我们管理 std::recursive_mutex,而非显式调用 lockunlock

    void recursive_function(int count) {
    +    std::lock_guard<std::recursive_mutex>lc{ mtx };
    +    std::cout << "Locked by thread: " << std::this_thread::get_id() << ", count: " << count << std::endl;
    +    if (count > 0) {
    +        recursive_function(count - 1);
    +    }
    +}
    +

    运行open in new window测试。

    newdelete 是线程安全的吗?

    如果你的标准达到 C++11,要求下列函数是线程安全的:

    所以以下函数在多线程运行是线程安全的:

    void f(){
    +    T* p = new T{};
    +    delete p;
    +}
    +

    内存分配、释放操作是线程安全,构造和析构不涉及共享资源。而局部对象 p 对于每个线程来说是独立的。换句话说,每个线程都有其自己的 p 对象实例,因此它们不会共享同一个对象,自然没有数据竞争。

    如果 p 是全局对象(或者外部的,只要可被多个线程读写),多个线程同时对其进行访问和修改时,就可能会导致数据竞争和未定义行为。因此,确保全局对象的线程安全访问通常需要额外的同步措施,比如互斥量或原子操作。

    T* p = nullptr;
    +void f(){
    +    p = new T{}; // 存在数据竞争
    +    delete p;
    +}
    +

    即使 p 是局部对象,如果构造函数(析构同理)涉及读写共享资源,那么一样存在数据竞争,需要进行额外的同步措施进行保护。

    int n = 1;
    +
    +struct X{
    +    X(int v){
    +        ::n += v;
    +    }
    +};
    +
    +void f(){
    +    X* p = new X{ 1 }; // 存在数据竞争
    +    delete p;
    +}
    +

    一个直观的展示是,我们可以在构造函数中使用 std::cout,看到无序的输出,例子open in new window


    值得注意的是,如果是自己重载 operator newoperator delete 替换了库的全局版本,那么它的线程安全就要我们来保证。

    // 全局的 new 运算符,替换了库的版本
    +void* operator new  (std::size_t count){
    +    return ::operator new(count); 
    +}
    +

    以上代码是线程安全的,因为 C++11 保证了 new 运算符的库版本,即 ::operator new 是线程安全的,我们直接调用它自然不成问题。如果你需要更多的操作,就得使用互斥量之类的方式保护了。


    总而言之,new 表达式线程安全要考虑三方面:operator new、构造函数、修改指针。

    delete 表达式线程安全考虑两方面:operator delete、析构函数。

    C++ 只保证了 operator newoperator delete 这两个方面的线程安全(不包括用户定义的),其它方面就得自己保证了。前面的内容也都提到了。

    总结

    本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题。并说明了可以使用互斥量(std::mutex)保护共享数据,并且要注意互斥量上锁的“粒度”。C++标准库提供了很多工具,包括管理互斥量的管理类(std::lock_guard),但是互斥量只能解决它能解决的问题,并且它有自己的问题(死锁)。同时我们讲述了一些避免死锁的方法和技术。还讲了一下互斥量所有权转移。然后讨论了面对不同情况保护共享数据的不同方式,使用 std::call_once() 保护共享数据的初始化过程,使用读写锁(std::shared_mutex)保护不常更新的数据结构。以及特殊情况可能用到的互斥量 recursive_mutex,有些人可能喜欢称作:递归锁。最后聊了一下 newdelete 运算符的库函数实际是线程安全的,以及一些问题。

    下一章,我们将开始讲述同步操作,会使用到 Futuresopen in new window条件变量open in new window等设施。


    1. "临界区open in new window"指的是一个访问共享资源的程序片段,而这些共享资源又无法同时被多个线程访问的特性。在临界区中,通常会使用同步机制,比如我们要讲的互斥量(Mutex)。 ↩︎

    + + + diff --git "a/md/04\345\220\214\346\255\245\346\223\215\344\275\234.html" "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.html" new file mode 100644 index 00000000..a1c98e61 --- /dev/null +++ "b/md/04\345\220\214\346\255\245\346\223\215\344\275\234.html" @@ -0,0 +1,421 @@ + + + + + + + + + + 同步操作 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    同步操作

    大约 19 分钟

    同步操作

    "同步操作"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在同步操作中,各个任务之间通常需要相互协调和等待,以确保数据的一致性和正确性

    本章的主要内容有:

    • 条件变量

    • std::future 等待异步任务

    • 在规定时间内等待

    本章将讨论如何使用条件变量等待事件,介绍 future 等标准库设施用作同步操作。

    等待事件或条件

    假设你正在一辆夜间运行的地铁上,那么你要如何在正确的站点下车呢?

    1. 一直不休息,每一站都能知道,这样就不会错过你要下车的站点,但是这会很疲惫。

    2. 可以看一下时间,估算一下地铁到达目的地的时间,然后设置一个稍早的闹钟,就休息。这个方法听起来还行,但是你可能被过早的叫醒,甚至估算错误导致坐过站,又或者闹钟没电了睡过站。

    3. 事实上最简单的方式是,到站的时候有人或者其它东西能将你叫醒(比如手机的地图,到达设置的位置就提醒)。

    这和线程有什么关系呢?其实第一种方法就是在说”忙等待open in new window(busy waiting)”也称“自旋“。

    bool flag = false;
    +std::mutex m;
    +
    +void wait_for_flag(){
    +    std::unique_lock<std::mutex>lk{ m };
    +    while (!flag){
    +        lk.unlock();    // 1 解锁互斥量
    +        lk.lock();      // 2 上锁互斥量
    +    }
    +}
    +

    第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。循环中,休眠②前函数对互斥量解锁①,再休眠结束后再对互斥量上锁,让另外的线程有机会获取锁并设置标识(因为修改函数和等待函数共用一个互斥量)。

    void wait_for_flag(){
    +    std::unique_lock<std::mutex>lk{ m };
    +    while (!flag){
    +        lk.unlock();    // 1 解锁互斥量
    +        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 2 休眠
    +        lk.lock();      // 3 上锁互斥量
    +    }
    +}
    +

    第三种方式(也是最好的)实际上就是使用条件变量了。通过另一线程触发等待事件的机制是最基本的唤醒方式,这种机制就称为“条件变量”。

    C++ 标准库对条件变量有两套实现:std::condition_variableopen in new windowstd::condition_variable_anyopen in new window,这两个实现都包含在 <condition_variable>open in new window 头文件中。

    condition_variable_any 类是 std::condition_variable 的泛化。相对于只在 std::unique_lock<std::mutex> 上工作的 std::condition_variablecondition_variable_any 能在任何满足可基本锁定(BasicLockable)open in new window要求的锁上工作,所以增加了 _any 后缀。显而易见,这种区分必然是 any更加通用但是却又更多的性能开销。所以通常首选 std::condition_variable。有特殊需求,才会考虑 std::condition_variable_any

    std::mutex mtx;
    +std::condition_variable cv;
    +bool arrived = false;
    +
    +void waitForArrival() {
    +    std::unique_lock<std::mutex> lck(mtx);
    +    cv.wait(lck, []{ return arrived; }); // 等待 arrived 变为 true
    +    std::cout << "到达目的地,可以下车了!" << std::endl;
    +}
    +
    +void simulateArrival() {
    +    std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟地铁到站,假设5秒后到达目的地
    +    {
    +        std::lock_guard<std::mutex> lck(mtx);
    +        arrived = true; // 设置条件变量为 true,表示到达目的地
    +    }
    +    cv.notify_one(); // 通知等待的线程
    +}
    +

    运行open in new window测试。更换为 std::condition_variable_any 效果相同open in new window

    • std::mutex mtx: 创建了一个互斥量,用于保护共享数据的访问,确保在多线程环境下的数据同步。

    • std::condition_variable cv: 创建了一个条件变量,用于线程间的同步,当条件不满足时,线程可以等待,直到条件满足时被唤醒。

    • bool arrived = false: 设置了一个标志位,表示是否到达目的地。

    waitForArrival 函数中:

    1. std::unique_lock<std::mutex> lck(mtx): 使用互斥量创建了一个独占锁。

    2. cv.wait(lck, []{ return arrived; }): 阻塞当前线程,释放(unlock)锁,直到条件被满足。

    3. 一旦条件满足,即 arrived 变为 true,并且条件变量 cv唤醒(包括虚假唤醒),那么当前线程会重新获取锁(lock),并执行后续的操作。

    simulateArrival 函数中:

    1. std::this_thread::sleep_for(std::chrono::seconds(5)): 模拟地铁到站,暂停当前线程 5 秒。

    2. 设置 arrived 为 true,表示到达目的地。

    3. cv.notify_one(): 唤醒一个等待条件变量的线程。

    这样,当 simulateArrival 函数执行后,arrived 被设置为 true,并且通过 cv.notify_one() 唤醒了等待在条件变量上的线程,从而使得 waitForArrival 函数中的等待结束,可以执行后续的操作,即输出提示信息。


    条件变量的 waitopen in new window 成员函数有两个版本,以上代码使用的就是第二个版本,传入了一个谓词open in new window

    void wait(std::unique_lock<std::mutex>& lock);                 // 1
    +
    +template<class Predicate>
    +void wait(std::unique_lock<std::mutex>& lock, Predicate pred); // 2
    +

    ②等价于:

    while (!pred())
    +    wait(lock);
    +

    这可以避免“虚假唤醒(spurious wakeup)open in new window”。

    条件变量虚假唤醒是指在使用条件变量进行线程同步时,有时候线程可能会在没有收到通知的情况下被唤醒。问题取决于程序和系统的具体实现。解决方法很简单,在循环中等待并判断条件可一并解决。使用 C++ 标准库则没有这个烦恼了。

    线程安全的队列

    在本节中,我们介绍了一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点:

    1. 当执行 push 操作时,需要确保没有其他线程正在执行 pushpop 操作;同样,在执行 pop 操作时,也需要确保没有其他线程正在执行 pushpop 操作。

    2. 当队列为时,不应该执行 pop 操作。因此,我们需要使用条件变量来传递一个谓词,以确保在执行 pop 操作时队列不为空。

    基于以上思考,我们设计了一个名为 threadsafe_queue 的模板类,如下:

    template<typename T>
    +class threadsafe_queue {
    +    mutable std::mutex m;              // 互斥量,用于保护队列操作的独占访问
    +    std::condition_variable data_cond; // 条件变量,用于在队列为空时等待
    +    std::queue<T> data_queue;          // 实际存储数据的队列
    +public:
    +    threadsafe_queue() {}
    +    void push(T new_value) {
    +        {
    +            std::lock_guard<std::mutex>lk(m);
    +            data_queue.push(new_value);
    +        }
    +        data_cond.notify_one();
    +    }
    +    // 从队列中弹出元素(阻塞直到队列不为空)
    +    void pop(T& value) {
    +        std::unique_lock<std::mutex>lk(m);
    +        data_cond.wait(lk, [this] {return !data_queue.empty(); });
    +        value = data_queue.front();
    +        data_queue.pop();
    +    }
    +    // 从队列中弹出元素(阻塞直到队列不为空),并返回一个指向弹出元素的 shared_ptr
    +    std::shared_ptr<T> pop() {
    +        std::unique_lock<std::mutex>lk(m);
    +        data_cond.wait(lk, [this] {return !data_queue.empty(); });
    +        std::shared_ptr<T>res(std::make_shared<T>(data_queue.front()));
    +        data_queue.pop();
    +        return res;
    +    }
    +    bool empty()const {
    +        std::lock_guard<std::mutex>lk(m);
    +        return data_queue.empty();
    +    }
    +};
    +

    请无视我们省略的构造、赋值、交换、try_xx 等操作。以上示例已经足够。

    光写好了肯定不够,我们还得测试运行,我们可以写一个经典的:”生产者消费者模型“,也就是一个线程 push生产“,一个线程 pop消费“。

    void producer(threadsafe_queue<int>& q) {
    +    for (int i = 0; i < 5; ++i) {
    +        q.push(i);
    +    }
    +}
    +void consumer(threadsafe_queue<int>& q) {
    +    for (int i = 0; i < 5; ++i) {
    +        int value{};
    +        q.pop(value);
    +    }
    +}
    +

    两个线程分别运行 producerconsumer,为了观测运行我们可以为 pushpop 中增加打印语句:

    std::cout << "push:" << new_value << std::endl;
    +std::cout << "pop:" << value << std::endl;
    +

    可能运行open in new window结果是:

    push:0
    +pop:0
    +push:1
    +pop:1
    +push:2
    +push:3
    +push:4
    +pop:2
    +pop:3
    +pop:4
    +

    这很正常,到底哪个线程会抢到 CPU 时间片持续运行,是系统调度决定的,我们只需要保证一开始提到的两点就行了:

    pushpop 都只能单独执行;当队列为时,不执行 pop 操作。

    我们可以给一个简单的示意图帮助你理解这段运行结果:

    初始状态:队列为空
    ++---+---+---+---+---+
    +
    +Producer 线程插入元素 0:
    ++---+---+---+---+---+
    +| 0 |   |   |   |   |
    +
    +Consumer 线程弹出元素 0:
    ++---+---+---+---+---+
    +|   |   |   |   |   |
    +
    +Producer 线程插入元素 1:
    ++---+---+---+---+---+
    +| 1 |   |   |   |   |
    +
    +Consumer 线程弹出元素 1:
    ++---+---+---+---+---+
    +|   |   |   |   |   |
    +
    +Producer 线程插入元素 2:
    ++---+---+---+---+---+
    +|   | 2 |   |   |   |
    +
    +Producer 线程插入元素 3:
    ++---+---+---+---+---+
    +|   | 2 | 3 |   |   |
    +
    +Producer 线程插入元素 4:
    ++---+---+---+---+---+
    +|   | 2 | 3 | 4 |   |
    +
    +Consumer 线程弹出元素 2:
    ++---+---+---+---+---+
    +|   |   | 3 | 4 |   |
    +
    +Consumer 线程弹出元素 3:
    ++---+---+---+---+---+
    +|   |   |   | 4 |   |
    +
    +Consumer 线程弹出元素 4:
    ++---+---+---+---+---+
    +|   |   |   |   |   |
    +
    +队列为空,所有元素已被弹出
    +

    到此,也就可以了。

    使用 future

    其实就是异步

    举个例子:我们在车站等车,你可能会做一些别的事情打发时间,比如学习现代 C++ 模板教程open in new window、观看 mq白open in new window 的视频教程、玩手机等。不过,你始终在等待一件事情:车到站

    C++ 标准库将这种事件称为 futureopen in new window。它用于处理线程中需要等待某个事件的情况,线程知道预期结果。等待的同时也可以执行其它的任务。

    C++ 标准库有两种 future,都声明在 <future>open in new window 头文件中:独占的 std::futureopen in new window 、共享的 std::shared_futureopen in new window。它们的区别与 std::unique_ptrstd::shared_ptr 类似。std::future 只能与单个指定事件关联,而 std::shared_future 能关联多个事件。它们都是模板,它们的模板类型参数,就是其关联的事件(函数)的返回类型。当多个线程需要访问一个独立 future 对象时, 必须使用互斥量或类似同步机制进行保护。而多个线程访问同一共享状态,若每个线程都是通过其自身的 shared_future 对象副本进行访问,则是安全的。

    最简单的作用是,我们先前讲的 std::thread 执行任务是没有返回值的,这个问题就能使用 future 解决。

    创建异步任务获取返回值

    假设需要执行一个耗时任务并获取其返回值,但是并不急切的需要它。那么就可以启动新线程计算,然而 std::thread 没提供直接接收返回值的机制。所以我们可以使用 std::asyncopen in new window 函数模板。

    使用 std::async 启动一个异步任务,它会返回一个 std::future 对象,这个对象和任务关联,将持有最终计算出来的结果。当需要这个值的时候,只需要调用 get()open in new window 成员函数,就会阻塞直到 future 为就绪为止(即任务执行完毕),返回执行结果。

    #include <iostream>
    +#include <thread>
    +#include <future>
    +
    +int task(int n){
    +    std::cout << "异步任务 ID: " << std::this_thread::get_id() << '\n';
    +    return n * n;
    +}
    +
    +int main(){
    +    std::future<int> future = std::async(task, 10);
    +    std::cout << "main\n";
    +    std::cout << future.get() << '\n';
    +}
    +

    运行open in new window测试。

    std::thread 一样,std::async 支持任意可调用(Callable)open in new window对象,以及传递调用参数。包括支持使用 std::ref ,以及移动的问题。我们下面详细聊一下 std::async 参数传递的事。

    struct X{
    +    int operator()(int n)const{
    +        return n * n;
    +    }
    +};
    +struct Y{
    +    int f(int n)const{
    +        return n * n;
    +    }
    +};
    +void f(int& p) { std::cout << &p << '\n'; }
    +
    +int main(){
    +    Y y;
    +    int n = 0;
    +    auto t1 = std::async(X{}, 10);
    +    auto t2 = std::async(&Y::f,&y,10);
    +    auto t3 = std::async([] {});         
    +    auto t4 = std::async(f, std::ref(n));
    +    std::cout << &n << '\n';
    +}
    +

    运行open in new window测试。

    如你所见,它支持所有可调用(Callable)open in new window对象,并且也是默认拷贝,必须使用 std::ref 才能传递引用。并且它和 std::thread 一样,内部会将保有的参数副本转换为右值表达式进行传递,这是为了那些只支持移动的类型,左值引用没办法引用右值表达式,所以如果不使用 std::ref,这里 void f(int&) 就会导致编译错误,如果是 void f(const int&) 则可以通过编译,不过引用的不是我们传递的局部对象。

    void f(const int& p) {}
    +void f2(int& p ){}
    +
    +int n = 0;
    +std::async(f, n);   // OK! 可以通过编译,不过引用的并非是局部的n
    +std::async(f2, n);  // Error! 无法通过编译
    +

    我们来展示使用 std::move ,也就移动传递参数:

    struct move_only {
    +    move_only() { std::puts("默认构造"); }
    +    move_only(const move_only&) = delete;
    +    move_only(move_only&&)noexcept {
    +        std::puts("移动构造");
    +    }
    +};
    +
    +void task(move_only x){
    +    std::cout << "异步任务 ID: " << std::this_thread::get_id() << '\n';
    +}
    +
    +int main(){
    +    move_only x;
    +    std::future<void> future = std::async(task, std::move(x));
    +    std::this_thread::sleep_for(std::chrono::milliseconds(1));
    +    std::cout << "main\n";
    +    future.wait();  // 等待异步任务执行完毕
    +}
    +

    运行open in new window测试。

    如你所见,它支持只移动类型,我们将参数使用 std::move 传递。


    接下来我们聊 std::async 的执行策略,我们前面一直没有使用,其实就是在传递可调用对象与参数之前传递枚举值罢了:

    1. std::launch::async 在不同线程上执行异步任务。
    2. std::launch::deferred 惰性求值,不创建线程,等待 future 对象调用 waitget 成员函数的时候执行任务。

    而我们先前没有写明这个参数,实际上是默认std::launch::async | std::launch::deferred ,也就是说由实现选择到底是否创建线程执行异步任务。我们来展示一下:

    void f(){
    +    std::cout << std::this_thread::get_id() << '\n';
    +}
    +
    +int main(){
    +    std::cout << std::this_thread::get_id() << '\n';
    +    auto f1 = std::async(std::launch::deferred, f);
    +    f1.wait(); // 在 wait 或 get() 调用时执行,不创建线程
    +    auto f2 = std::async(std::launch::async,f); // 创建线程执行异步任务
    +    auto f3 = std::async(std::launch::deferred | std::launch::async, f); // 实现选择的执行方式
    +}
    +

    运行open in new window测试。


    其实到此基本就差不多了,我们再介绍两个常见问题即可:

    1. 如果从 std::async 获得的 std::futureopen in new window 没有被移动或绑定到引用,那么在完整表达式结尾, std::futureopen in new window析构函数将阻塞到异步计算完成

      std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f()
      +std::async(std::launch::async, []{ g(); }); // f() 完成前不开始
      +

      如你所见,这并不能创建异步任务,会堵塞,然后逐个执行。

    2. 被移动的 std::future 没有所有权,失去共享状态,不能调用 getwait 成员函数。

      auto t = std::async([] {});
      +std::future<void> future{ std::move(t) };
      +t.wait();   // Error! 抛出异常
      +

      如同没有线程资源所有权的 std::thread 对象调用 join() 一样错误,这是移动语义的基本语义逻辑。

    futurestd::packaged_task

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

    通常它会和 std::future 一起使用,不过也可以单独使用,我们一步一步来:

    std::packaged_task<double(int, int)> task([](int a, int b){
    +    return std::pow(a, b);
    +});
    +task(10, 2); // 执行传递的 lambda,但无法获取返回值
    +

    它有 operator()open in new window 的重载,它会执行我们传递的可调用(Callable)open in new window对象,不过这个重载的返回类型是 void 没办法获取返回值

    如果想要异步的获取返回值,我们需要在调用 operator() 之前,让它和 future 关联,然后使用 future.get(),也就是:

    std::packaged_task<double(int, int)> task([](int a, int b){
    +    return std::pow(a, b);
    +});
    +std::future<double>future = task.get_future();
    +task(10, 2); // 此处执行任务
    +std::cout << future.get() << '\n'; // 不堵塞,此处获取返回值
    +

    运行open in new window测试。

    先关联任务,再执行任务,当我们想要获取任务的返回值的时候,就 future.get() 即可。值得注意的是,任务并不会在线程中执行,想要在线程中执行异步任务,然后再获取返回值,我们可以这么做:

    std::packaged_task<double(int, int)> task([](int a, int b){
    +    return std::pow(a, b);
    +});
    +std::future<double>future = task.get_future();
    +std::thread t{ std::move(task),10,2 }; // 任务在线程中执行
    +t.join();
    +
    +std::cout << future.get() << '\n'; // 并不堵塞,获取任务返回值罢了
    +

    运行open in new window测试。

    因为 task 本身是重载了 operator() 的,是可调用对象,自然可以传递给 std::thread 执行,以及传递调用参数。唯一需要注意的是我们使用了 std::move ,这是因为 std::packaged_task 只能移动,不能复制。


    简而言之,其实 std::packaged_task 也就是一个“包装”类而已,它本身并没什么特殊的,老老实实执行我们传递的任务,且方便我们获取返回值罢了,明确这一点,那么一切都不成问题。

    std::packaged_task 也可以在线程中传递,在需要的时候获取返回值,而非像上面那样将它自己作为可调用对象:

    template<typename R, typename...Ts, typename...Args>
    +    requires std::invocable<std::packaged_task<R(Ts...)>&, Args...> 
    +void async_task(std::packaged_task<R(Ts...)>& task, Args&&...args) {
    +    // todo..
    +    task(std::forward<Args>(args)...);
    +}
    +
    +int main() {
    +    std::packaged_task<int(int,int)> task([](int a,int b){
    +        return a + b;
    +    });
    +    
    +    int value = 50;
    +    std::future<int> future = task.get_future();
    +    // 创建一个线程来执行异步任务
    +    std::thread t{ [&] {async_task(task, value, value); } };
    +    std::cout << future.get() << '\n';
    +    t.join();
    +}
    +

    运行open in new window测试。

    我们套了一个 lambda,这是因为函数模板不是函数,它并非具体类型,没办法直接被那样传递使用,只能包一层了。这只是一个简单的示例,展示可以使用 std::packaged_task 作函数形参,然后我们来传递任务进行异步调用等操作。

    我们再将第二章实现的并行 sum 改成 std::package_task + std::future 的形式:

    template<typename ForwardIt>
    +auto sum(ForwardIt first, ForwardIt last) {
    +    using value_type = std::iter_value_t<ForwardIt>;
    +    std::size_t num_threads = std::thread::hardware_concurrency();
    +    std::ptrdiff_t distance = std::distance(first, last);
    +
    +    if (distance > 1024000) {
    +        // 计算每个线程处理的元素数量
    +        std::size_t chunk_size = distance / num_threads;
    +        std::size_t remainder = distance % num_threads;
    +
    +        // 存储每个线程要执行的任务
    +        std::vector<std::packaged_task<value_type()>>tasks;
    +        // 和每一个任务进行关联的 future 用于获取返回值
    +        std::vector<std::future<value_type>>futures(num_threads);
    +
    +        // 存储关联线程的线程对象
    +        std::vector<std::thread> threads;
    +
    +        // 制作任务、与 future 关联、启动线程执行
    +        auto start = first;
    +        for (std::size_t i = 0; i < num_threads; ++i) {
    +            auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0));
    +            tasks.emplace_back(std::packaged_task<value_type()>{[start, end, i] {
    +                return std::accumulate(start, end, value_type{});
    +            }});
    +            start = end; // 开始迭代器不断向前
    +            futures[i] = tasks[i].get_future(); // 任务与 std::future 关联
    +            threads.emplace_back(std::move(tasks[i]));
    +        }
    +
    +        // 等待所有线程执行完毕
    +        for (auto& thread : threads)
    +            thread.join();
    +
    +        // 汇总线程的计算结果
    +        value_type total_sum {};
    +        for (std::size_t i = 0; i < num_threads; ++i) {
    +            total_sum += futures[i].get();
    +        }
    +        return total_sum;
    +    }
    +
    +    value_type total_sum = std::accumulate(first, last, value_type{});
    +    return total_sum;
    +}
    +

    运行open in new window测试。

    相比于之前,其实不同无非是定义了 std::vector<std::packaged_task<value_type()>> tasksstd::vector<std::future<value_type>> futures ,然后在循环中制造任务插入容器,关联 tuple,再放到线程中执行。最后汇总的时候写一个循环,futures[i].get() 获取任务的返回值加起来即可。

    到此,也就可以了。

    使用 std::promise

    类模板 std::promiseopen in new window 用于存储一个值或一个异常,之后通过 std::promise 对象所创建的 std::futureopen in new window 对象异步获得。

    // 计算函数,接受一个整数并返回它的平方
    +void calculate_square(std::promise<int> promiseObj, int num) {
    +    // 模拟一些计算
    +    std::this_thread::sleep_for(std::chrono::seconds(1));
    +
    +    // 计算平方并设置值到 promise 中
    +    promiseObj.set_value(num * num);
    +}
    +
    +// 创建一个 promise 对象,用于存储计算结果
    +std::promise<int> promise;
    +
    +// 从 promise 获取 future 对象进行关联
    +std::future<int> future = promise.get_future();
    +
    +// 启动一个线程进行计算
    +int num = 5;
    +std::thread t(calculate_square, std::move(promise), num);
    +
    +// 阻塞,直到结果可用
    +int result = future.get();
    +std::cout << num << " 的平方是:" << result << std::endl;
    +
    +t.join();
    +

    运行open in new window测试。

    我们在新线程中通过调用 set_value()open in new window 函数设置 promise 的值,并在主线程中通过与其关联的 future 对象的 get() 成员函数获取这个值,如果promise的值还没有被设置,那么将阻塞当前线程,直到被设置为止。同样的 std::promise 只能移动,不可复制,所以我们使用了 std::move 进行传递。


    除了 set_value() 函数外,std::promise 还有一个 set_exception()open in new window 成员函数,它接受一个 std::exception_ptropen in new window 类型的参数,这个参数通常通过 std::current_exception()open in new window 获取,用于指示当前线程中抛出的异常。然后,std::future 对象通过 get() 函数获取这个异常,如果 promise 所在的函数有异常被抛出,则 std::future 对象会重新抛出这个异常,从而允许主线程捕获并处理它。

    void throw_function(std::promise<int> prom) {
    +    try {
    +        throw std::runtime_error("一个异常");
    +    }
    +    catch (...) {
    +        prom.set_exception(std::current_exception());
    +    }
    +}
    +
    +int main() {
    +    std::promise<int> prom;
    +    std::future<int> fut = prom.get_future();
    +
    +    std::thread t(throw_function, std::move(prom));
    +
    +    try {
    +        std::cout << "等待线程执行,抛出异常并设置\n";
    +        fut.get();
    +    }
    +    catch (std::exception& e) {
    +        std::cerr << "来自线程的异常: " << e.what() << '\n';
    +    }
    +    t.join();
    +}
    +

    运行结果open in new window

    等待线程执行,抛出异常并设置
    +来自线程的异常: 一个异常
    +

    你可能对这段代码还有一些疑问:我们写的是 promised<int> ,但是却没有使用 set_value 设置值,你可能会想着再写一行 prom.set_value(0)

    共享状态的 promise 已经存储值或者异常,再次调用 set_valueset_exception) 会抛出 std::future_erroropen in new window 异常,将错误码设置为 promise_already_satisfiedopen in new window。这是因为 std::promise 对象只能是存储值或者异常其中一种,而无法共存

    简而言之,set_valueset_exception 二选一,如果先前调用了 set_value ,就不可再次调用 set_exception,反之亦然(不然就会抛出异常),示例如下:

    void throw_function(std::promise<int> prom) {
    +    prom.set_value(100);
    +    try {
    +        throw std::runtime_error("一个异常");
    +    }
    +    catch (...) {
    +        try{
    +            // 共享状态的 promise 已存储值,调用 set_exception 产生异常
    +            prom.set_exception(std::current_exception());
    +        }catch (std::exception& e){
    +            std::cerr << "来自 set_exception 的异常: " << e.what() << '\n';
    +        }
    +    }
    +}
    +
    +int main() {
    +    std::promise<int> prom;
    +    std::future<int> fut = prom.get_future();
    +
    +    std::thread t(throw_function, std::move(prom));
    +    
    +    std::cout << "等待线程执行,抛出异常并设置\n";
    +    std::cout << "值:" << fut.get() << '\n'; // 100
    +
    +    t.join();
    +}
    +

    运行结果open in new window

    等待线程执行,抛出异常并设置
    +值:100
    +来自 set_exception 的异常: promise already satisfied
    +

    多个线程的等待

    之前的例子中都在用 std::future ,不过 std::future 也有局限性。很多线程在等待的时候,只有一个线程能获取结果。当多个线程等待相同事件的结果时,就需要使用 std::shared_future 来替代 std::future 了。

    + + + diff --git a/md/index.html b/md/index.html new file mode 100644 index 00000000..bf14d47d --- /dev/null +++ b/md/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Md | 现代C++并发编程教程 + + + + + + + + + diff --git "a/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.html" "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.html" new file mode 100644 index 00000000..4e45bc27 --- /dev/null +++ "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/01thread\347\232\204\346\236\204\351\200\240\344\270\216\346\272\220\347\240\201\350\247\243\346\236\220.html" @@ -0,0 +1,92 @@ + + + + + + + + + + std::thread 的构造-源码解析 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    std::thread 的构造-源码解析

    大约 7 分钟

    std::thread 的构造-源码解析

    我们这单章是为了专门解释一下 C++11 引入的 std::thread 是如何构造的,是如何创建线程传递参数的,让你彻底了解这个类。

    我们以 MSVC 实现的 std::threadopen in new window 代码进行讲解,MSVC STL 很早之前就不支持 C++11 了,它的实现完全基于 C++14,出于某些原因 C++17 的一些库(如 invokeopen in new window, _v 变量模板)被向后移植到了 C++14 模式,所以即使是 C++11 标准库设施,实现中可能也是使用到了 C++14、17 的东西。

    std::thread 的数据成员

    • 了解一个庞大的类,最简单的方式就是先看它的数据成员有什么

    std::thread 只保有一个私有数据成员 _Thropen in new window

    private:
    +    _Thrd_t _Thr;
    +

    _Thrd_topen in new window 是一个结构体,它保有两个数据成员:

    using _Thrd_id_t = unsigned int;
    +struct _Thrd_t { // thread identifier for Win32
    +    void* _Hnd; // Win32 HANDLE
    +    _Thrd_id_t _Id;
    +};
    +

    结构很明确,这个结构体的 _Hnd 成员是指向线程的句柄,_Id 成员就是保有线程的 ID。

    在64 位操作系统,因为内存对齐,指针 8 ,无符号 int 4,这个结构体 _Thrd_t 就是占据 16 个字节。也就是说 sizeof(std::thread) 的结果应该为 16

    std::thread 的构造函数

    std::thread 有四个构造函数open in new window,分别是:

    1. 默认构造函数,构造不关联线程的新 std::thread 对象。

      thread() noexcept : _Thr{} {}
      +

      值初始化open in new window了数据成员 _Thr ,这里的效果相当于给其成员 _Hnd_Id 都进行零初始化open in new window

    2. 移动构造函数,转移线程的所有权,构造 other 关联的执行线程的 std::thread 对象。此调用后 other 不再表示执行线程失去了线程的所有权。

      thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}
      +

      _STDopen in new window 是一个宏,展开就是 ::std::,也就是 ::std::exchangeopen in new window,将 _Other._Thr 赋为 {} (也就是置空),返回 _Other._Thr 的旧值用以初始化当前对象的数据成员 _Thr

    3. 复制构造函数被定义为弃置的,std::thread 不可复制。两个 std::thread 不可表示一个线程,std::thread 对线程资源是独占所有权。

      thread(const thread&) = delete;
      +
    4. 构造新的 std::thread 对象并将它与执行线程关联。表示新的执行线程开始执行

      template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
      +    _NODISCARD_CTOR_THREAD explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
      +        _Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
      +    }
      +

    前三个构造函数都没啥要特别聊的,非常简单,只有第四个构造函数较为复杂,且是我们本章重点,需要详细讲解。(注意 MSVC 使用标准库的内容很多时候不加 std::,脑补一下就行

    如你所见,这个构造函数本身并没有做什么,它只是一个可变参数成员函数模板,增加了一些 SFINAEopen in new window 进行约束我们传入的可调用open in new window对象的类型不能是 std::thread。函数体中调用了一个函数 _Startopen in new window,将我们构造函数的参数全部完美转发,去调用它,这个函数才是我们的重点,如下:

    template <class _Fn, class... _Args>
    +void _Start(_Fn&& _Fx, _Args&&... _Ax) {
    +    using _Tuple                 = tuple<decay_t<_Fn>, decay_t<_Args>...>;
    +    auto _Decay_copied           = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
    +    constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
    +
    +    _Thr._Hnd =
    +        reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
    +
    +    if (_Thr._Hnd) { // ownership transferred to the thread
    +        (void) _Decay_copied.release();
    +    } else { // failed to start thread
    +        _Thr._Id = 0;
    +        _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
    +    }
    +}
    +
    1. 它也是一个可变参数成员函数模板,接受一个可调用对象 _Fn 和一系列参数 _Args... ,这些东西用来创建一个线程。

    2. using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>

    3. auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...)

      • 使用 make_uniqueopen in new window 创建了一个独占指针,指向的是 _Tuple 类型的对象,存储了传入的函数对象和参数的副本
    4. constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{})

      • 调用 _Get_invokeopen in new window 函数,传入 _Tuple 类型和一个参数序列的索引序列(为了遍历形参包)。这个函数用于获取一个函数指针,指向了一个静态成员函数 _Invokeopen in new window,用来实际执行线程。这两个函数都非常的简单,我们来看看:
       template <class _Tuple, size_t... _Indices>
      + _NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept {
      +     return &_Invoke<_Tuple, _Indices...>;
      + }
      + 
      + template <class _Tuple, size_t... _Indices>
      + static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ {
      +     // adapt invoke of user's callable object to _beginthreadex's thread procedure
      +     const unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals));
      +     _Tuple& _Tup = *_FnVals.get(); // avoid ADL, handle incomplete types
      +     _STD invoke(_STD move(_STD get<_Indices>(_Tup))...);
      +     _Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABI
      +     return 0;
      + }
      +

      _Get_invoke 函数很简单,就是接受一个元组类型,和形参包的索引,传递给 _Invoke 静态成员函数模板,实例化,获取它的函数指针。

      它的形参类型我们不再过多介绍,你只需要知道 index_sequenceopen in new window 这个东西可以用来接收一个由 make_index_sequence 创建的索引形参包,帮助我们进行遍历元组即可。示例代码open in new window

      _Invoke 是重中之重,它是线程实际执行的函数,如你所见它的形参类型是 void* ,这是必须的,要符合 _beginthreadex 执行函数的类型要求。虽然是 void*,但是我可以将它转换为 _Tuple* 类型,构造一个独占智能指针,然后调用 get() 成员函数获取底层指针,解引用指针,得到元组的引用初始化_Tup

      此时,我们就可以进行调用了,使用 std::invokeopen in new window + std::move(默认移动) ,这里有一个形参包展开,_STD get<_Indices>(_Tup))...,_Tup 就是 std::tuple 的引用,我们使用 std::get<> 获取元组存储的数据,需要传入一个索引,这里就用到了 _Indices。展开之后,就等于 invoke 就接受了我们构造 std::thread 传入的可调用对象,调用可调用对象的参数,invoke 就可以执行了。

    5. _Thr._Hnd = reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id))

      • 调用 _beginthreadexopen in new window 函数来启动一个线程,并将线程句柄存储到 _Thr._Hnd 中。传递给线程的参数为 _Invoker_proc(一个静态函数指针,就是我们前面讲的 _Invoke)和 _Decay_copied.get()(存储了函数对象和参数的副本的指针)。
    6. if (_Thr._Hnd) {

      • 如果线程句柄 _Thr._Hnd 不为空,则表示线程已成功启动,将独占指针的所有权转移给线程。
    7. (void) _Decay_copied.release()

      • 释放独占指针的所有权,因为已经将参数传递给了线程。
    8. } else { // failed to start thread

      • 如果线程启动失败,则进入这个分支
    9. _Thr._Id = 0;

      • 将线程ID设置为0。
    10. _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);

      • 抛出一个 C++ 错误,表示资源不可用,请再次尝试。

    总结

    需要注意,libstdc++ 和 libc++ 可能不同,就比如它们 64 位环境下 sizeof(std::thread) 的结果就可能是 8。libstdc++ 的实现只保有一个 std::thread::idopen in new window参见open in new window。不过实测 gcc 不管是 win32 还是 POSIX 线程模型,线程对象的大小都是 8,宏 _GLIBCXX_HAS_GTHREADS 的值都为 1(GThreadopen in new window)。

     class thread
    +  {
    +  public:
    +#ifdef _GLIBCXX_HAS_GTHREADS
    +    using native_handle_type = __gthread_t;
    +#else
    +    using native_handle_type = int;
    +#endif
    +

    __gthread_tvoid*

    我们这里的源码解析涉及到的 C++ 技术很多,我们也没办法每一个都单独讲,那会显得文章很冗长,而且也不是重点。

    相信你也感受到了,不会模板,你阅读标准库源码,是无稽之谈,市面上很多教程教学,教导一些实现容器,过度简化了,真要去出错了去看标准库的代码,那是不现实的。不需要模板的水平有多高,也不需要会什么元编程,但是基本的需求得能做到,得会,这里推荐一下:现代C++模板教程open in new window

    + + + diff --git "a/md/\350\257\246\347\273\206\345\210\206\346\236\220/02scoped_lock\346\272\220\347\240\201\350\247\243\346\236\220.html" "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/02scoped_lock\346\272\220\347\240\201\350\247\243\346\236\220.html" new file mode 100644 index 00000000..48a37bbc --- /dev/null +++ "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/02scoped_lock\346\272\220\347\240\201\350\247\243\346\236\220.html" @@ -0,0 +1,127 @@ + + + + + + + + + + std::scoped_lock 的源码实现与解析 | 现代C++并发编程教程 + + + + + +
    跳至主要內容

    std::scoped_lock 的源码实现与解析

    大约 6 分钟

    std::scoped_lock 的源码实现与解析

    本单章专门介绍标准库在 C++17 引入的类模板 std::scoped_lock 的实现,让你对它再无疑问。

    这会涉及到不少的模板技术,这没办法,就如同我们先前聊 std::thread 的构造与源码分析最后说的:“不会模板,你阅读标准库源码,是无稽之谈”。建议学习现代C++模板教程open in new window

    我们还是一样的,以 MSVC STL 实现的 std::scoped_lockopen in new window 代码进行讲解,不用担心,我们也查看了 libstdc++open in new windowlibc++open in new window的实现,并没有太多区别,更多的是一些风格上的。而且个人觉得 MSVC 的实现是最简单直观的。

    std::scoped_lock 的数据成员

    std::scoped_lock 是一个类模板,它有两个特化,也就是有三个版本,其中的数据成员也是不同的。并且它们都不可移动不可拷贝,“管理类”应该如此。

    1. 主模板,是一个可变参数类模板,声明了一个类型形参包 _Mutexes存储了一个 std::tuple,具体类型根据类型形参包决定。

      _EXPORT_STD template <class... _Mutexes>
      +class _NODISCARD_LOCK scoped_lock { // class with destructor that unlocks mutexes
      +public:
      +    explicit scoped_lock(_Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) { // construct and lock
      +        _STD lock(_Mtxes...);
      +    }
      +
      +    explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened
      +        : _MyMutexes(_Mtxes...) {} // construct but don't lock
      +
      +    ~scoped_lock() noexcept {
      +        _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes);
      +    }
      +
      +    scoped_lock(const scoped_lock&)            = delete;
      +    scoped_lock& operator=(const scoped_lock&) = delete;
      +
      +private:
      +    tuple<_Mutexes&...> _MyMutexes;
      +};
      +
    2. 对模板类型形参包只有一个类型情况的偏特化,是不是很熟悉,和 lock_guard 几乎没有任何区别,保有一个互斥量的引用,构造上锁,析构解锁,提供一个额外的构造函数让构造的时候不上锁。所以用 scoped_lock 替代 lock_guard 不会造成任何额外开销。

      template <class _Mutex>
      +class _NODISCARD_LOCK scoped_lock<_Mutex> {
      +public:
      +    using mutex_type = _Mutex;
      +
      +    explicit scoped_lock(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
      +        _MyMutex.lock();
      +    }
      +
      +    explicit scoped_lock(adopt_lock_t, _Mutex& _Mtx) noexcept // strengthened
      +        : _MyMutex(_Mtx) {} // construct but don't lock
      +
      +    ~scoped_lock() noexcept {
      +        _MyMutex.unlock();
      +    }
      +
      +    scoped_lock(const scoped_lock&)            = delete;
      +    scoped_lock& operator=(const scoped_lock&) = delete;
      +
      +private:
      +    _Mutex& _MyMutex;
      +};
      +
    3. 对类型形参包为空的情况的全特化,没有数据成员

      template <>
      +class scoped_lock<> {
      +public:
      +    explicit scoped_lock() = default;
      +    explicit scoped_lock(adopt_lock_t) noexcept /* strengthened */ {}
      +
      +    scoped_lock(const scoped_lock&)            = delete;
      +    scoped_lock& operator=(const scoped_lock&) = delete;
      +};
      +

    std::mutex m1,m2;
    +
    +std::scoped_lock<std::mutex>lc{ m1 };                   // 匹配到偏特化版本  保有一个 std::mutex&
    +std::scoped_lock<std::mutex, std::mutex>lc2{ m1,m2 };   // 匹配到主模板     保有一个 std::tuple<std::mutex&,std::mutex&>
    +std::scoped_lock<> lc3;                                 // 匹配到全特化版本  空
    +

    std::scoped_lock的构造与析构

    在上一节讲 scoped_lock 的数据成员的时候已经把这个模板类的全部源码,三个版本的代码都展示了,就不再重复。

    这三个版本中,只有两个版本需要介绍,也就是

    1. 形参包元素数量为一的偏特化,只管理一个互斥量的。
    2. 主模板,可以管理任意个数的互斥量。

    那这两个的共同点是什么呢?构造上锁,析构解锁。这很明显,明确这一点我们就开始讲吧。


    std::mutex m;
    +void f(){
    +    m.lock();
    +    std::lock_guard<std::mutex> lc{ m, std::adopt_lock };
    +}
    +void f2(){
    +    m.lock();
    +    std::scoped_lock<std::mutex>sp{ std::adopt_lock,m };
    +}
    +

    这段代码为你展示了 std::lock_guardstd::scoped_lock 形参包元素数量为一的偏特化的唯一区别:调用不会上锁的构造函数的参数顺序不同。那么到此也就够了。

    接下来我们进入 std::scoped_lock 主模板的讲解:

    explicit scoped_lock(_Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) { // construct and lock
    +        _STD lock(_Mtxes...);
    +    }
    +

    这个构造函数做了两件事情,初始化数据成员 _MyMutexes让它保有这些互斥量的引用,以及给所有互斥量上锁,使用了 std::lockopen in new window 帮助我们完成这件事情。

    explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened
    +    : _MyMutexes(_Mtxes...) {} // construct but don't lock
    +

    这个构造函数不上锁,只是初始化数据成员 _MyMutexes让它保有这些互斥量的引用。

    ~scoped_lock() noexcept {
    +    _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes);
    +}
    +

    析构函数就要稍微聊一下了,主要是用 std::applyopen in new window 去遍历 std::tupleopen in new window ,让元组保有的互斥量引用都进行解锁。简单来说是 std::apply 可以将元组存储的参数全部拿出,用于调用这个可变参数的可调用对象,我们就能利用折叠表达式展开形参包并对其调用 unlock()

    不在乎其返回类型只用来实施它的副作用,显式转换为 (void) 也就是弃值表达式open in new window。在我们之前讲的 std::thread 源码中也有这种用法open in new window

    不过你可能有疑问:“我们的标准库的那些互斥量open in new window unlock() 返回类型都是 void 呀,为什么要这样?”

    的确,这是个好问题,libstdc++open in new windowlibc++open in new window 都没这样做,或许 MSVC STL 想着会有人设计的互斥量让它的 unlock() 返回类型不为 void,毕竟 互斥体 (Mutex)open in new window 没有要求 unlock() 的返回类型。


    template< class F, class Tuple >
    +constexpr decltype(auto) apply( F&& f, Tuple&& t );
    +

    这个函数模板接受两个参数,一个可调用 (Callable)open in new window对象 f,以及一个元组 t,用做调用 f 。我们可以自己简单实现一下它,其实不算难,这种遍历元组的方式在之前讲 std::thread 的源码的时候也提到过。

    template<class Callable, class Tuple, std::size_t...index>
    +constexpr decltype(auto) Apply_impl(Callable&& obj,Tuple&& tuple,std::index_sequence<index...>){
    +    return std::invoke(std::forward<Callable>(obj), std::get<index>(std::forward<Tuple>(tuple))...);
    +}
    +
    +template<class Callable, class Tuple>
    +constexpr decltype(auto) apply(Callable&& obj, Tuple&& tuple){
    +    return Apply_impl(std::forward<Callable>(obj), std::forward<Tuple>(tuple),
    +        std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
    +}
    +

    其实就是把元组给解包了,利用了 std::index_sequence + std::make_index_sequence 然后就用 std::get 形参包展开用 std::invoke 调用可调用对象即可,非常经典的处理可变参数做法,这个非常重要,一定要会使用。

    举一个简单的调用例子:

    std::tuple<int, std::string, char>tuple{66,"😅",'c'};
    +::apply([](const auto&... t) { ((std::cout << t << ' '), ...); }, tuple);
    +

    运行测试open in new window

    使用了折叠表达式open in new window展开形参包,打印了元组所有的元素。

    总结

    如你所见,其实这很简单。至少使用与了解其设计原理是很简单的。唯一的难度或许只有那点源码,处理可变参数,这会涉及不少模板技术,既常见也通用。还是那句话:“不会模板,你阅读标准库源码,是无稽之谈”。

    相对于 std::thread 的源码解析,std::scoped_lock 还是简单的多。

    + + + diff --git "a/md/\350\257\246\347\273\206\345\210\206\346\236\220/03async\344\270\216future\346\272\220\347\240\201\350\247\243\346\236\220.html" "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/03async\344\270\216future\346\272\220\347\240\201\350\247\243\346\236\220.html" new file mode 100644 index 00000000..f693bf6e --- /dev/null +++ "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/03async\344\270\216future\346\272\220\347\240\201\350\247\243\346\236\220.html" @@ -0,0 +1,40 @@ + + + + + + + + + + st::async 与 std::future 源码解析 | 现代C++并发编程教程 + + + + + + + + + diff --git "a/md/\350\257\246\347\273\206\345\210\206\346\236\220/index.html" "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/index.html" new file mode 100644 index 00000000..c10eafad --- /dev/null +++ "b/md/\350\257\246\347\273\206\345\210\206\346\236\220/index.html" @@ -0,0 +1,40 @@ + + + + + + + + + + 详细分析 | 现代C++并发编程教程 + + + + + + + + + diff --git a/search-pro.worker.js b/search-pro.worker.js new file mode 100644 index 00000000..fd61881b --- /dev/null +++ b/search-pro.worker.js @@ -0,0 +1,2 @@ +const V=Object.entries,et=Object.fromEntries,st="ENTRIES",L="KEYS",T="VALUES",_="";class D{set;_type;_path;constructor(t,s){const n=t._tree,o=Array.from(n.keys());this.set=t,this._type=s,this._path=o.length>0?[{node:n,keys:o}]:[]}next(){const t=this.dive();return this.backtrack(),t}dive(){if(this._path.length===0)return{done:!0,value:void 0};const{node:t,keys:s}=E(this._path);if(E(s)===_)return{done:!1,value:this.result()};const n=t.get(E(s));return this._path.push({node:n,keys:Array.from(n.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;const t=E(this._path).keys;t.pop(),!(t.length>0)&&(this._path.pop(),this.backtrack())}key(){return this.set._prefix+this._path.map(({keys:t})=>E(t)).filter(t=>t!==_).join("")}value(){return E(this._path).node.get(_)}result(){switch(this._type){case T:return this.value();case L:return this.key();default:return[this.key(),this.value()]}}[Symbol.iterator](){return this}}const E=e=>e[e.length-1],nt=(e,t,s)=>{const n=new Map;if(t===void 0)return n;const o=t.length+1,u=o+s,i=new Uint8Array(u*o).fill(s+1);for(let r=0;r{const d=u*i;t:for(const c of e.keys())if(c===_){const a=o[d-1];a<=s&&n.set(r,[e.get(c),a])}else{let a=u;for(let h=0;hs)continue t}R(e.get(c),t,s,n,o,a,i,r+c)}};class C{_tree;_prefix;_size=void 0;constructor(t=new Map,s=""){this._tree=t,this._prefix=s}atPrefix(t){if(!t.startsWith(this._prefix))throw new Error("Mismatched prefix");const[s,n]=x(this._tree,t.slice(this._prefix.length));if(s===void 0){const[o,u]=O(n);for(const i of o.keys())if(i!==_&&i.startsWith(u)){const r=new Map;return r.set(i.slice(u.length),o.get(i)),new C(r,t)}}return new C(s,t)}clear(){this._size=void 0,this._tree.clear()}delete(t){return this._size=void 0,ot(this._tree,t)}entries(){return new D(this,st)}forEach(t){for(const[s,n]of this)t(s,n,this)}fuzzyGet(t,s){return nt(this._tree,t,s)}get(t){const s=k(this._tree,t);return s!==void 0?s.get(_):void 0}has(t){const s=k(this._tree,t);return s!==void 0&&s.has(_)}keys(){return new D(this,L)}set(t,s){if(typeof t!="string")throw new Error("key must be a string");return this._size=void 0,I(this._tree,t).set(_,s),this}get size(){if(this._size)return this._size;this._size=0;const t=this.entries();for(;!t.next().done;)this._size+=1;return this._size}update(t,s){if(typeof t!="string")throw new Error("key must be a string");this._size=void 0;const n=I(this._tree,t);return n.set(_,s(n.get(_))),this}fetch(t,s){if(typeof t!="string")throw new Error("key must be a string");this._size=void 0;const n=I(this._tree,t);let o=n.get(_);return o===void 0&&n.set(_,o=s()),o}values(){return new D(this,T)}[Symbol.iterator](){return this.entries()}static from(t){const s=new C;for(const[n,o]of t)s.set(n,o);return s}static fromObject(t){return C.from(Object.entries(t))}}const x=(e,t,s=[])=>{if(t.length===0||e==null)return[e,s];for(const n of e.keys())if(n!==_&&t.startsWith(n))return s.push([e,n]),x(e.get(n),t.slice(n.length),s);return s.push([e,t]),x(void 0,"",s)},k=(e,t)=>{if(t.length===0||e==null)return e;for(const s of e.keys())if(s!==_&&t.startsWith(s))return k(e.get(s),t.slice(s.length))},I=(e,t)=>{const s=t.length;t:for(let n=0;e&&n{const[s,n]=x(e,t);if(s!==void 0){if(s.delete(_),s.size===0)W(n);else if(s.size===1){const[o,u]=s.entries().next().value;q(n,o,u)}}},W=e=>{if(e.length===0)return;const[t,s]=O(e);if(t.delete(s),t.size===0)W(e.slice(0,-1));else if(t.size===1){const[n,o]=t.entries().next().value;n!==_&&q(e.slice(0,-1),n,o)}},q=(e,t,s)=>{if(e.length===0)return;const[n,o]=O(e);n.set(o+t,s),n.delete(o)},O=e=>e[e.length-1],ut=(e,t)=>{const s=e._idToShortId.get(t);if(s!=null)return e._storedFields.get(s)},it=/[\n\r -#%-*,-/:;?@[-\]_{}\u00A0\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]+/u,M="or",$="and",rt="and_not",ct=(e,t)=>{e.includes(t)||e.push(t)},N=(e,t)=>{for(const s of t)e.includes(s)||e.push(s)},P=({score:e},{score:t})=>t-e,lt=()=>new Map,b=e=>{const t=new Map;for(const s of Object.keys(e))t.set(parseInt(s,10),e[s]);return t},G=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0,ht={[M]:(e,t)=>{for(const s of t.keys()){const n=e.get(s);if(n==null)e.set(s,t.get(s));else{const{score:o,terms:u,match:i}=t.get(s);n.score=n.score+o,n.match=Object.assign(n.match,i),N(n.terms,u)}}return e},[$]:(e,t)=>{const s=new Map;for(const n of t.keys()){const o=e.get(n);if(o==null)continue;const{score:u,terms:i,match:r}=t.get(n);N(o.terms,i),s.set(n,{score:o.score+u,terms:o.terms,match:Object.assign(o.match,r)})}return s},[rt]:(e,t)=>{for(const s of t.keys())e.delete(s);return e}},dt=(e,t,s,n,o,u)=>{const{k:i,b:r,d}=u;return Math.log(1+(s-t+.5)/(t+.5))*(d+e*(i+1)/(e+i*(1-r+r*n/o)))},at=e=>(t,s,n)=>{const o=typeof e.fuzzy=="function"?e.fuzzy(t,s,n):e.fuzzy||!1,u=typeof e.prefix=="function"?e.prefix(t,s,n):e.prefix===!0;return{term:t,fuzzy:o,prefix:u}},H=(e,t,s,n)=>{for(const o of Object.keys(e._fieldIds))if(e._fieldIds[o]===s){e._options.logger("warn",`SlimSearch: document with ID ${e._documentIds.get(t)} has changed before removal: term "${n}" was not present in field "${o}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}},ft=(e,t,s,n)=>{if(!e._index.has(n)){H(e,s,t,n);return}const o=e._index.fetch(n,lt),u=o.get(t);u==null||u.get(s)==null?H(e,s,t,n):u.get(s)<=1?u.size<=1?o.delete(t):u.delete(s):u.set(s,u.get(s)-1),e._index.get(n).size===0&&e._index.delete(n)},gt={k:1.2,b:.7,d:.5},mt={idField:"id",extractField:(e,t)=>e[t],tokenize:e=>e.split(it),processTerm:e=>e.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(e,t)=>{typeof console?.[e]=="function"&&console[e](t)},autoVacuum:!0},J={combineWith:M,prefix:!1,fuzzy:!1,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:gt},pt={combineWith:$,prefix:(e,t,s)=>t===s.length-1},Ft={batchSize:1e3,batchWait:10},U={minDirtFactor:.1,minDirtCount:20},_t={...Ft,...U},K=Symbol("*"),yt=(e,t)=>{const s=new Map,n={...e._options.searchOptions,...t};for(const[o,u]of e._documentIds){const i=n.boostDocument?n.boostDocument(u,"",e._storedFields.get(o)):1;s.set(o,{score:i,terms:[],match:{}})}return s},X=(e,t=M)=>{if(e.length===0)return new Map;const s=t.toLowerCase(),n=ht[s];if(!n)throw new Error(`Invalid combination operator: ${t}`);return e.reduce(n)||new Map},S=(e,t,s,n,o,u,i,r,d=new Map)=>{if(o==null)return d;for(const c of Object.keys(u)){const a=u[c],h=e._fieldIds[c],g=o.get(h);if(g==null)continue;let m=g.size;const p=e._avgFieldLength[h];for(const l of g.keys()){if(!e._documentIds.has(l)){ft(e,h,l,s),m-=1;continue}const f=i?i(e._documentIds.get(l),s,e._storedFields.get(l)):1;if(!f)continue;const y=g.get(l),F=e._fieldLength.get(l)[h],v=dt(y,m,e._documentCount,F,p,r),z=n*a*f*v,A=d.get(l);if(A){A.score+=z,ct(A.terms,t);const w=G(A.match,s);w?w.push(c):A.match[s]=[c]}else d.set(l,{score:z,terms:[t],match:{[s]:[c]}})}}return d},At=(e,t,s)=>{const n={...e._options.searchOptions,...s},o=(n.fields||e._options.fields).reduce((l,f)=>({...l,[f]:G(n.boost,f)||1}),{}),{boostDocument:u,weights:i,maxFuzzy:r,bm25:d}=n,{fuzzy:c,prefix:a}={...J.weights,...i},h=e._index.get(t.term),g=S(e,t.term,t.term,1,h,o,u,d);let m,p;if(t.prefix&&(m=e._index.atPrefix(t.term)),t.fuzzy){const l=t.fuzzy===!0?.2:t.fuzzy,f=l<1?Math.min(r,Math.round(t.term.length*l)):l;f&&(p=e._index.fuzzyGet(t.term,f))}if(m)for(const[l,f]of m){const y=l.length-t.term.length;if(!y)continue;p?.delete(l);const F=a*l.length/(l.length+.3*y);S(e,t.term,l,F,f,o,u,d,g)}if(p)for(const l of p.keys()){const[f,y]=p.get(l);if(!y)continue;const F=c*l.length/(l.length+y);S(e,t.term,l,F,f,o,u,d,g)}return g},Y=(e,t,s={})=>{if(t===K)return yt(e,s);if(typeof t!="string"){const a={...s,...t,queries:void 0},h=t.queries.map(g=>Y(e,g,a));return X(h,a.combineWith)}const{tokenize:n,processTerm:o,searchOptions:u}=e._options,i={tokenize:n,processTerm:o,...u,...s},{tokenize:r,processTerm:d}=i,c=r(t).flatMap(a=>d(a)).filter(a=>!!a).map(at(i)).map(a=>At(e,a,i));return X(c,i.combineWith)},Q=(e,t,s={})=>{const n=Y(e,t,s),o=[];for(const[u,{score:i,terms:r,match:d}]of n){const c=r.length||1,a={id:e._documentIds.get(u),score:i*c,terms:Object.keys(d),queryTerms:r,match:d};Object.assign(a,e._storedFields.get(u)),(s.filter==null||s.filter(a))&&o.push(a)}return t===K&&s.boostDocument==null&&e._options.searchOptions.boostDocument==null||o.sort(P),o},Ct=(e,t,s={})=>{s={...e._options.autoSuggestOptions,...s};const n=new Map;for(const{score:u,terms:i}of Q(e,t,s)){const r=i.join(" "),d=n.get(r);d!=null?(d.score+=u,d.count+=1):n.set(r,{score:u,terms:i,count:1})}const o=[];for(const[u,{score:i,terms:r,count:d}]of n)o.push({suggestion:u,terms:r,score:i/d});return o.sort(P),o};class Et{_options;_index;_documentCount;_documentIds;_idToShortId;_fieldIds;_fieldLength;_avgFieldLength;_nextId;_storedFields;_dirtCount;_currentVacuum;_enqueuedVacuum;_enqueuedVacuumConditions;constructor(t){if(t?.fields==null)throw new Error('SlimSearch: option "fields" must be provided');const s=t.autoVacuum==null||t.autoVacuum===!0?_t:t.autoVacuum;this._options={...mt,...t,autoVacuum:s,searchOptions:{...J,...t.searchOptions||{}},autoSuggestOptions:{...pt,...t.autoSuggestOptions||{}}},this._index=new C,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=U,this.addFields(this._options.fields)}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}get documentCount(){return this._documentCount}get termCount(){return this._index.size}toJSON(){const t=[];for(const[s,n]of this._index){const o={};for(const[u,i]of n)o[u]=Object.fromEntries(i);t.push([s,o])}return{documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:t,serializationVersion:2}}addFields(t){for(let s=0;s{if(c!==1&&c!==2)throw new Error("SlimSearch: cannot deserialize an index created with an incompatible version");const h=new Et(a);h._documentCount=t,h._nextId=s,h._documentIds=b(n),h._idToShortId=new Map,h._fieldIds=o,h._fieldLength=b(u),h._avgFieldLength=i,h._storedFields=b(r),h._dirtCount=d||0,h._index=new C;for(const[g,m]of h._documentIds)h._idToShortId.set(m,g);for(const[g,m]of e){const p=new Map;for(const l of Object.keys(m)){let f=m[l];c===1&&(f=f.ds),p.set(parseInt(l,10),b(f))}h._index.set(g,p)}return h},B=(e,t)=>{const s=e.toLowerCase(),n=t.toLowerCase(),o=[];let u=0,i=0;const r=(c,a=!1)=>{let h="";i===0?h=c.length>20?`… ${c.slice(-20)}`:c:a?h=c.length+i>100?`${c.slice(0,100-i)}… `:c:h=c.length>20?`${c.slice(0,20)} … ${c.slice(-20)}`:c,h&&o.push(h),i+=h.length,a||(o.push(["mark",t]),i+=t.length,i>=100&&o.push(" …"))};let d=s.indexOf(n,u);if(d===-1)return null;for(;d>=0;){const c=d+n.length;if(r(e.slice(u,d)),u=c,i>100)break;d=s.indexOf(n,u)}return i<100&&r(e.slice(u),!0),o},wt=(e,t)=>t.contents.reduce((s,[,n])=>s+n,0)-e.contents.reduce((s,[,n])=>s+n,0),xt=(e,t)=>Math.max(...t.contents.map(([,s])=>s))-Math.max(...e.contents.map(([,s])=>s)),Z=(e,t,s={})=>{const n={};return Q(t,e,{boost:{h:2,t:1,c:4},prefix:!0,...s}).forEach(o=>{const{id:u,terms:i,score:r}=o,d=u.includes("@"),c=u.includes("#"),[a,h]=u.split(/[#@]/),g=Number(a),m=i.sort((l,f)=>l.length-f.length).filter((l,f)=>i.slice(f+1).every(y=>!y.includes(l))),{contents:p}=n[g]??={title:"",contents:[]};if(d)p.push([{type:"customField",id:g,index:h,display:m.map(l=>o.c.map(f=>B(f,l))).flat().filter(l=>l!==null)},r]);else{const l=m.map(f=>B(o.h,f)).filter(f=>f!==null);if(l.length&&p.push([{type:c?"heading":"title",id:g,...c&&{anchor:h},display:l},r]),"t"in o)for(const f of o.t){const y=m.map(F=>B(f,F)).filter(F=>F!==null);y.length&&p.push([{type:"text",id:g,...c&&{anchor:h},display:y},r])}}}),V(n).sort(([,o],[,u])=>"max"==="total"?wt(o,u):xt(o,u)).map(([o,{title:u,contents:i}])=>{if(!u){const r=ut(t,o);r&&(u=r.h)}return{title:u,contents:i.map(([r])=>r)}})},tt=(e,t,s={})=>{const n=Ct(t,e,{fuzzy:.2,maxFuzzy:3,...s}).map(({suggestion:o})=>o);return e.includes(" ")?n:n.filter(o=>!o.includes(" "))},bt=et(V(JSON.parse("{\"/\":{\"documentCount\":56,\"nextId\":56,\"documentIds\":{\"0\":\"0\",\"1\":\"1\",\"2\":\"2\",\"3\":\"2#前言\",\"4\":\"2#并发\",\"5\":\"2#在计算机中的并发\",\"6\":\"2#并发与并行\",\"7\":\"2#总结\",\"8\":\"3\",\"9\":\"3#hello-world\",\"10\":\"3#当前环境支持并发线程数\",\"11\":\"3#线程管理\",\"12\":\"3#启动新线程\",\"13\":\"3#raii\",\"14\":\"3#传递参数\",\"15\":\"3#std-this-thread\",\"16\":\"3#std-thread-转移所有权\",\"17\":\"3#std-thread-的构造-源码解析\",\"18\":\"3#实现-joining-thread\",\"19\":\"3#总结\",\"20\":\"4\",\"21\":\"4#条件竞争\",\"22\":\"4#使用互斥量\",\"23\":\"4#std-lock-guard\",\"24\":\"4#try-lock\",\"25\":\"4#保护共享数据\",\"26\":\"4#死锁-问题与解决\",\"27\":\"4#std-unique-lock-灵活的锁\",\"28\":\"4#在不同作用域传递互斥量\",\"29\":\"4#保护共享数据的初始化过程\",\"30\":\"4#保护不常更新的数据结构\",\"31\":\"4#std-recursive-mutex\",\"32\":\"4#new、delete-是线程安全的吗\",\"33\":\"4#总结\",\"34\":\"5\",\"35\":\"5#等待事件或条件\",\"36\":\"5#线程安全的队列\",\"37\":\"5#使用-future\",\"38\":\"5#创建异步任务获取返回值\",\"39\":\"5#future-与-std-packaged-task\",\"40\":\"5#使用-std-promise\",\"41\":\"5#多个线程的等待\",\"42\":\"6\",\"43\":\"7\",\"44\":\"7#std-thread-的数据成员\",\"45\":\"7#std-thread-的构造函数\",\"46\":\"7#总结\",\"47\":\"8\",\"48\":\"8#std-scoped-lock-的数据成员\",\"49\":\"8#std-scoped-lock的构造与析构\",\"50\":\"8#总结\",\"51\":\"9\",\"52\":\"10\",\"53\":\"11\",\"54\":\"12\",\"55\":\"13\"},\"fieldIds\":{\"h\":0,\"t\":1,\"c\":2},\"fieldLength\":{\"0\":[1,58],\"1\":[1,16],\"2\":[1],\"3\":[1,14],\"4\":[1,7],\"5\":[1,39],\"6\":[1,23],\"7\":[1,11],\"8\":[1,9],\"9\":[2,68],\"10\":[1,226],\"11\":[1,14],\"12\":[1,299],\"13\":[1,104],\"14\":[1,226],\"15\":[1,114],\"16\":[1,107],\"17\":[1,11],\"18\":[1,96],\"19\":[1,28],\"20\":[1,16],\"21\":[1,110],\"22\":[1,79],\"23\":[1,213],\"24\":[1,58],\"25\":[1,62],\"26\":[2,207],\"27\":[1,186],\"28\":[1,99],\"29\":[1,144],\"30\":[1,84],\"31\":[1,82],\"32\":[2,97],\"33\":[1,46],\"34\":[1,17],\"35\":[1,185],\"36\":[1,134],\"37\":[1,54],\"38\":[1,159],\"39\":[1,197],\"40\":[1,136],\"41\":[1,13],\"42\":[1,5],\"43\":[4,32],\"44\":[1,44],\"45\":[1,267],\"46\":[1,68],\"47\":[4,32],\"48\":[1,91],\"49\":[1,159],\"50\":[1,19],\"51\":[6],\"52\":[1,2],\"53\":[1,3],\"54\":[1],\"55\":[1]},\"averageFieldLength\":[1.25,86.98583877995642],\"storedFields\":{\"0\":{\"h\":\"现代C++并发编程教程\",\"t\":[\"本仓库用来存放 B 站课程《现代 C++ 并发编程教程》的教案、代码。\",\"不管是否购买课程,任何组织和个人遵守 CC BY-NC-ND 4.0 协议均可随意使用学习。\",\"捐赠、issues、pr 均会在致谢列表中铭记您的贡献。\",\" 国内的 C++ 并发编程的教程并不稀少,不管是书籍、博客、视频。然而大多数是粗糙的、不够准确、复杂的。而我们想以更加现代、简单、准确的方式进行教学。\",\" 我们在教学中可能常常为您展示部分标准库源码,自己手动实现一些库,这是必须的,希望您是已经较为熟练使用模板(如果没有,可以先学习 现代C++模板教程)。阅读源码可以帮助我们更轻松的理解标准库设施的使用与原理。\",\" 本教程假设开发者的最低水平为:C++11 + STL + template。\",\" 虽强调现代,但不用担心,我们几乎是从头教学,即使你从来没使用过 C++ 进行多线程编程,也不成问题。\",\"  我们希望您的编译器版本和标准尽可能的高,我们的代码均会测试三大编译器 gcc、clang、msvc。需要更高的标准会进行强调。\"]},\"1\":{\"h\":\"Summary\",\"t\":[\"介绍\",\"基本概念\",\"使用线程\",\"共享数据\",\"同步操作\",\"详细分析\",\"std::thread 的构造-源码解析\",\"std::scoped_lock 的源码实现与解析\",\"std::async 与 std::future 源码解析\"]},\"2\":{\"h\":\"基本概念\"},\"3\":{\"h\":\"前言\",\"t\":[\" 在我们谈起“并发编程”,其实可以直接简单理解为“多线程编程”,我知道你或许有疑问:“那多进程呢?” C++ 语言层面没有进程的概念,并发支持库也不涉及多进程,所以在本教程中,不用在意。\",\" 我们完全使用标准 C++ 进行教学。\"]},\"4\":{\"h\":\"并发\",\"t\":[\"并发,指两个或两个以上的独立活动同时发生。\",\"并发在生活中随处可见,我们可以一边走路一边说话,也可以两只手同时做不同的动作,又或者一边看电视一边吃零食。\"]},\"5\":{\"h\":\"在计算机中的并发\",\"t\":[\"计算机中的并发有两种方式:\",\"多核机器的真正并行。\",\"单核机器的任务切换。\",\" 在早期,一些单核机器,它要想并发,执行多个任务,那就只能是任务切换,任务切换会给你一种“好像这些任务都在同时执行”的假象。只有硬件上是多核的,才能进行真正的并行,也就是真正的”同时执行任务“。\",\" 在现在,我们日常使用的机器,基本上是二者都有。我们现在的 CPU 基本都是多核,而操作系统调度基本也一样有任务切换,因为要执行的任务非常之多,CPU 是很快的,但是核心却没有那么多,不可能每一个任务都单独给一个核心。大家可以打开自己电脑的任务管理器看一眼,进程至少上百个,线程更是上千。这基本不可能每一个任务分配一个核心,都并行,而且也没必要。正是任务切换使得这些后台任务可以运行,这样系统使用者就可以同时运行文字处理器、编译器、编辑器和 Web 浏览器。\"]},\"6\":{\"h\":\"并发与并行\",\"t\":[\"事实上,对于这两个术语,并没有非常公认的说法。\",\"有些人认为二者毫无关系,指代的东西完全不同。\",\"有些人认为二者大多数时候是相同的,只是用于描述一些东西的时候关注点不同。\",\"我喜欢第二种,那我们就讲第二种。\",\"对多线程来说,这两个概念大部分是重叠的。对于很多人来说,它们没有什么区别。 这两个词是用来描述硬件同时执行多个任务的方式:\",\"“并行”更加注重性能。使用硬件提高数据处理速度时,会讨论程序的并行性。\",\"当关注重点在于任务分离或任务响应时,会讨论程序的并发性。\",\"这两个术语存在的目的,就是为了区别多线程中不同的关注点。\"]},\"7\":{\"h\":\"总结\",\"t\":[\" 概念从来不是我们的重点,尤其是某些说法准确性也一般,假设开发者对操作系统等知识有基本了解。\",\"  我们也不打算特别介绍什么 C++ 并发库的历史发展、什么时候你该使用多线程、什么时候不该使用多线程... 类似问题应该是看你自己的,而我们回到代码上即可。\"]},\"8\":{\"h\":\"使用线程\",\"t\":[\"在标准 C++ 中,std::thread 可以指代线程,使用线程也就是使用 std::thread 类。\"]},\"9\":{\"h\":\"Hello World\",\"t\":[\"在我们初学 C++ 的时候应该都写过这样一段代码:\",\"#include int main(){ std::cout << \\\"Hello World!\\\" << std::endl; } \",\"这段代码将\\\"Hello World!\\\"写入到标准输出流,换行并刷新。\",\"我们可以启动一个线程来做这件事情:\",\"#include #include // 引入线程支持头文件 void hello(){ // 定义一个函数用作打印任务 std::cout << \\\"Hello World\\\" << std::endl; } int main(){ std::thread t{ hello }; t.join(); } \",\"std::thread t{ hello }; 创建了一个线程对象 t,将 hello 作为它的可调用(Callable)对象,在新线程中执行。线程对象关联了一个线程资源,我们无需手动控制,在线程对象构造成功,就自动在新线程开始执行函数 hello。\",\"t.join(); 等待线程对象 t 关联的线程执行完毕,否则将一直堵塞。这里的调用是必须的,否则 std::thread 的析构函数将调用 std::terminate() 无法正确析构。\",\"这是因为我们创建线程对象 t 的时候就关联了一个活跃的线程,调用 join() 就是确保线程对象关联的线程已经执行完毕,然后会修改对象的状态,让 std::thread::joinable() 返回 false,表示线程对象目前没有关联活跃线程。std::thread 的析构函数,正是通过 joinable() 判断线程对象目前是否有关联活跃线程,如果为 true,那么就当做有关联活跃线程,会调用 std::terminate()。\",\"如你所见,std::thread 高度封装,其成员函数也很少,我们可以轻易的创建线程执行任务,不过,它的用法也还远不止如此,我们慢慢介绍。\"]},\"10\":{\"h\":\"当前环境支持并发线程数\",\"t\":[\"使用 hardware_concurrency 可以获得我们当前硬件支持的并发线程数量,它是 std::thread 的静态成员函数。\",\"#include #include int main(){ unsigned int n = std::thread::hardware_concurrency(); std::cout << \\\"支持 \\\" << n << \\\" 个并发线程。\\\\n\\\"; } \",\"本节其实是要普及一下计算机常识,一些古老的书籍比如 csapp 应该也会提到“超线程技术”。\",\"英特尔® 超线程技术是一项硬件创新,允许在每个内核上运行多个线程。更多的线程意味着可以并行完成更多的工作。\",\"AMD 超线程技术被称为 SMT(Simultaneous Multi-Threading),它与英特尔的技术实现有所不同,不过使用类似。\",\"举个例子:一款 4 核心 8 线程的 CPU,这里的 8 线程其实是指所谓的逻辑处理器,也意味着这颗 CPU 最多可并行执行 8 个任务。\",\"我们的 hardware_concurrency() 获取的值自然也会是 8。\",\"当然了,都 2024 年了,我们还得考虑一个问题:“ 英特尔从 12 代酷睿开始,为其处理器引入了全新的“大小核”混合设计架构”。\",\"比如我的 CPU i7 13700H 它是 14 核心,20 线程,有 6 个能效核,6 个性能核。不过我们说了,物理核心这个通常不看重,hardware_concurrency() 输出的值会为 20。\",\"在进行多线程编程的时候,我们可以参考此值确定我们要创建的线程数量\",\"我们可以举个简单的例子运用这个值:\",\"template auto sum(ForwardIt first, ForwardIt last){ using value_type = std::iter_value_t; std::size_t num_threads = std::thread::hardware_concurrency(); std::ptrdiff_t distance = std::distance(first, last); if(distance > 1024000){ // 计算每个线程处理的元素数量 std::size_t chunk_size = distance / num_threads; std::size_t remainder = distance % num_threads; // 存储每个线程的结果 std::vectorresults(num_threads); // 存储关联线程的线程对象 std::vector threads; // 创建并启动线程 auto start = first; for (std::size_t i = 0; i < num_threads; ++i) { auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0)); threads.emplace_back([start, end, &results, i] { results[i] = std::accumulate(start, end, value_type{}); }); start = end; // 开始迭代器不断向前 } // 等待所有线程执行完毕 for (auto& thread : threads) thread.join(); // 汇总线程的计算结果 value_type total_sum = std::accumulate(results.begin(), results.end(), value_type{}); return total_sum; } value_type total_sum = std::accumulate(first, last, value_type{}); return total_sum; } \",\"运行测试。\",\"我们写了这样一个求和函数 sum,接受两个迭代器计算它们范围中对象的和。\",\"我们先获取了迭代器所指向的值的类型,定义了一个别名 value_type,我们这里使用到的 std::iter_value_t 是 C++20 引入的,返回类型推导是 C++14 引入。如果希望代码可以在 C++11 的环境运行也可以自行修改为:\",\"template typename std::iterator_traits::value_type sum(ForwardIt first, ForwardIt last); \",\"运行测试。\",\"num_threads 是当前硬件支持的并发线程的值。std::distance 用来计算 first 到 last 的距离,也就是我们要进行求和的元素个数了。\",\"我们这里的设计比较简单,毕竟是初学,所以只对元素个数大于 1024000 的进行多线程求和,而小于这个值的则直接使用标准库函数 std::accumulate 求和即可。\",\"多线程求和只需要介绍三个地方\",\"chunk_size 是每个线程分配的任务,但是这是可能有余数的,比如 10 个任务分配三个线程,必然余 1。但是我们也需要执行这个任务,所以还定义了一个对象 remainder ,它存储的就是余数。\",\"auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0)); 这行代码是获取当前线程的执行范围,其实也就是要 chunk_size 再加上我们的余数 remainder 。这里写了一个三目运算符是为了进行分配任务,比如:\",\"假设有 3 个线程执行,并且余数是 2。那么,每个线程的处理情况如下:\",\"当 i = 0 时,由于 0 < 2,所以这个线程会多分配一个元素。\",\"当 i = 1 时,同样因为 1 < 2,这个线程也会多分配一个元素。\",\"当 i = 2 时,由于 2 >= 2,所以这个线程只处理平均数量的元素。\",\"这确保了剩余的 2 个元素被分配给了前两个线程,而第三个线程只处理了平均数量的元素。这样就确保了所有的元素都被正确地分配给了各个线程进行处理。\",\"auto start = first; 在创建线程执行之前先定义了一个开始迭代器。在传递给线程执行的lambda表达式中,最后一行是:start = end; 这是为了让迭代器一直向前。\",\"由于求和不涉及数据竞争之类的问题,所以我们甚至可以在刚讲完 Hello World 就手搓了一个“并行求和”的简单的模板函数。主要的难度其实在于对 C++ 的熟悉程度,而非对线程类 std::thread 的使用了,这里反而是最简单的,无非是用容器存储线程对象管理,最后进行 join() 罢了。\",\"本节代码只是为了学习,而且只是百万数据通常没必要多线程,上亿的话差不多。如果你需要多线程求和,可以使用 C++17 引入的求和算法 std::reduce 并指明执行策略。它的效率接近我们实现的 sum 的两倍,当前环境核心越多数据越多,和单线程效率差距越明显。\"]},\"11\":{\"h\":\"线程管理\",\"t\":[\"在 C++ 标准库中,只能管理与 std::thread 关联的线程,类 std::thread 的对象就是指代线程的对象,我们说“线程管理”,其实也就是管理 std::thread 对象。\"]},\"12\":{\"h\":\"启动新线程\",\"t\":[\"使用 C++ 线程库启动线程,就是构造 std::thread 对象。\",\"当然了,如果是默认构造之类的,那么 std::thread 线程对象没有关联线程的,自然也不会启动线程执行任务。\",\"std::thread t; // 构造不表示线程的新 std::thread 对象 \",\"我们上一节的示例是传递了一个函数给 std::thread 对象,函数会在新线程中执行。std::thread 支持的形式还有很多,只要是可调用(Callable)对象即可,比如重载了 operator() 的类对象(也可以直接叫函数对象)。\",\"class Task{ public: void operator()()const { std::cout << \\\"operator()()const\\\\n\\\"; } }; \",\"我们显然没办法直接像函数使用函数名一样,使用“类名”,函数名可以隐式转换到指向它的函数指针,而类名可不会直接变成对象,我们想使用 Task 自然就得构造对象了\",\"std::thread t{ Task{} }; t.join(); \",\"直接创建临时对象即可,可以简化代码并避免引入不必要的局部对象。\",\"不过有件事情需要注意,当我们使用函数对象用于构造 std::thread 的时候,如果你传入的是一个临时对象,且使用的都是 “()”小括号初始化,那么编译器会将此语法解析为函数声明。\",\"std::thread t( Task() ); // 函数声明 \",\"这被编译器解析为函数声明,是一个返回类型为 std::thread,函数名为 t,接受一个返回 Task 的空参的函数指针类型,也就是 Task(*)()。\",\"之所以我们看着抽象是因为这里的形参是无名的,且写了个函数类型。\",\"我们用一个简单的示例为你展示:\",\"void h(int(int)); //#1 声明 void h(int (*p)(int)){} //#2 定义 \",\"即使我还没有为你讲述概念,我相信你也发现了,#1 和 #2 的区别无非是,#1 省略了形参的名称,还有它的形参是函数类型而不是函数指针类型,没有 *。\",\"在确定每个形参的类型后,类型是 “T 的数组”或某个函数类型 T 的形参会调整为具有类型“指向 T 的指针”。文档。\",\"显然,int(int) 是一个函数类型,它被调整为了一个指向这个函数类型的指针类型。\",\"那么回到我们最初的:\",\"std::thread t( Task() ); // #1 函数声明 std::thread t( Task (*p)() ){ return {}; } // #2 函数定义 \",\"#2我们写出了函数形参名称 p,再将函数类型写成函数指针类型,事实上完全等价。我相信,这样,也就足够了。\",\"所以总而言之,建议使用 {} 进行初始化,这是好习惯,大多数时候它是合适的。\",\"C++11 引入的 Lambda 表达式,同样可以作为构造 std::thread 的参数,因为 Lambda 本身就是生成了一个函数对象,它自身就是类类型。\",\"#include #include int main(){ std::thread thread{ [] {std::cout << \\\"Hello World!\\\\n\\\"; } }; thread.join(); } \",\"启动线程后(也就是构造 std::thread 对象)我们必须在线程对象的生存期结束之前,即 std::thread::~thread 调用之前,决定它的执行策略,是 join()(合并)还是 detach()(分离)。\",\"我们先前使用的就是 join(),我们聊一下 detach(),当 std::thread 线程对象调用了 detach(),那么就是线程对象放弃了对线程资源的所有权,不再管理此线程,允许此线程独立的运行,在线程退出时释放所有分配的资源。\",\"放弃了对线程资源的所有权,也就是线程对象没有关联活跃线程了,此时 joinable 为 false。\",\"在单线程的代码中,对象销毁之后再去访问,会产生未定义行为,多线程增加了这个问题发生的几率。\",\"比如函数结束,那么函数局部对象的生存期都已经结束了,都被销毁了,此时线程函数还持有函数局部对象的指针或引用。\",\"#include #include struct func { int& m_i; func(int& i) :m_i{ i } {} void operator()(int n)const { for (int i = 0; i <= n; ++i) { m_i += i; // 可能悬空引用 } } }; int main(){ int n = 0; std::thread my_thread{ func{n},100 }; my_thread.detach(); // 分离,不等待线程结束 } // 分离的线程可能还在运行 \",\"主线程(main)创建局部对象 n、创建线程对象 my_thread 启动线程,执行任务 func{n},局部对象 n 的引用被子线程持有。传入 100 用于调用 func 的 operator(int)。\",\"my_thread.detach();,joinable() 为 false。线程分离,线程对象不再持有线程资源,线程独立的运行。\",\"主线程不等待,此时分离的子线程可能没有执行完毕,但是主线程(main)已经结束,局部对象 n 生存期结束,被销毁,而此时子线程还持有它的引用,访问悬空引用,造成未定义行为。my_thread 已经没有关联线程资源,正常析构,没有问题。\",\"解决方法很简单,将 detach() 替换为 join()。\",\"通常非常不推荐使用 detach(),因为程序员必须确保所有创建的线程正常退出,释放所有获取的资源并执行其它必要的清理操作。这意味着通过调用 detach() 放弃线程的所有权不是一种选择,因此 join 应该在所有场景中使用。 一些老式特殊情况不聊。\",\"另外提示一下,也不要想着 detach() 之后,再次调用 join()\",\"my_thread.detach(); // todo.. my_thread.join(); // 函数结束 \",\"认为这样可以确保被分离的线程在这里阻塞执行完?\",\"我们前面聊的很清楚了,detach() 是线程分离,线程对象放弃了线程资源的所有权,此时我们的 my_thread 它现在根本没有关联任何线程。调用 join() 是:“阻塞当前线程直至 *this 所标识的线程结束其执行”,我们的线程对象都没有线程,堵塞什么?执行什么呢?\",\"简单点说,必须是 std::thread 的 joinable() 为 true 即线程对象有活跃线程,才能调用 join() 和 detach()。\",\"顺带的,我们还得处理线程运行后的异常问题,举个例子:你在一个函数中构造了一个 std::thread 对象,线程开始执行,函数继续执行下面别的代码,但是如果抛出了异常呢?下面我的 join() 就会被跳过。\",\"std::thread my_thread{func{n},10}; //todo.. 抛出异常的代码 my_thread.join(); \",\"避免程序被抛出的异常所终止,在异常处理过程中调用 join(),从而避免线程对象析构产生问题。\",\"struct func; // 复用之前 void f(){ int n = 0; std::thread t{ func{n},10 }; try{ // todo.. 一些当前线程可能抛出异常的代码 f2(); } catch (...){ t.join(); // 1 throw; } t.join(); // 2 } \",\"我知道你可能有很多疑问,我们既然 catch 接住了异常,为什么还要 throw?以及为什么我们要两个 join()?\",\"这两个问题其实也算一个问题,如果代码里抛出了异常,就会跳转到 catch 的代码中,执行 join() 确保线程正常执行完成,线程对象可以正常析构。然而此时我们必须再次 throw 抛出异常,因为你要是不抛出,那么你不是还得执行一个 t.join()?显然逻辑不对,自然抛出。\",\"至于这个函数产生的异常,由调用方进行处理,我们只是确保函数 f 中创建的线程正常执行完成,其局部对象正常析构释放。测试代码。\",\"我知道你可能会想到:“我在 try 块中最后一行写一个 t.join() ,这样如果前面的代码没有抛出异常,就能正常的调用 join(),如果抛出了异常,那就调用 catch 中的 t.join() 根本不需要最外部 2 那里的 join(),也不需要再次 throw 抛出异常”\",\"void f(){ int n = 0; std::thread t{ func{n},10 }; try{ // todo.. 一些当前线程可能抛出异常的代码 f2(); t.join(); // try 最后一行调用 join() } catch (...){ t.join(); // 如果抛出异常,就在 这里调用 join() } } \",\"你是否觉得这样也可以?也没问题?简单的测试运行的确没问题。\",\"但是这是不对的,你要注意我们的注释:“一些当前线程可能抛出异常的代码”,而不是 f2(),我们的 trycatch 只是为了让线程对象关联的线程得以正确执行完毕,以及线程对象正确析构。并没有处理什么其他的东西,不掩盖错误,try块中的代码抛出了异常,catch` 接住了,我们理所应当再次抛出。\"]},\"13\":{\"h\":\"RAII\",\"t\":[\"“资源获取即初始化”(RAII,Resource Acquisition Is Initialization)。\",\"简单的说是:构造函数申请资源,析构函数释放资源,让对象的生命周期和资源绑定。当异常抛出时,C++ 会自动调用栈上所有对象的析构函数。\",\"我们可以提供一个类,在析构函数中使用 join() 确保线程执行完成,线程对象正常析构。\",\"class thread_guard{ std::thread& m_t; public: explicit thread_guard(std::thread& t) :m_t{ t } {} ~thread_guard(){ std::puts(\\\"析构\\\"); // 打印 不用在乎 if (m_t.joinable()) { // 没有关联活跃线程 m_t.join(); } } thread_guard(const thread_guard&) = delete; thread_guard& operator=(const thread_guard&) = delete; }; void f(){ int n = 0; std::thread t{ func{n},10 }; thread_guard g(t); f2(); // 可能抛出异常 } \",\"函数 f 执行完毕,局部对象就要逆序销毁了。因此,thread_guard 对象 g 是第一个被销毁的,调用析构函数。即使函数 f2() 抛出了一个异常,这个销毁依然会发生(前提是你捕获了这个异常)。这确保了线程对象 t 所关联的线程正常的执行完毕以及线程对象的正常析构。测试代码。\",\"如果异常被抛出但未被捕获那么就会调用 std::terminate。是否对未捕获的异常进行任何栈回溯由实现定义。(简单的说就是不一定会调用析构)\",\"我们的测试代码是捕获了异常的,为了观测,看到它一定打印“析构”。\",\"在 thread_guard 的析构函数中,我们要判断 std::thread 线程对象现在是否有关联的活跃线程,如果有,我们才会执行 join(),阻塞当前线程直到线程对象关联的线程执行完毕。如果不想等待线程结束可以使用 detach() ,但是这让 std::thread 对象失去了线程资源的所有权,难以掌控,具体如何,看情况分析。\",\"拷贝赋值和拷贝构造定义为 =delete 可以防止编译器隐式生成,同时会阻止移动构造函数和移动赋值运算符的隐式定义。这样的话,对 thread_guard 对象进行拷贝或赋值等操作会引发一个编译错误。\",\"不允许这些操作主要在于:这是个管理类,而且顾名思义,它就应该只是单纯的管理线程对象仅此而已,只保有一个引用,单纯的做好 RAII 的事情就行,允许其他操作没有价值。\",\"严格来说其实这里倒也不算 RAII,因为 thread_guard 的构造函数其实并没有申请资源,只是保有了线程对象的引用,在析构的时候进行了 join() 。\"]},\"14\":{\"h\":\"传递参数\",\"t\":[\"向可调用对象或函数传递参数很简单,我们前面也都写了,只需要将这些参数作为 std::thread 的构造参数即可。需要注意的是,这些参数会拷贝到新线程的内存空间中,即使函数中的参数是引用,依然实际是拷贝。\",\"void f(int, const int& a); int n = 1; std::thread t(f, 3, n); \",\"线程对象 t 的构造没有问题,可以通过编译,但是这个 n 实际上并没有按引用传递,而是拷贝了,我们可以打印地址来验证我们的猜想。\",\"void f(int, const int& a) { // a 并非引用了局部对象 n std::cout << &a << '\\\\n'; } int main() { int n = 1; std::cout << &n << '\\\\n'; std::thread t(f, 3, n); t.join(); } \",\"运行代码,打印的地址截然不同。\",\"可以通过编译,但通常这不符合我们的需求,因为我们的函数中的参数是引用,我们自然希望能引用调用方传递的参数,而不是拷贝。如果我们的 f 的形参类型不是 const 的引用,则会产生一个编译错误。\",\"想要解决这个问题很简单,我们可以使用标准库的设施 std::ref 、 std::cref 函数模板。\",\"void f(int, int& a) { std::cout << &a << '\\\\n'; } int main() { int n = 1; std::cout << &n << '\\\\n'; std::thread t(f, 3, std::ref(n)); t.join(); } \",\"运行代码,打印地址完全相同。\",\"我们来解释一下,“ref” 其实就是 “reference”(引用)的缩写,意思也很简单,返回“引用”,当然了,不是真的返回引用,它们返回一个包装类 std::reference_wrapper,顾名思义,这个类就是包装引用对象类模板,将对象包装,可以隐式转换为被包装对象的引用。\",\"“cref”呢?,这个“c”就是“const”,就是返回了 std::reference_wrapper。我们不详细介绍他们的实现,你简单认为reference_wrapper可以隐式转换为被包装对象的引用即可,\",\"int n = 0; std::reference_wrapper r = std::ref(n); int& p = r; // r 隐式转换为 n 的引用 此时 p 引用的就是 n \",\"int n = 0; std::reference_wrapper r = std::cref(n); const int& p = r; // r 隐式转换为 n 的 const 的引用 此时 p 引用的就是 n \",\"如果对他们的实现感兴趣,可以观看视频。\",\"以上代码void f(int, int&) 如果不使用 std::ref 并不会和前面 void f(int, const int&) 一样只是多了拷贝,而是会产生编译错误,这是因为 std::thread 内部会将保有的参数副本转换为右值表达式进行传递,这是为了那些只支持移动的类型,左值引用没办法引用右值表达式,所以产生编译错误。\",\"struct move_only { move_only() { std::puts(\\\"默认构造\\\"); } move_only(const move_only&) = delete; move_only(move_only&&)noexcept { std::puts(\\\"移动构造\\\"); } }; void f(move_only){} int main(){ move_only obj; std::thread t{ f,std::move(obj) }; t.join(); } \",\"运行测试。\",\"没有 std::ref 自然是会保有一个副本,所以有两次移动构造,一次是被 std::thread 构造函数中初始化副本,一次是调用函数 f。\",\"如果还有不理解,不用担心,记住,这一切的问题都会在后面的 std::thread 的构造-源码解析 解释清楚。\",\"成员函数指针也是可调用(Callable)的 ,可以传递给 std::thread 作为构造参数,让其关联的线程执行成员函数。\",\"struct X{ void task_run(int)const; }; X x; int n = 0; std::thread t{ &X::task_run,&x,n }; t.join(); \",\"传入成员函数指针、与其配合使用的对象、调用成员函数的参数,构造线程对象 t,启动线程。\",\"如果你是第一次见到成员指针,那么我们稍微聊一下,&X::task_run 是一个整体,它们构成了成员指针,&类名::非静态成员。\",\"成员指针必须和对象一起使用,这是唯一标准用法,成员指针不可以转换到函数指针单独使用,即使是非静态成员函数没有使用任何数据成员。\",\"我们还可以使用模板函数 std::bind与成员指针一起使用\",\"std::thread t{ std::bind(&X::task_run, &x ,n) }; \",\"不过需要注意,std::bind 也是默认拷贝的,即使我们的成员函数形参类型为引用:\",\"struct X { void task_run(int& a)const{ std::cout << &a << '\\\\n'; } }; X x; int n = 0; std::cout << &n << '\\\\n'; std::thread t{ std::bind(&X::task_run,&x,n) }; t.join(); \",\"除非给参数 n 加上 std::ref,就是按引用传递了:\",\"std::thread t{ std::bind(&X::task_run,&x,std::ref(n)) }; \",\"void f(const std::string&); std::thread t{ f,\\\"hello\\\" }; \",\"代码创建了一个调用 f(\\\"hello\\\") 的线程。注意,函数 f 实际需要的是一个 std::string 类型的对象作为参数,但这里使用的是字符串字面量,我们要明白“A的引用只能引用A,或者以任何形式转换到A”,字符串字面量的类型是 const char[N] ,它会退化成指向它的const char* 指针,被线程对象保存。在调用 f 的时候,这个指针可以通过 std::string 的转换构造函数,构造出一个临时的 std::string 对象,就能成功调用。\",\"字符串字面量具有静态存储期,指向它的指针这当然没问题了,不用担心生存期的问题,但是如果是指向“动态”对象的指针,就要特别注意了:\",\"void f(const std::string&); void test(){ char buffer[1024]{}; //todo.. code std::thread t{ f,buffer }; t.detach(); } \",\"以上代码可能导致一些问题,buffer 是一个数组对象,作为 std::thread 构造参数的传递的时候会decay-copy (确保实参在按值传递时会退化) 隐式转换为了指向这个数组的指针。\",\"我们要特别强调,std::thread 构造是代表“启动线程”,而不是调用我们传递的可调用对象。\",\"std::thread 的构造函数中调用了创建线程的函数(windows 下可能为 _beginthreadex),它将我们传入的参数,f、buffer ,传递给这个函数,在新线程中执行函数 f。也就是说,调用和执行 f(buffer) 并不是说要在 std::thread 的构造函数中,而是在创建的新线程中,具体什么时候执行,取决于操作系统的调度,所以完全有可能函数 test 先执行完,而新线程此时还没有进行 f(buffer) 的调用,转换为std::string,那么 buffer 指针就悬空了,会导致问题。解决方案:\",\"将 detach() 替换为 join()。\",\"void f(const std::string&); void test(){ char buffer[1024]{}; //todo.. code std::thread t{ f,buffer }; t.join(); } \",\"显式将 buffer 转换为 std::string。\",\"void f(const std::string&); void test(){ char buffer[1024]{}; //todo.. code std::thread t{ f,std::string(buffer) }; t.detach(); } \"]},\"15\":{\"h\":\"\",\"t\":[\"这个命名空间包含了管理当前线程的函数。\",\"yield 建议实现重新调度各执行线程。\",\"get_id 返回当前线程 id。\",\"sleep_for 使当前线程停止执行指定时间。\",\"sleep_until 使当前线程执行停止到指定的时间点。\",\"它们之中最常用的是 get_id,其次是 sleep_for,再然后 yield,sleep_until 较少。\",\"使用 get_id打印主线程和子线程的 ID。\",\"int main() { std::cout << std::this_thread::get_id() << '\\\\n'; std::thread t{ [] { std::cout << std::this_thread::get_id() << '\\\\n'; } }; t.join(); } \",\"使用 sleep_for 延时。当 Sleep 之类的就行,但是它需要接受的参数不同,是 std::chrono 命名空间中的时间对象。\",\"int main() { std::this_thread::sleep_for(std::chrono::seconds(3)); } \",\"主线程延时 3 秒,这个传入了一个临时对象 seconds ,它是模板 std::chrono::duration 的别名,以及还有很多其他的时间类型,都基于这个类。说实话挺麻烦的,如果您支持 C++14,建议使用时间字面量,在 std::chrono_literals 命名空间中。我们可以改成下面这样:\",\"using namespace std::chrono_literals; int main() { std::this_thread::sleep_for(3s); } \",\"简单直观。\",\"yield 减少 CPU 的占用。\",\"while (!isDone()){ std::this_thread::yield(); } \",\"线程需要等待某个操作完成,如果你直接用一个循环不断判断这个操作是否完成就会使得这个线程占满 CPU 时间,这会造成资源浪费。此时可以判断操作是否完成,如果还没完成就调用 yield 交出 CPU 时间片让其他线程执行,过一会儿再来判断是否完成,这样这个线程占用 CPU 时间会大大减少。\",\"使用 sleep_until 让当前线程延迟到具体的时间。我们延时 5 秒就是。\",\"int main() { // 获取当前时间点 auto now = std::chrono::system_clock::now(); // 设置要等待的时间点为当前时间点之后的5秒 auto wakeup_time = now + 5s; // 输出当前时间 auto now_time = std::chrono::system_clock::to_time_t(now); std::cout << \\\"Current time:\\\\t\\\\t\\\" << std::put_time(std::localtime(&now_time), \\\"%H:%M:%S\\\") << std::endl; // 输出等待的时间点 auto wakeup_time_time = std::chrono::system_clock::to_time_t(wakeup_time); std::cout << \\\"Waiting until:\\\\t\\\\t\\\" << std::put_time(std::localtime(&wakeup_time_time), \\\"%H:%M:%S\\\") << std::endl; // 等待到指定的时间点 std::this_thread::sleep_until(wakeup_time); // 输出等待结束后的时间 now = std::chrono::system_clock::now(); now_time = std::chrono::system_clock::to_time_t(now); std::cout << \\\"Time after waiting:\\\\t\\\" << std::put_time(std::localtime(&now_time), \\\"%H:%M:%S\\\") << std::endl; } \",\"sleep_until 本身设置使用很简单,是打印时间格式、设置时区麻烦。运行结果。\",\"介绍了一下 std::this_thread 命名空间中的四个函数的基本用法,我们后续会经常看到这些函数的使用,不用着急。\"]},\"16\":{\"h\":\"转移所有权\",\"t\":[\"传入可调用对象以及参数,构造 std::thread 对象,启动线程,而线程对象拥有了线程的所有权,线程是一种系统资源,所以可称作“线程资源”。\",\"std::thread 不可复制。两个 std::thread 不可表示一个线程,std::thread 对线程资源是独占所有权。移动就是转移它的线程资源的所有权给别的 std::thread 对象。\",\"int main() { std::thread t{ [] { std::cout << std::this_thread::get_id() << '\\\\n'; } }; std::cout << t.joinable() << '\\\\n'; // 线程对象 t 当前关联了活跃线程 打印 1 std::thread t2{ std::move(t) }; // 将 t 的线程资源的所有权移交给 t2 std::cout << t.joinable() << '\\\\n'; // 线程对象 t 当前没有关联活跃线程 打印 0 //t.join(); // Error! t 没有线程资源 t2.join(); // t2 当前持有线程资源 } \",\"这段代码通过移动构造转移了线程对象 t 的线程资源所有权到 t2,这里虽然有两个 std::thread 对象,但是从始至终只有一个线程资源,让持有线程资源的 t2 对象最后调用 join() 堵塞让其线程执行完毕。t 与 t2 都能正常析构。\",\"我们还可以使用移动赋值来转移线程资源的所有权:\",\"int main() { std::thread t; // 默认构造,没有关联活跃线程 std::cout << t.joinable() << '\\\\n'; // 0 std::thread t2{ [] {} }; t = std::move(t2); // 转移线程资源的所有权到 t std::cout << t.joinable() << '\\\\n'; // 1 t.join(); t2 = std::thread([] {}); t2.join(); } \",\"我们只需要介绍 t2 = std::thread([] {}) ,临时对象是右值表达式,不用调用 std::move,这里相当于是将临时的 std::thread 对象所持有的线程资源转移给 t2,t2 再调用 join() 正常析构。\",\"函数返回 std::thread 对象:\",\"std::thread f(){ std::thread t{ [] {} }; return t; } int main(){ std::thread rt = f(); rt.join(); } \",\"这段代码可以通过编译,你是否感到奇怪?我们在函数 f() 中创建了一个局部的 std::thread 对象,启动线程,然后返回它。\",\"这里的 return t重载决议[1]选择到了移动构造,将 t 线程资源的所有权转移给函数调用 f() 返回的临时 std::thread 对象中,然后这个临时对象再用来初始化 rt ,临时对象是右值表达式,这里一样选择到移动构造,将临时对象的线程资源所有权移交给 rt。此时 rt 具有线程资源的所有权,由它调用 join() 正常析构。\",\"如果标准达到 C++17,RVO 保证这里少一次移动构造的开销(临时对象初始化 rt 的这次)。\",\"所有权也可以在函数内部传递:\",\"void f(std::thread t){ t.join(); } int main(){ std::thread t{ [] {} }; f(std::move(t)); f(std::thread{ [] {} }); } \",\"std::move 将 t 转换为了一个右值表达式,初始化函数f 形参 t,选择到了移动构造转移线程资源的所有权,在函数中调用 t.join() 后正常析构。std::thread{ [] {} } 构造了一个临时对象,本身就是右值表达式,初始化函数f 形参 t,移动构造转移线程资源的所有权到 t,t.join() 后正常析构。\",\"本节内容总体来说是很简单的,如果你有些地方无法理解,那只有一种可能,“对移动语义不了解”,不过这也不是问题,在后续我们详细介绍 std::thread 构造函数的源码即可,不用着急。\"]},\"17\":{\"h\":\"\",\"t\":[\"我们上一个大节讲解了线程管理,也就是 std::thread 的管理,其中的重中之重就是它的构造,传递参数。我们用源码实现为各位从头讲解。\",\"了解其实现,才能更好的使用它。\"]},\"18\":{\"h\":\"实现\",\"t\":[\"这个类和 std::thread 的区别就是析构函数会自动 join 。如果您好好的学习了上一节的内容,阅读了 std::thread 的源码,以下内容不会对您构成任何的难度。\",\"我们存储一个 std::thread 作为底层数据成员,稍微注意一下构造函数和赋值运算符的实现即可。\",\"class joining_thread { std::thread t; public: joining_thread()noexcept = default; template explicit joining_thread(Callable&& func, Args&&...args) : t{ std::forward(func), std::forward(args)... } {} explicit joining_thread(std::thread t_)noexcept : t{ std::move(t_) } {} joining_thread(joining_thread&& other)noexcept : t{ std::move(other.t) } {} joining_thread& operator=(std::thread&& other)noexcept { if (joinable()) { // 如果当前有活跃线程,那就先执行完 join(); } t = std::move(other); return *this; } ~joining_thread() { if (joinable()) { join(); } } void swap(joining_thread& other)noexcept { t.swap(other.t); } std::thread::id get_id()const noexcept { return t.get_id(); } bool joinable()const noexcept { return t.joinable(); } void join() { t.join(); } void detach() { t.detach(); } std::thread& data()noexcept { return t; } const std::thread& data()const noexcept { return t; } }; \",\"简单使用一下:\",\"int main(){ std::cout << std::this_thread::get_id() << '\\\\n'; joining_thread thread{[]{ std::cout << std::this_thread::get_id() << '\\\\n'; } }; joining_thread thread2{ std::move(thread) }; } \",\"使用容器管理线程对象,等待线程执行结束:\",\"void do_work(std::size_t id){ std::cout << id << '\\\\n'; } int main(){ std::vectorthreads; for (std::size_t i = 0; i < 10; ++i){ threads.emplace_back(do_work, i); // 产生线程 } for(auto& thread:threads){ thread.join(); // 对每个线程对象调用 join() } } \",\"运行测试。\",\"线程对象代表了线程,管理线程对象也就是管理线程,这个 vector 对象管理 10 个线程,保证他们的执行、退出。\",\"使用我们这节实现的 joining_thread 则不需要最后的循环 join():\",\"int main(){ std::vectorthreads; for (std::size_t i = 0; i < 10; ++i){ threads.emplace_back(do_work, i); } } \",\"运行测试。\",\"如果你自己编译了这些代码,相信你注意到了,打印的是乱序的,没什么规律,而且重复运行的结果还不一样,这是正常现象。多线程执行就是如此,无序且操作可能被打断。使用互斥量可以解决这些问题,这也就是下一章节的内容了。\"]},\"19\":{\"h\":\"总结\",\"t\":[\"本章节的内容围绕着:“使用线程”,也就是\\\"使用 std::thread\\\"展开, std::thread 是我们学习 C++ 并发支持库的重中之重,本章的内容并不少见,但是却是少有的准确与完善。即使你早已学习过乃至使用 C++ 标准库进行多线程编程已经很久,我相信本章也一定可以让你收获良多。\",\"如果是第一次学习,有还不够理解的地方,则一定要多思考,或记住,以后多看。\",\"我尽量讲的简单与通俗易懂。学完本章,你大概率还无法在实际环境使用多线程提升程序效率,至少也要学习到使用互斥量,保护共享数据,才可实际使用。\",\"重载决议简单来说就是编译器必须要根据规则选择最合适的函数重载进行调用。 ↩︎\"]},\"20\":{\"h\":\"共享数据\",\"t\":[\"本章节主要内容:\",\"多线程共享数据的问题\",\"使用互斥量保护共享数据\",\"保护共享数据的其它方案\",\"在上一章内容,我们对于线程的基本使用和管理,可以说已经比较了解了,甚至深入阅读了部分的 std::thread 源码。所以如果你好好学习了上一章,本章也完全不用担心。\",\"我们本章,就要开始聊共享数据的那些事。\"]},\"21\":{\"h\":\"条件竞争\",\"t\":[\"在多线程的情况下,每个线程都抢着完成自己的任务。在大多数情况下,即使会改变执行顺序,也是良性竞争,这是无所谓的。比如两个线程都要往标准输出输出一段字符,谁先谁后并不会有什么太大影响。\",\"void f() { std::cout << \\\"❤️\\\\n\\\"; } void f2() { std::cout << \\\"😢\\\\n\\\"; } int main(){ std::thread t{ f }; std::thread t2{ f2 }; t.join(); t2.join(); } \",\"std::cout 的单个 operator<< 调用是线程安全的,不会被打断。即:同步的 C++ 流保证是线程安全的(从多个线程输出的单独字符可能交错,但无数据竞争)\",\"只有在涉及多线程修改相同共享数据的时候,才会导致“恶性的条件竞争”。\",\"std::vectorv; void f() { v.emplace_back(1); } void f2() { v.erase(v.begin()); } int main() { std::thread t{ f }; std::thread t2{ f2 }; t.join(); t2.join(); std::cout << v.size() << '\\\\n'; } \",\"比如这段代码就是典型的恶性条件竞争,两个线程共享一个 vector,并对它进行修改。可能导致许多问题,比如 f2 先执行,此时 vector 还没有元素,导致抛出异常。又或者 f 执行了一半,调用了 f2(),等等。\",\"当然了,也有可能先执行 f,然后执行 f2,最后打印了 0,程序老老实实执行完毕。\",\"但是我们显然不能寄希望于这种操作系统的调度。\",\"而且即使不是一个添加元素,一个删除元素,全是 emplace_back 添加元素,也一样会有问题,由于 std::vector 不是线程安全的容器,因此当多个线程同时访问并修改 v 时,可能会发生未定义的行为。具体来说,当两个线程同时尝试向 v 中添加元素时,但是 emplace_back 函数却是可以被打断的,执行了一半,又去执行另一个线程。可能会导致数据竞争,从而引发未定义的结果。\",\"当某个表达式的求值写入某个内存位置,而另一求值读或修改同一内存位置时,称这些表达式冲突。拥有两个冲突的求值的程序就有数据竞争,除非\",\"两个求值都在同一线程上,或者在同一信号处理函数中执行,或\",\"两个冲突的求值都是原子操作(见 std::atomic),或\",\"一个冲突的求值发生早于 另一个(见 std::memory_order)\",\"如果出现数据竞争,那么程序的行为未定义。\",\"标量类型等都同理,有数据竞争,未定义行为:\",\"int cnt = 0; auto f = [&]{cnt++;}; std::thread t1{f}, t2{f}, t3{f}; // 未定义行为 \"]},\"22\":{\"h\":\"使用互斥量\",\"t\":[\"互斥量(Mutex),又称为互斥锁,是一种用来保护临界区[1]的特殊对象,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:\",\"如果互斥锁是锁定的, 通常说某个特定的线程正持有这个互斥锁\",\"如果没有线程持有这个互斥量,那么这个互斥量就处于解锁状态\",\"概念从来不是我们的重点,用一段对比代码为你直观的展示互斥量的作用:\",\"void f() { std::cout << std::this_thread::get_id() << '\\\\n'; } int main() { std::vectorthreads; for (std::size_t i = 0; i < 10; ++i) threads.emplace_back(f); for (auto& thread : threads) thread.join(); } \",\"这段代码你多次运行它会得到毫无规律和格式的结果,我们可以使用互斥量解决这个问题:\",\"#include // 必要标头 std::mutex m; void f() { m.lock(); std::cout << std::this_thread::get_id() << '\\\\n'; m.unlock(); } int main() { std::vectorthreads; for (std::size_t i = 0; i < 10; ++i) threads.emplace_back(f); for (auto& thread : threads) thread.join(); } \",\"当多个线程执行函数 f 的时候,只有一个线程能成功调用 lock() 给互斥量上锁,其他所有的线程 lock() 的调用将阻塞执行,直至获得锁。第一个调用 lock() 的线程得以继续往下执行,执行我们的 std::cout 输出语句,不会有任何其他的线程打断这个操作。直到线程执行 unlock(),就解锁了互斥量。\",\"那么其他线程此时也就能再有一个成功调用 lock...\",\"至于到底哪个线程才会成功调用,这个是由操作系统调度决定的。\",\"看一遍描述就可以了,简而言之,被 lock() 和 unlock() 包含在其中的代码是线程安全的,同一时间只有一个线程执行,不会被其它线程的执行所打断。\"]},\"23\":{\"h\":\"\",\"t\":[\"不过一般不推荐这样显式的 lock() 与 unlock(),我们可以使用 C++11 标准库引入的“管理类” std::lock_guard:\",\"void f() { std::lock_guardlc{ m }; std::cout << std::this_thread::get_id() << '\\\\n'; } \",\"那么问题来了,std::lock_guard 是如何做到的呢?它是怎么实现的呢?首先顾名思义,这是一个“管理类”模板,用来管理互斥量的上锁与解锁,我们来看它在 MSVC STL 的实现:\",\"_EXPORT_STD template class _NODISCARD_LOCK lock_guard { // class with destructor that unlocks a mutex public: using mutex_type = _Mutex; explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock _MyMutex.lock(); } lock_guard(_Mutex& _Mtx, adopt_lock_t) noexcept // strengthened : _MyMutex(_Mtx) {} // construct but don't lock ~lock_guard() noexcept { _MyMutex.unlock(); } lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete; private: _Mutex& _MyMutex; }; \",\"这段代码极其简单,首先管理类,自然不可移动不可复制,我们定义复制构造与复制赋值为弃置函数,同时阻止了移动等函数的隐式定义。\",\"它只保有一个私有数据成员,一个引用,用来引用互斥量。\",\"构造函数中初始化这个引用,同时上锁,析构函数中解锁,这是一个非常典型的 RAII 式的管理。\",\"同时它还提供一个有额外std::adopt_lock_t参数的构造函数 ,如果使用这个构造函数,则构造函数不会上锁。\",\"所以有的时候你可能会看到一些这样的代码:\",\"void f(){ //code.. { std::lock_guardlc{ m }; // 涉及共享资源的修改的代码... } //code.. } \",\"使用 {} 创建了一个块作用域,限制了对象 lc 的生存期,进入作用域构造 lock_guard 的时候上锁(lock),离开作用域析构的时候解锁(unlock)。\",\"我们要尽可能的让互斥量上锁的粒度小,只用来确保必须的共享资源的线程安全。\",\"“粒度”通常用于描述锁定的范围大小,较小的粒度意味着锁定的范围更小,因此有更好的性能和更少的竞争。\",\"我们举一个例子:\",\"std::mutex m; void add_to_list(int n, std::list& list) { std::vector numbers(n + 1); std::iota(numbers.begin(), numbers.end(), 0); int sum = std::accumulate(numbers.begin(), numbers.end(), 0); { std::lock_guardlc{ m }; list.push_back(sum); } } void print_list(const std::list& list){ std::lock_guardlc{ m }; for(const auto& i : list){ std::cout << i << ' '; } std::cout << '\\\\n'; } \",\"std::list list; std::thread t1{ add_to_list,i,std::ref(list) }; std::thread t2{ add_to_list,i,std::ref(list) }; std::thread t3{ print_list,std::cref(list) }; std::thread t4{ print_list,std::cref(list) }; t1.join(); t2.join(); t3.join(); t4.join(); \",\"完整代码测试。\",\"先看 add_to_list,只有 list.push_back(sum) 涉及到了对共享数据的修改,需要进行保护,我们用 {} 包起来了。\",\"假设有线程 A、B执行函数 add_to_list() :线程 A 中的 numbers、sum 与线程 B 中的,不是同一个,希望大家分清楚,自然不存在数据竞争,也不需要上锁。线程 A、B执行完了前面求 0-n 的计算,只有一个线程能在 lock_guard 的构造函数中成功调用 lock() 给互斥量上锁。假设线程 A 成功调用 lock(),那么线程 B 的 lock() 调用就阻塞了,必须等待线程 A 执行完里面的代码,然后作用域结束,调用 lock_guard 的析构函数,解锁 unlock(),此时线程 B 就可以进去执行了,避免了数据竞争,不存在一个对象同时被多个线程修改。\",\"函数 print_list() 就更简单了,打印 list,给整个函数上锁,同一时刻只能有一个线程执行。\",\"我们的使用代码是多个线程执行这两个函数,两个函数共享了一个锁,这样确保了当执行函数 print_list() 打印的时候,list 的状态是确定的。打印函数 print_list 和 add_to_list 函数的修改操作同一时间只能有一个线程在执行。print_list() 不可能看到正在被add_to_list() 修改的 list。\",\"至于到底哪个函数哪个线程会先执行,执行多少次,这些都由操作系统调度决定,也完全有可能连续 4 次都是执行函数 print_list 的线程成功调用 lock,会打印出了一样的值,这都很正常。\",\"C++17 添加了一个新的特性,类模板实参推导, std::lock_guard 可以根据传入的参数自行推导,而不需要写明模板类型参数:\",\"std::mutex m; std::lock_guard lc{ m }; // std::lock_guard \",\"并且 C++17 还引入了一个新的“管理类”:std::scoped_lock,它相较于 lock_guard的区别在于,它可以管理多个互斥量。不过对于处理一个互斥量的情况,它和 lock_guard 几乎完全相同。\",\"std::mutex m; std::scoped_lock lc{ m }; // std::scoped_lock \",\"我们在后续管理多个互斥量,会详细了解这个类。\"]},\"24\":{\"h\":\"\",\"t\":[\"try_lock 是互斥量中的一种尝试上锁的方式。与常规的 lock 不同,try_lock 会尝试上锁,但如果锁已经被其他线程占用,则不会阻塞当前线程,而是立即返回。\",\"它的返回类型是 bool ,如果上锁成功就返回 true,失败就返回 false。\",\"这种方法在多线程编程中很有用,特别是在需要保护临界区的同时,又不想线程因为等待锁而阻塞的情况下。\",\"std::mutex mtx; void threadFunction(int id) { // 尝试加锁 if (mtx.try_lock()) { std::cout << \\\"线程:\\\" << id << \\\" 获得锁\\\" << std::endl; // 临界区代码 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟临界区操作 mtx.unlock(); // 解锁 std::cout << \\\"线程:\\\" << id << \\\" 释放锁\\\" << std::endl; } else { std::cout << \\\"线程:\\\" << id << \\\" 获取锁失败 处理步骤\\\" << std::endl; } } \",\"如果有两个线程运行这段代码,必然有一个线程无法成功上锁,要走 else 的分支。\",\"std::thread t1(threadFunction, 1); std::thread t2(threadFunction, 2); t1.join(); t2.join(); \",\"运行测试。\"]},\"25\":{\"h\":\"保护共享数据\",\"t\":[\"互斥量主要也就是为了保护共享数据,上一节的使用互斥量也已经为各位展示了一些。\",\"然而使用互斥量来保护共享数据也并不是在函数中加上一个 std::lock_guard 就万事大吉了。有的时候只需要一个指针或者引用,就能让这种保护形同虚设。\",\"class Data{ int a{}; std::string b{}; public: void do_something(){ // 修改数据成员等... } }; class Data_wrapper{ Data data; std::mutex m; public: template void process_data(Func func){ std::lock_guardlc{m}; func(data); // 受保护数据传递给函数 } }; Data* p = nullptr; void malicious_function(Data& protected_data){ p = &protected_data; // 受保护的数据被传递 } Data_wrapper d; void foo(){ d.process_data(malicious_function); // 传递了一个恶意的函数 p->do_something(); // 在无保护的情况下访问保护数据 } \",\"成员函数模板 process_data 看起来一点问题也没有,使用 std::lock_guard 对数据做了保护,但是调用方传递了 malicious_function 这样一个恶意的函数,使受保护数据传递给外部,可以在没有被互斥量保护的情况下调用 do_something()。\",\"我们传递的函数就不该是涉及外部副作用的,就应该是单纯的在受互斥量保护的情况下老老实实调用 do_something() 操作受保护的数据。\",\"简而言之:切勿将受保护数据的指针或引用传递到互斥量作用域之外,不然保护将形同虚设。\",\"process_data 的确算是没问题,用户非要做这些事情也是防不住的,我们只是告诉各位可能的情况。\"]},\"26\":{\"h\":\"死锁:问题与解决\",\"t\":[\"试想一下,有一个玩具,这个玩具有两个部分,必须同时拿到两部分才能玩。比如一个遥控汽车,需要遥控器和玩具车才能玩。有两个小孩,他们都想玩这个玩具。当其中一个小孩拿到了遥控器和玩具车时,就可以尽情玩耍。当另一个小孩也想玩,他就得等待另一个小孩玩完才行。再试想,遥控器和玩具车被放在两个不同的地方,并且两个小孩都想要玩,并且一个拿到了遥控器,另一个拿到了玩具车。问题就出现了,除非其中一个孩子决定让另一个先玩,他把自己的那个部分给另一个小孩。但如果他们都不愿意,那么这个遥控汽车就谁都没有办法玩。\",\"我们当然不在乎小孩抢玩具,我们要聊的是线程对锁的竞争:两个线程需要对它们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个线程的互斥量解锁。因为它们都在等待对方释放互斥量,没有线程工作。 这种情况就是死锁。\",\"多个互斥量才可能遇到死锁问题。\",\"避免死锁的一般建议是让两个互斥量以相同的顺序上锁,总在互斥量 B 之前锁住互斥量 A,就通常不会死锁。反面示例\",\"std::mutex m1,m2; std::size_t n{}; void f(){ std::lock_guardlc1{ m1 }; std::lock_guardlc2{ m2 };; ++n; } void f2() { std::lock_guardlc1{ m2 }; std::lock_guardlc2{ m1 }; ++n; } \",\"f 与 f2 因为互斥量上锁顺序不同,就有死锁风险。函数 f 先锁定 m1,然后再尝试锁定 m2,而函数 f2 先锁定 m2 再锁定 m1 。如果两个线程同时运行,它们就可能会彼此等待对方释放其所需的锁,从而造成死锁。\",\"简而言之,有可能函数 f 锁定了 m1,函数 f2 锁定了 m2,函数 f 要往下执行,给 m2 上锁,所以在等待 f2 解锁 m2,然而函数 f2 也在等待函数 f 解锁 m1 它才能往下执行。所以死锁。测试代码。\",\"但是有的时候即使固定锁顺序,依旧会产生问题。当有多个互斥量保护同一个类的对象时,对于相同类型的两个不同对象进行数据的交换操作,为了保证数据交换的正确性,就要避免其它线程修改,确保每个对象的互斥量都锁住自己要保护的区域。如果按照前面的的选择一个固定的顺序上锁解锁,则毫无意义,比如:\",\"struct X{ X(const std::string& str) :object{ str } {} friend void swap(X& lhs, X& rhs); private: std::string object; std::mutex m; }; void swap(X& lhs, X& rhs) { if (&lhs == &rhs) return; std::lock_guardlock1{ lhs.m }; std::lock_guardlock2{ rhs.m }; swap(lhs.object, rhs.object); } \",\"考虑用户调用的时候将参数交换,就会产生死锁:\",\"X a{ \\\"🤣\\\" }, b{ \\\"😅\\\" }; std::thread t{ [&] {swap(a, b); } }; // 1 std::thread t2{ [&] {swap(b, a); } }; // 2 \",\"1 执行的时候,先上锁 a 的互斥量,再上锁 b 的互斥量。\",\"2 执行的时候,先上锁 b 的互斥量,再上锁 a 的互斥量。\",\"完全可能线程 A 执行 1 的时候上锁了 a 的互斥量,线程 B 执行 2 上锁了 b 的互斥量。线程 A 往下执行需要上锁 b 的互斥量,线程 B 则要上锁 a 的互斥量执行完毕才能解锁,哪个都没办法往下执行,死锁。测试代码。\",\"其实也就是回到了第一个示例的问题。\",\"C++ 标准库有很多办法解决这个问题,可以使用 std::lock ,它能一次性锁住多个互斥量,并且没有死锁风险。修改 swap 代码后如下:\",\"void swap(X& lhs, X& rhs) { if (&lhs == &rhs) return; std::lock(lhs.m, rhs.m); // 给两个互斥量上锁 std::lock_guardlock1{ lhs.m,std::adopt_lock }; std::lock_guardlock2{ rhs.m,std::adopt_lock }; swap(lhs.object, rhs.object); } \",\"因为前面已经使用了 std::lock 上锁,所以后的 std::lock_guard 构造都额外传递了一个 std::adopt_lock 参数,让其选择到不上锁的构造函数。函数退出也能正常解锁。\",\"std::lock 给 lhs.m 或 rhs.m 上锁时若抛出异常,则在重抛前对任何已锁的对象调用 unlock() 解锁,也就是 std::lock 要么将互斥量都上锁,要么一个都不锁。\",\"C++17 新增了 std::scoped_lock ,提供此函数的 RAII 包装,通常它比裸调用 std::lock 更好。\",\"所以我们前面的代码可以改写为:\",\"void swap(X& lhs, X& rhs) { if (&lhs == &rhs) return; std::scoped_lock guard{ lhs.m,rhs.m }; swap(lhs.object, rhs.object); } \",\"对此类有兴趣或任何疑问,建议阅读std::scoped_lock 的源码实现与解析\",\"使用 std::scoped_lock 可以将所有 std::lock 替换掉,减少错误发生。\",\"然而它们的帮助都是有限的,一切最终都是依靠开发者使用与管理。\",\"死锁是多线程编程中令人相当头疼的问题,并且死锁经常是不可预见,甚至难以复现,因为在大部分时间里,程序都能正常完成工作。我们可以通过一些简单的规则,约束开发者的行为,帮助写出“无死锁”的代码。\",\"避免嵌套锁\",\"线程获取一个锁时,就别再获取第二个锁。每个线程只持有一个锁,自然不会产生死锁。如果必须要获取多个锁,使用 std::lock 。\",\"避免在持有锁时调用外部代码\",\"这个建议是很简单的:因为代码是外部提供的,所以没办法确定外部要做什么。外部程序可能做任何事情,包括获取锁。在持有锁的情况下,如果用外部代码要获取一个锁,就会违反第一个指导意见,并造成死锁(有时这是无法避免的)。当写通用代码时(比如保护共享数据中的 Date 类)。这不是接口设计者可以处理的,只能寄希望于调用方传递的代码是能正常执行的。\",\"使用固定顺序获取锁\",\"如同第一个示例那样,固定的顺序上锁就不存在问题。\"]},\"27\":{\"h\":\"灵活的锁\",\"t\":[\"std::unique_lock 是 C++11 引入的一种通用互斥包装器,它相比于 std::lock_guard 更加的灵活。当然,它也更加的复杂,尤其它还可以与我们下一章要讲的条件变量一起使用。使用它可以将之前使用 std::lock_guard 的 swap 改写一下:\",\"void swap(X& lhs, X& rhs) { if (&lhs == &rhs) return; std::unique_locklock1{ lhs.m, std::defer_lock }; std::unique_locklock2{ rhs.m, std::defer_lock }; std::lock(lock1, lock2); swap(lhs.object, rhs.object); ++n; } \",\"解释这段代码最简单的方式就是直接展示标准库的源码,首先,我们要了解 std::defer_lock 是“假设调用方线程已拥有互斥体的所有权”。没有所有权自然构造函数就不会上锁,但不止如此。我们还要先知道 std::unique_lock 保有的数据成员(都以 MSVC STL 为例):\",\"private: _Mutex* _Pmtx = nullptr; bool _Owns = false; \",\"如你所见很简单,一个互斥量的指针,还有一个就是表示对象是否拥有互斥量所有权的 bool 类型的对象 _Owns 了。我们前面代码会调用构造函数:\",\"unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept : _Pmtx(_STD addressof(_Mtx)), _Owns(false) {} // construct but don't lock \",\"如你所见,只是初始化了数据成员而已,注意,这个构造函数没有给互斥量上锁,且 _Owns 为 false 表示没有互斥量所有权。并且 std::unique_lock 是有 lock() 、try_lock() 、unlock() 成员函数的,所以可以直接传递给 std::lock、 进行调用。这里还需要提一下 lock() 成员函数的代码:\",\"void lock() { // lock the mutex _Validate(); _Pmtx->lock(); _Owns = true; } \",\"如你所见,正常上锁,并且把 _Owns 设置为 true,即表示当前对象拥有互斥量的所有权。那么接下来看析构函数:\",\"~unique_lock() noexcept { if (_Owns) { _Pmtx->unlock(); } } \",\"必须得是当前对象拥有互斥量的所有权析构函数才会调用 unlock() 解锁互斥量。我们的代码因为调用了 lock ,所以 _Owns 设置为 true ,函数结束的时候会解锁互斥量。\",\"设计挺奇怪的对吧,这个所有权语义。其实上面的代码还不够简单直接,我们再举个例子:\",\"std::mutex m; int main() { std::unique_locklock{ m,std::adopt_lock }; lock.lock(); } \",\"这段代码运行会抛出异常,原因很简单,因为 std::adopt_lock 只是不上锁,但是有所有权,即 _Owns 设置为 true 了,当运行 lock() 成员函数的时候,调用了 _Validate() 进行检测,也就是:\",\"void _Validate() const { // check if the mutex can be locked if (!_Pmtx) { _Throw_system_error(errc::operation_not_permitted); } if (_Owns) { _Throw_system_error(errc::resource_deadlock_would_occur); } } \",\"满足第二个 if,因为 _Owns 为 true 所以抛出异常,别的标准库也都有类似设计。很诡异的设计对吧,正常。除非我们写成:\",\"lock.mutex()->lock(); \",\"也就是说 std::unique_lock 要想调用 lock() 成员函数,必须是当前没有所有权。\",\"所以正常的用法其实是,先上锁了互斥量,然后传递 std::adopt_lock 构造 std::unique_lock 对象表示拥有互斥量的所有权,即可在析构的时候正常解锁。如下:\",\"std::mutex m; int main() { m.lock(); std::unique_locklock{ m,std::adopt_lock }; } \",\"简而言之:\",\"使用 std::defer_lock 构造函数不上锁,要求构造之后上锁\",\"使用 std::adopt_lock 构造函数不上锁,要求在构造之前互斥量上锁\",\"默认构造会上锁,要求构造函数之前和构造函数之后都不能再次上锁\",\"我们前面提到了 std::unique_lock 更加灵活,那么灵活在哪?很简单,它拥有 lock() 和 unlock() 成员函数,所以我们能写出如下代码:\",\"void f() { //code.. std::unique_locklock{ m }; // 涉及共享资源的修改的代码... lock.unlock(); // 解锁并释放所有权,析构函数不会再 unlock() //code.. } \",\"而不是像之前 std::lock_guard 一样使用 {}。\",\"另外再聊一聊开销吧,其实倒也还好,多了一个 bool ,内存对齐,x64 环境也就是 16 字节。这都不是最重要的,主要是复杂性和需求,通常建议优先 std::lock_guard,当它无法满足你的需求或者显得代码非常繁琐,那么可以考虑使用 std::unique_lock。\"]},\"28\":{\"h\":\"在不同作用域传递互斥量\",\"t\":[\"首先我们要明白,互斥量满足互斥体 (Mutex)的要求,不可复制不可移动。所谓的在不同作用域传递互斥量,其实只是传递了它们的指针或者引用罢了。可以利用各种类来进行传递,比如前面提到的 std::unique_lock。\",\"std::unique_lock 可以获取互斥量的所有权,而互斥量的所有权可以通过移动操作转移给其他的 std::unique_lock 对象。有些时候,这种转移(就是调用移动构造)是自动发生的,比如当函数返回std::unique_lock 对象。另一种情况就是得显式使用 std::move。\",\"请勿对移动语义和转移所有权抱有错误的幻想,我们说的无非是调用 std::unique_lock 的移动构造罢了:\",\"_NODISCARD_CTOR_LOCK unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { _Other._Pmtx = nullptr; _Other._Owns = false; } \",\"将数据成员赋给新对象,原来的置空,这就是所谓的 “所有权”转移,切勿被词语迷惑。\",\"std::unique_lock 是只能移动不可复制的类,它移动即标志其管理的互斥量的所有权转移了。\",\"一种可能的使用是允许函数去锁住一个互斥量,并将互斥量的所有权转移到调用者上,所以调用者可以在这个锁保护的范围内执行代码。\",\"std::unique_lockget_lock(){ extern std::mutex some_mutex; std::unique_locklk{ some_mutex }; return lk; } void process_data(){ std::unique_locklk{ get_lock() }; // 执行一些任务... } \",\"return lk 这里会调用移动构造,将互斥量的所有权转移给调用方, process_data 函数结束的时候会解锁互斥量。\",\"我相信你可能对 extern std::mutex some_mutex 有疑问,其实不用感到奇怪,这是一个互斥量的声明,可能别的翻译单元(或 dll 等)有它的定义,成功链接上。我们前面也说了:“所谓的在不同作用域传递互斥量,其实只是传递了它们的指针或者引用罢了”,所以要特别注意互斥量的生存期。\",\"extern 说明符只能搭配变量声明和函数声明(除了类成员或函数形参)。它指定外部链接,而且技术上不影响存储期,但它不能用来定义自动存储期的对象,故所有 extern 对象都具有静态或线程存储期。\",\"如果你简单写一个 std::mutex some_mutex 那么函数 process_data 中的 lk 会持有一个悬垂指针。\",\"举一个使用 extern std::mutex 的完整运行示例。当然,其实理论上你 new std::mutex 也是完全可行...... 🤣🤣\",\"std::unique_lock 是灵活的,同样允许在对象销毁之前就解锁互斥量,调用 unlock() 成员函数即可,不再强调。\"]},\"29\":{\"h\":\"保护共享数据的初始化过程\",\"t\":[\"保护共享数据并非必须使用互斥量,互斥量只是其中一种常见的方式而已,对于一些特殊的场景,也有专门的保护方式,比如对于共享数据的初始化过程的保护。我们通常就不会用互斥量,这会造成很多的额外开销。\",\"我们不想为各位介绍其它乱七八糟的各种保护初始化的方式,我们只介绍三种:双检锁(错误)、使用 std::call_once、静态局部变量初始化在 C++11 是线程安全。\",\"双检锁(错误)线程不安全\",\"void f(){ if(!ptr){ // 1 std::lock_guardlk{ m }; if(!ptr){ // 2 ptr.reset(new some); // 3 } } ptr->do_something(); // 4 } \",\"① 是查看指针是否为空,空才需要初始化,才需要获取锁。指针为空,当获取锁后会再检查一次指针②(这就是双重检查),避免另一线程在第一次检查后再做初始化,并且让当前线程获取锁。\",\"然而这显然没用,因为有潜在的条件竞争。未被锁保护的读取操作①没有与其他线程里被锁保护的写入操作③进行同步,因此就会产生条件竞争。\",\"简而言之:一个线程知道另一个线程已经在执行③,但是此时还没有创建 some 对象,而只是分配内存对指针写入。那么这个线程在①的时候就不会进入,直接执行了 ptr->do_something()④,得不到正确的结果,因为对象还没构造。\",\"如果你觉得难以理解,那就记住 ptr.reset(new some); 并非是不可打断不可交换的固定指令。\",\"这种错误写法在一些单例中也非常的常见。如果你的同事或上司写出此代码,一般不建议指出,因为不见得你能教会他们,不要“没事找事”,只要不影响自己即可。\",\"C++ 标准委员会也认为处理此问题很重要,所以标准库提供了 std::call_once 和 std::once_flag 来处理这种情况。比起锁住互斥量并显式检查指针,每个线程只需要使用 std::call_once 就可以。使用 std::call_once 比显式使用互斥量消耗的资源更少,特别是当初始化完成之后。\",\"std::shared_ptrptr; std::mutex m; std::once_flag resource_flag; void init_resource(){ ptr.reset(new some); } void foo(){ std::call_once(resource_flag, init_resource); // 线程安全的一次初始化 ptr->do_something(); } \",\"以上代码 std::once_flag 对象是命名空间作用域声明,如果你有需要,它也可以是类的成员。用于搭配 std::call_once 使用,保证线程安全的一次初始化。std::call_once 只需要接受可调用 (Callable)对象即可,也不要求一定是函数。\",\"“初始化”,自然是一次。但是 std::call_once 也有一些例外情况(比如异常)会让传入的可调用对象被多次调用,即“多次”初始化:\",\"std::once_flag flag; int n = 0; void f(){ std::call_once(flag, [] { ++n; std::cout << \\\"第\\\" << n << \\\"次调用\\\\n\\\"; throw std::runtime_error(\\\"异常\\\"); }); } int main(){ try{ f(); } catch (std::exception&){} try{ f(); } catch (std::exception&){} } \",\"测试链接。正常情况会保证传入的可调用对象只调用一次,即初始化只有一次。异常之类的是例外。\",\"静态局部变量初始化在 C++11 是线程安全\",\"class my_class; my_class& get_my_class_instance(){ static my_class instance; // 线程安全的初始化过程 初始化严格发生一次 } \",\"多线程可以安全的调用 get_my_class_instance 函数,不用为数据竞争而担心。此方式也在单例中多见,是简单合理的做法。\",\"其实还有不少其他的做法或者反例,但是觉得没必要再聊了,因为本文不是详尽的文档,而是“教程”。\"]},\"30\":{\"h\":\"保护不常更新的数据结构\",\"t\":[\"试想一下,你有一个数据结构存储了用户的设置信息,每次用户打开程序的时候,都要进行读取,且运行时很多地方都依赖这个数据结构需要读取,所以为了效率,我们使用了多线程读写。这个数据结构很少进行改变,而我们知道,多线程读取,是没有数据竞争的,是安全的,但是有些时候又不可避免的有修改和读取都要工作的时候,所以依然必须得使用互斥量进行保护。\",\"然而使用 std::mutex 的开销是过大的,它不管有没有发生数据竞争(也就是就算全是读的情况)也必须是老老实实上锁解锁,只有一个线程可以运行。如果你学过其它语言或者操作系统,相信这个时候就已经想到了:“读写锁”。\",\"C++ 标准库自然为我们提供了: std::shared_timed_mutex(C++14)、 std::shared_mutex(C++17)。它们的区别简单来说,前者支持更多的操作方式,后者有更高的性能优势。\",\"std::shared_mutex 同样支持 std::lock_guard、std::unique_lock。和 std::mutex 做的一样,保证写线程的独占访问。而那些无需修改数据结构的读线程,可以使用 std::shared_lock 获取访问权,多个线程可以一起读取。\",\"class Settings { private: std::map data_; mutable std::shared_mutex mutex_; // “M&M 规则”:mutable 与 mutex 一起出现 public: void set(const std::string& key, const std::string& value) { std::lock_guard lock(mutex_); data_[key] = value; } std::string get(const std::string& key) const { std::shared_lock lock(mutex_); auto it = data_.find(key); return (it != data_.end()) ? it->second : \\\"\\\"; // 如果没有找到键返回空字符串 } }; \",\"完整代码。测试链接。标准输出可能交错,但无数据竞争。\",\"std::shared_timed_mutex 具有 std::shared_mutex 的所有功能,并且额外支持超时功能。所以以上代码可以随意更换这两个互斥量。\"]},\"31\":{\"h\":\"\",\"t\":[\"线程对已经上锁的 std::mutex 再次上锁是错误的,这是未定义行为。然而在某些情况下,一个线程会尝试在释放一个互斥量前多次获取,所以提供了std::recursive_mutex。\",\"std::recursive_mutex 是 C++ 标准库提供的一种互斥量类型,它允许同一线程多次锁定同一个互斥量,而不会造成死锁。当同一线程多次对同一个 std::recursive_mutex 进行锁定时,只有在解锁与锁定次数相匹配时,互斥量才会真正释放。但它并不影响不同线程对同一个互斥量进行锁定的情况。不同线程对同一个互斥量进行锁定时,会按照互斥量的规则进行阻塞,\",\"#include #include #include std::recursive_mutex mtx; void recursive_function(int count) { // 递归函数,每次递归都会锁定互斥量 mtx.lock(); std::cout << \\\"Locked by thread: \\\" << std::this_thread::get_id() << \\\", count: \\\" << count << std::endl; if (count > 0) { recursive_function(count - 1); // 递归调用 } mtx.unlock(); // 解锁互斥量 } int main() { std::thread t1(recursive_function, 3); std::thread t2(recursive_function, 2); t1.join(); t2.join(); } \",\"运行测试。\",\"lock:线程可以在递归互斥体上重复调用 lock。在线程调用 unlock 匹配次数后,所有权才会得到释放。\",\"unlock:若所有权层数为 1(此线程对 lock() 的调用恰好比 unlock() 多一次 )则解锁互斥体,否则将所有权层数减少 1。\",\"我们重点的强调了一下这两个成员函数的这个概念,其实也很简单,总而言之就是 unlock 必须和 lock 的调用次数一样,才会真正解锁互斥量。\",\"同样的,我们也可以使用 std::lock_guard、std::unique_lock 帮我们管理 std::recursive_mutex,而非显式调用 lock 与 unlock:\",\"void recursive_function(int count) { std::lock_guardlc{ mtx }; std::cout << \\\"Locked by thread: \\\" << std::this_thread::get_id() << \\\", count: \\\" << count << std::endl; if (count > 0) { recursive_function(count - 1); } } \",\"运行测试。\"]},\"32\":{\"h\":\"、 是线程安全的吗?\",\"t\":[\"如果你的标准达到 C++11,要求下列函数是线程安全的:\",\"new 运算符和 delete 运算符的库版本\",\"全局 new 运算符和 delete 运算符的用户替换版本\",\"std::calloc、std::malloc、std::realloc、std::aligned_alloc (C++17 起)、std::free\",\"所以以下函数在多线程运行是线程安全的:\",\"void f(){ T* p = new T{}; delete p; } \",\"内存分配、释放操作是线程安全,构造和析构不涉及共享资源。而局部对象 p 对于每个线程来说是独立的。换句话说,每个线程都有其自己的 p 对象实例,因此它们不会共享同一个对象,自然没有数据竞争。\",\"如果 p 是全局对象(或者外部的,只要可被多个线程读写),多个线程同时对其进行访问和修改时,就可能会导致数据竞争和未定义行为。因此,确保全局对象的线程安全访问通常需要额外的同步措施,比如互斥量或原子操作。\",\"T* p = nullptr; void f(){ p = new T{}; // 存在数据竞争 delete p; } \",\"即使 p 是局部对象,如果构造函数(析构同理)涉及读写共享资源,那么一样存在数据竞争,需要进行额外的同步措施进行保护。\",\"int n = 1; struct X{ X(int v){ ::n += v; } }; void f(){ X* p = new X{ 1 }; // 存在数据竞争 delete p; } \",\"一个直观的展示是,我们可以在构造函数中使用 std::cout,看到无序的输出,例子。\",\"值得注意的是,如果是自己重载 operator new、operator delete 替换了库的全局版本,那么它的线程安全就要我们来保证。\",\"// 全局的 new 运算符,替换了库的版本 void* operator new (std::size_t count){ return ::operator new(count); } \",\"以上代码是线程安全的,因为 C++11 保证了 new 运算符的库版本,即 ::operator new 是线程安全的,我们直接调用它自然不成问题。如果你需要更多的操作,就得使用互斥量之类的方式保护了。\",\"总而言之,new 表达式线程安全要考虑三方面:operator new、构造函数、修改指针。\",\"delete 表达式线程安全考虑两方面:operator delete、析构函数。\",\"C++ 只保证了 operator new、operator delete 这两个方面的线程安全(不包括用户定义的),其它方面就得自己保证了。前面的内容也都提到了。\"]},\"33\":{\"h\":\"总结\",\"t\":[\"本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题。并说明了可以使用互斥量(std::mutex)保护共享数据,并且要注意互斥量上锁的“粒度”。C++标准库提供了很多工具,包括管理互斥量的管理类(std::lock_guard),但是互斥量只能解决它能解决的问题,并且它有自己的问题(死锁)。同时我们讲述了一些避免死锁的方法和技术。还讲了一下互斥量所有权转移。然后讨论了面对不同情况保护共享数据的不同方式,使用 std::call_once() 保护共享数据的初始化过程,使用读写锁(std::shared_mutex)保护不常更新的数据结构。以及特殊情况可能用到的互斥量 recursive_mutex,有些人可能喜欢称作:递归锁。最后聊了一下 new、delete 运算符的库函数实际是线程安全的,以及一些问题。\",\"下一章,我们将开始讲述同步操作,会使用到 Futures、条件变量等设施。\",\"\\\"临界区\\\"指的是一个访问共享资源的程序片段,而这些共享资源又无法同时被多个线程访问的特性。在临界区中,通常会使用同步机制,比如我们要讲的互斥量(Mutex)。 ↩︎\"]},\"34\":{\"h\":\"同步操作\",\"t\":[\"\\\"同步操作\\\"是指在计算机科学和信息技术中的一种操作方式,其中不同的任务或操作按顺序执行,一个操作完成后才能开始下一个操作。在同步操作中,各个任务之间通常需要相互协调和等待,以确保数据的一致性和正确性。\",\"本章的主要内容有:\",\"条件变量\",\"std::future 等待异步任务\",\"在规定时间内等待\",\"本章将讨论如何使用条件变量等待事件,介绍 future 等标准库设施用作同步操作。\"]},\"35\":{\"h\":\"等待事件或条件\",\"t\":[\"假设你正在一辆夜间运行的地铁上,那么你要如何在正确的站点下车呢?\",\"一直不休息,每一站都能知道,这样就不会错过你要下车的站点,但是这会很疲惫。\",\"可以看一下时间,估算一下地铁到达目的地的时间,然后设置一个稍早的闹钟,就休息。这个方法听起来还行,但是你可能被过早的叫醒,甚至估算错误导致坐过站,又或者闹钟没电了睡过站。\",\"事实上最简单的方式是,到站的时候有人或者其它东西能将你叫醒(比如手机的地图,到达设置的位置就提醒)。\",\"这和线程有什么关系呢?其实第一种方法就是在说”忙等待(busy waiting)”也称“自旋“。\",\"bool flag = false; std::mutex m; void wait_for_flag(){ std::unique_locklk{ m }; while (!flag){ lk.unlock(); // 1 解锁互斥量 lk.lock(); // 2 上锁互斥量 } } \",\"第二种方法就是加个延时,这种实现进步了很多,减少浪费的执行时间,但很难确定正确的休眠时间。这会影响到程序的行为,在需要快速响应的程序中就意味着丢帧或错过了一个时间片。循环中,休眠②前函数对互斥量解锁①,再休眠结束后再对互斥量上锁,让另外的线程有机会获取锁并设置标识(因为修改函数和等待函数共用一个互斥量)。\",\"void wait_for_flag(){ std::unique_locklk{ m }; while (!flag){ lk.unlock(); // 1 解锁互斥量 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 2 休眠 lk.lock(); // 3 上锁互斥量 } } \",\"第三种方式(也是最好的)实际上就是使用条件变量了。通过另一线程触发等待事件的机制是最基本的唤醒方式,这种机制就称为“条件变量”。\",\"C++ 标准库对条件变量有两套实现:std::condition_variable 和 std::condition_variable_any,这两个实现都包含在 头文件中。\",\"condition_variable_any 类是 std::condition_variable 的泛化。相对于只在 std::unique_lock 上工作的 std::condition_variable,condition_variable_any 能在任何满足可基本锁定(BasicLockable)要求的锁上工作,所以增加了 _any 后缀。显而易见,这种区分必然是 any 版更加通用但是却又更多的性能开销。所以通常首选std::condition_variable。有特殊需求,才会考虑 std::condition_variable_any。\",\"std::mutex mtx; std::condition_variable cv; bool arrived = false; void waitForArrival() { std::unique_lock lck(mtx); cv.wait(lck, []{ return arrived; }); // 等待 arrived 变为 true std::cout << \\\"到达目的地,可以下车了!\\\" << std::endl; } void simulateArrival() { std::this_thread::sleep_for(std::chrono::seconds(5)); // 模拟地铁到站,假设5秒后到达目的地 { std::lock_guard lck(mtx); arrived = true; // 设置条件变量为 true,表示到达目的地 } cv.notify_one(); // 通知等待的线程 } \",\"运行测试。更换为 std::condition_variable_any 效果相同。\",\"std::mutex mtx: 创建了一个互斥量,用于保护共享数据的访问,确保在多线程环境下的数据同步。\",\"std::condition_variable cv: 创建了一个条件变量,用于线程间的同步,当条件不满足时,线程可以等待,直到条件满足时被唤醒。\",\"bool arrived = false: 设置了一个标志位,表示是否到达目的地。\",\"在 waitForArrival 函数中:\",\"std::unique_lock lck(mtx): 使用互斥量创建了一个独占锁。\",\"cv.wait(lck, []{ return arrived; }): 阻塞当前线程,释放(unlock)锁,直到条件被满足。\",\"一旦条件满足,即 arrived 变为 true,并且条件变量 cv 被唤醒(包括虚假唤醒),那么当前线程会重新获取锁(lock),并执行后续的操作。\",\"在 simulateArrival 函数中:\",\"std::this_thread::sleep_for(std::chrono::seconds(5)): 模拟地铁到站,暂停当前线程 5 秒。\",\"设置 arrived 为 true,表示到达目的地。\",\"cv.notify_one(): 唤醒一个等待条件变量的线程。\",\"这样,当 simulateArrival 函数执行后,arrived 被设置为 true,并且通过 cv.notify_one() 唤醒了等待在条件变量上的线程,从而使得 waitForArrival 函数中的等待结束,可以执行后续的操作,即输出提示信息。\",\"条件变量的 wait 成员函数有两个版本,以上代码使用的就是第二个版本,传入了一个谓词。\",\"void wait(std::unique_lock& lock); // 1 template void wait(std::unique_lock& lock, Predicate pred); // 2 \",\"②等价于:\",\"while (!pred()) wait(lock); \",\"这可以避免“虚假唤醒(spurious wakeup)”。\",\"条件变量虚假唤醒是指在使用条件变量进行线程同步时,有时候线程可能会在没有收到通知的情况下被唤醒。问题取决于程序和系统的具体实现。解决方法很简单,在循环中等待并判断条件可一并解决。使用 C++ 标准库则没有这个烦恼了。\"]},\"36\":{\"h\":\"线程安全的队列\",\"t\":[\"在本节中,我们介绍了一个更为复杂的示例,以巩固我们对条件变量的学习。为了实现一个线程安全的队列,我们需要考虑以下两个关键点:\",\"当执行 push 操作时,需要确保没有其他线程正在执行 push 或 pop 操作;同样,在执行 pop 操作时,也需要确保没有其他线程正在执行 push 或 pop 操作。\",\"当队列为空时,不应该执行 pop 操作。因此,我们需要使用条件变量来传递一个谓词,以确保在执行 pop 操作时队列不为空。\",\"基于以上思考,我们设计了一个名为 threadsafe_queue 的模板类,如下:\",\"template class threadsafe_queue { mutable std::mutex m; // 互斥量,用于保护队列操作的独占访问 std::condition_variable data_cond; // 条件变量,用于在队列为空时等待 std::queue data_queue; // 实际存储数据的队列 public: threadsafe_queue() {} void push(T new_value) { { std::lock_guardlk(m); data_queue.push(new_value); } data_cond.notify_one(); } // 从队列中弹出元素(阻塞直到队列不为空) void pop(T& value) { std::unique_locklk(m); data_cond.wait(lk, [this] {return !data_queue.empty(); }); value = data_queue.front(); data_queue.pop(); } // 从队列中弹出元素(阻塞直到队列不为空),并返回一个指向弹出元素的 shared_ptr std::shared_ptr pop() { std::unique_locklk(m); data_cond.wait(lk, [this] {return !data_queue.empty(); }); std::shared_ptrres(std::make_shared(data_queue.front())); data_queue.pop(); return res; } bool empty()const { std::lock_guardlk(m); return data_queue.empty(); } }; \",\"请无视我们省略的构造、赋值、交换、try_xx 等操作。以上示例已经足够。\",\"光写好了肯定不够,我们还得测试运行,我们可以写一个经典的:”生产者消费者模型“,也就是一个线程 push ”生产“,一个线程 pop ”消费“。\",\"void producer(threadsafe_queue& q) { for (int i = 0; i < 5; ++i) { q.push(i); } } void consumer(threadsafe_queue& q) { for (int i = 0; i < 5; ++i) { int value{}; q.pop(value); } } \",\"两个线程分别运行 producer 与 consumer,为了观测运行我们可以为 push 与 pop 中增加打印语句:\",\"std::cout << \\\"push:\\\" << new_value << std::endl; std::cout << \\\"pop:\\\" << value << std::endl; \",\"可能的运行结果是:\",\"push:0 pop:0 push:1 pop:1 push:2 push:3 push:4 pop:2 pop:3 pop:4 \",\"这很正常,到底哪个线程会抢到 CPU 时间片持续运行,是系统调度决定的,我们只需要保证一开始提到的两点就行了:\",\"push 与 pop 都只能单独执行;当队列为空时,不执行 pop 操作。\",\"我们可以给一个简单的示意图帮助你理解这段运行结果:\",\"初始状态:队列为空 +---+---+---+---+---+ Producer 线程插入元素 0: +---+---+---+---+---+ | 0 | | | | | Consumer 线程弹出元素 0: +---+---+---+---+---+ | | | | | | Producer 线程插入元素 1: +---+---+---+---+---+ | 1 | | | | | Consumer 线程弹出元素 1: +---+---+---+---+---+ | | | | | | Producer 线程插入元素 2: +---+---+---+---+---+ | | 2 | | | | Producer 线程插入元素 3: +---+---+---+---+---+ | | 2 | 3 | | | Producer 线程插入元素 4: +---+---+---+---+---+ | | 2 | 3 | 4 | | Consumer 线程弹出元素 2: +---+---+---+---+---+ | | | 3 | 4 | | Consumer 线程弹出元素 3: +---+---+---+---+---+ | | | | 4 | | Consumer 线程弹出元素 4: +---+---+---+---+---+ | | | | | | 队列为空,所有元素已被弹出 \",\"到此,也就可以了。\"]},\"37\":{\"h\":\"使用\",\"t\":[\"其实就是异步。\",\"举个例子:我们在车站等车,你可能会做一些别的事情打发时间,比如学习现代 C++ 模板教程、观看 mq白 的视频教程、玩手机等。不过,你始终在等待一件事情:车到站。\",\"C++ 标准库将这种事件称为 future。它用于处理线程中需要等待某个事件的情况,线程知道预期结果。等待的同时也可以执行其它的任务。\",\"C++ 标准库有两种 future,都声明在 头文件中:独占的 std::future 、共享的 std::shared_future。它们的区别与 std::unique_ptr 和 std::shared_ptr 类似。std::future 只能与单个指定事件关联,而 std::shared_future 能关联多个事件。它们都是模板,它们的模板类型参数,就是其关联的事件(函数)的返回类型。当多个线程需要访问一个独立 future 对象时, 必须使用互斥量或类似同步机制进行保护。而多个线程访问同一共享状态,若每个线程都是通过其自身的 shared_future 对象副本进行访问,则是安全的。\",\"最简单的作用是,我们先前讲的 std::thread 执行任务是没有返回值的,这个问题就能使用 future 解决。\"]},\"38\":{\"h\":\"创建异步任务获取返回值\",\"t\":[\"假设需要执行一个耗时任务并获取其返回值,但是并不急切的需要它。那么就可以启动新线程计算,然而 std::thread 没提供直接接收返回值的机制。所以我们可以使用 std::async 函数模板。\",\"使用 std::async 启动一个异步任务,它会返回一个 std::future 对象,这个对象和任务关联,将持有最终计算出来的结果。当需要这个值的时候,只需要调用 get() 成员函数,就会阻塞直到 future 为就绪为止(即任务执行完毕),返回执行结果。\",\"#include #include #include int task(int n){ std::cout << \\\"异步任务 ID: \\\" << std::this_thread::get_id() << '\\\\n'; return n * n; } int main(){ std::future future = std::async(task, 10); std::cout << \\\"main\\\\n\\\"; std::cout << future.get() << '\\\\n'; } \",\"运行测试。\",\"与 std::thread 一样,std::async 支持任意可调用(Callable)对象,以及传递调用参数。包括支持使用 std::ref ,以及移动的问题。我们下面详细聊一下 std::async 参数传递的事。\",\"struct X{ int operator()(int n)const{ return n * n; } }; struct Y{ int f(int n)const{ return n * n; } }; void f(int& p) { std::cout << &p << '\\\\n'; } int main(){ Y y; int n = 0; auto t1 = std::async(X{}, 10); auto t2 = std::async(&Y::f,&y,10); auto t3 = std::async([] {}); auto t4 = std::async(f, std::ref(n)); std::cout << &n << '\\\\n'; } \",\"运行测试。\",\"如你所见,它支持所有可调用(Callable)对象,并且也是默认拷贝,必须使用 std::ref 才能传递引用。并且它和 std::thread 一样,内部会将保有的参数副本转换为右值表达式进行传递,这是为了那些只支持移动的类型,左值引用没办法引用右值表达式,所以如果不使用 std::ref,这里 void f(int&) 就会导致编译错误,如果是 void f(const int&) 则可以通过编译,不过引用的不是我们传递的局部对象。\",\"void f(const int& p) {} void f2(int& p ){} int n = 0; std::async(f, n); // OK! 可以通过编译,不过引用的并非是局部的n std::async(f2, n); // Error! 无法通过编译 \",\"我们来展示使用 std::move ,也就移动传递参数:\",\"struct move_only { move_only() { std::puts(\\\"默认构造\\\"); } move_only(const move_only&) = delete; move_only(move_only&&)noexcept { std::puts(\\\"移动构造\\\"); } }; void task(move_only x){ std::cout << \\\"异步任务 ID: \\\" << std::this_thread::get_id() << '\\\\n'; } int main(){ move_only x; std::future future = std::async(task, std::move(x)); std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::cout << \\\"main\\\\n\\\"; future.wait(); // 等待异步任务执行完毕 } \",\"运行测试。\",\"如你所见,它支持只移动类型,我们将参数使用 std::move 传递。\",\"接下来我们聊 std::async 的执行策略,我们前面一直没有使用,其实就是在传递可调用对象与参数之前传递枚举值罢了:\",\"std::launch::async 在不同线程上执行异步任务。\",\"std::launch::deferred 惰性求值,不创建线程,等待 future 对象调用 wait 或 get 成员函数的时候执行任务。\",\"而我们先前没有写明这个参数,实际上是默认:std::launch::async | std::launch::deferred ,也就是说由实现选择到底是否创建线程执行异步任务。我们来展示一下:\",\"void f(){ std::cout << std::this_thread::get_id() << '\\\\n'; } int main(){ std::cout << std::this_thread::get_id() << '\\\\n'; auto f1 = std::async(std::launch::deferred, f); f1.wait(); // 在 wait 或 get() 调用时执行,不创建线程 auto f2 = std::async(std::launch::async,f); // 创建线程执行异步任务 auto f3 = std::async(std::launch::deferred | std::launch::async, f); // 实现选择的执行方式 } \",\"运行测试。\",\"其实到此基本就差不多了,我们再介绍两个常见问题即可:\",\"如果从 std::async 获得的 std::future 没有被移动或绑定到引用,那么在完整表达式结尾, std::future 的析构函数将阻塞到异步计算完成。\",\"std::async(std::launch::async, []{ f(); }); // 临时量的析构函数等待 f() std::async(std::launch::async, []{ g(); }); // f() 完成前不开始 \",\"如你所见,这并不能创建异步任务,会堵塞,然后逐个执行。\",\"被移动的 std::future 没有所有权,失去共享状态,不能调用 get、wait 成员函数。\",\"auto t = std::async([] {}); std::future future{ std::move(t) }; t.wait(); // Error! 抛出异常 \",\"如同没有线程资源所有权的 std::thread 对象调用 join() 一样错误,这是移动语义的基本语义逻辑。\"]},\"39\":{\"h\":\"与\",\"t\":[\"类模板 std::packaged_task 包装任何可调用(Callable)目标(函数、lambda 表达式、bind 表达式或其它函数对象),使得能异步调用它。其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。\",\"通常它会和 std::future 一起使用,不过也可以单独使用,我们一步一步来:\",\"std::packaged_task task([](int a, int b){ return std::pow(a, b); }); task(10, 2); // 执行传递的 lambda,但无法获取返回值 \",\"它有 operator() 的重载,它会执行我们传递的可调用(Callable)对象,不过这个重载的返回类型是 void没办法获取返回值。\",\"如果想要异步的获取返回值,我们需要在调用 operator() 之前,让它和 future 关联,然后使用 future.get(),也就是:\",\"std::packaged_task task([](int a, int b){ return std::pow(a, b); }); std::futurefuture = task.get_future(); task(10, 2); // 此处执行任务 std::cout << future.get() << '\\\\n'; // 不堵塞,此处获取返回值 \",\"运行测试。\",\"先关联任务,再执行任务,当我们想要获取任务的返回值的时候,就 future.get() 即可。值得注意的是,任务并不会在线程中执行,想要在线程中执行异步任务,然后再获取返回值,我们可以这么做:\",\"std::packaged_task task([](int a, int b){ return std::pow(a, b); }); std::futurefuture = task.get_future(); std::thread t{ std::move(task),10,2 }; // 任务在线程中执行 t.join(); std::cout << future.get() << '\\\\n'; // 并不堵塞,获取任务返回值罢了 \",\"运行测试。\",\"因为 task 本身是重载了 operator() 的,是可调用对象,自然可以传递给 std::thread 执行,以及传递调用参数。唯一需要注意的是我们使用了 std::move ,这是因为 std::packaged_task 只能移动,不能复制。\",\"简而言之,其实 std::packaged_task 也就是一个“包装”类而已,它本身并没什么特殊的,老老实实执行我们传递的任务,且方便我们获取返回值罢了,明确这一点,那么一切都不成问题。\",\"std::packaged_task 也可以在线程中传递,在需要的时候获取返回值,而非像上面那样将它自己作为可调用对象:\",\"template requires std::invocable&, Args...> void async_task(std::packaged_task& task, Args&&...args) { // todo.. task(std::forward(args)...); } int main() { std::packaged_task task([](int a,int b){ return a + b; }); int value = 50; std::future future = task.get_future(); // 创建一个线程来执行异步任务 std::thread t{ [&] {async_task(task, value, value); } }; std::cout << future.get() << '\\\\n'; t.join(); } \",\"运行测试。\",\"我们套了一个 lambda,这是因为函数模板不是函数,它并非具体类型,没办法直接被那样传递使用,只能包一层了。这只是一个简单的示例,展示可以使用 std::packaged_task 作函数形参,然后我们来传递任务进行异步调用等操作。\",\"我们再将第二章实现的并行 sum 改成 std::package_task + std::future 的形式:\",\"template auto sum(ForwardIt first, ForwardIt last) { using value_type = std::iter_value_t; std::size_t num_threads = std::thread::hardware_concurrency(); std::ptrdiff_t distance = std::distance(first, last); if (distance > 1024000) { // 计算每个线程处理的元素数量 std::size_t chunk_size = distance / num_threads; std::size_t remainder = distance % num_threads; // 存储每个线程要执行的任务 std::vector>tasks; // 和每一个任务进行关联的 future 用于获取返回值 std::vector>futures(num_threads); // 存储关联线程的线程对象 std::vector threads; // 制作任务、与 future 关联、启动线程执行 auto start = first; for (std::size_t i = 0; i < num_threads; ++i) { auto end = std::next(start, chunk_size + (i < remainder ? 1 : 0)); tasks.emplace_back(std::packaged_task{[start, end, i] { return std::accumulate(start, end, value_type{}); }}); start = end; // 开始迭代器不断向前 futures[i] = tasks[i].get_future(); // 任务与 std::future 关联 threads.emplace_back(std::move(tasks[i])); } // 等待所有线程执行完毕 for (auto& thread : threads) thread.join(); // 汇总线程的计算结果 value_type total_sum {}; for (std::size_t i = 0; i < num_threads; ++i) { total_sum += futures[i].get(); } return total_sum; } value_type total_sum = std::accumulate(first, last, value_type{}); return total_sum; } \",\"运行测试。\",\"相比于之前,其实不同无非是定义了 std::vector> tasks 与 std::vector> futures ,然后在循环中制造任务插入容器,关联 tuple,再放到线程中执行。最后汇总的时候写一个循环,futures[i].get() 获取任务的返回值加起来即可。\",\"到此,也就可以了。\"]},\"40\":{\"h\":\"使用\",\"t\":[\"类模板 std::promise 用于存储一个值或一个异常,之后通过 std::promise 对象所创建的 std::future 对象异步获得。\",\"// 计算函数,接受一个整数并返回它的平方 void calculate_square(std::promise promiseObj, int num) { // 模拟一些计算 std::this_thread::sleep_for(std::chrono::seconds(1)); // 计算平方并设置值到 promise 中 promiseObj.set_value(num * num); } // 创建一个 promise 对象,用于存储计算结果 std::promise promise; // 从 promise 获取 future 对象进行关联 std::future future = promise.get_future(); // 启动一个线程进行计算 int num = 5; std::thread t(calculate_square, std::move(promise), num); // 阻塞,直到结果可用 int result = future.get(); std::cout << num << \\\" 的平方是:\\\" << result << std::endl; t.join(); \",\"运行测试。\",\"我们在新线程中通过调用 set_value() 函数设置 promise 的值,并在主线程中通过与其关联的 future 对象的 get() 成员函数获取这个值,如果promise的值还没有被设置,那么将阻塞当前线程,直到被设置为止。同样的 std::promise只能移动,不可复制,所以我们使用了 std::move 进行传递。\",\"除了 set_value() 函数外,std::promise 还有一个 set_exception() 成员函数,它接受一个 std::exception_ptr 类型的参数,这个参数通常通过 std::current_exception() 获取,用于指示当前线程中抛出的异常。然后,std::future 对象通过 get() 函数获取这个异常,如果 promise 所在的函数有异常被抛出,则 std::future 对象会重新抛出这个异常,从而允许主线程捕获并处理它。\",\"void throw_function(std::promise prom) { try { throw std::runtime_error(\\\"一个异常\\\"); } catch (...) { prom.set_exception(std::current_exception()); } } int main() { std::promise prom; std::future fut = prom.get_future(); std::thread t(throw_function, std::move(prom)); try { std::cout << \\\"等待线程执行,抛出异常并设置\\\\n\\\"; fut.get(); } catch (std::exception& e) { std::cerr << \\\"来自线程的异常: \\\" << e.what() << '\\\\n'; } t.join(); } \",\"运行结果:\",\"等待线程执行,抛出异常并设置 来自线程的异常: 一个异常 \",\"你可能对这段代码还有一些疑问:我们写的是 promised ,但是却没有使用 set_value 设置值,你可能会想着再写一行 prom.set_value(0)?\",\"共享状态的 promise 已经存储值或者异常,再次调用 set_value(set_exception) 会抛出 std::future_error 异常,将错误码设置为 promise_already_satisfied。这是因为 std::promise 对象只能是存储值或者异常其中一种,而无法共存。\",\"简而言之,set_value 与 set_exception 二选一,如果先前调用了 set_value ,就不可再次调用 set_exception,反之亦然(不然就会抛出异常),示例如下:\",\"void throw_function(std::promise prom) { prom.set_value(100); try { throw std::runtime_error(\\\"一个异常\\\"); } catch (...) { try{ // 共享状态的 promise 已存储值,调用 set_exception 产生异常 prom.set_exception(std::current_exception()); }catch (std::exception& e){ std::cerr << \\\"来自 set_exception 的异常: \\\" << e.what() << '\\\\n'; } } } int main() { std::promise prom; std::future fut = prom.get_future(); std::thread t(throw_function, std::move(prom)); std::cout << \\\"等待线程执行,抛出异常并设置\\\\n\\\"; std::cout << \\\"值:\\\" << fut.get() << '\\\\n'; // 100 t.join(); } \",\"运行结果:\",\"等待线程执行,抛出异常并设置 值:100 来自 set_exception 的异常: promise already satisfied \"]},\"41\":{\"h\":\"多个线程的等待\",\"t\":[\"之前的例子中都在用 std::future ,不过 std::future 也有局限性。很多线程在等待的时候,只有一个线程能获取结果。当多个线程等待相同事件的结果时,就需要使用 std::shared_future 来替代 std::future 了。\"]},\"42\":{\"h\":\"\",\"t\":[\"  我们会收集捐赠者进行感谢,所以请您捐赠了可以选择备注,或者联系我,或者直接在捐赠初始记录名单中进行评论。\"]},\"43\":{\"h\":\"std::thread 的构造-源码解析\",\"t\":[\"我们这单章是为了专门解释一下 C++11 引入的 std::thread 是如何构造的,是如何创建线程传递参数的,让你彻底了解这个类。\",\"我们以 MSVC 实现的 std::thread 代码进行讲解,MSVC STL 很早之前就不支持 C++11 了,它的实现完全基于 C++14,出于某些原因 C++17 的一些库(如 invoke, _v 变量模板)被向后移植到了 C++14 模式,所以即使是 C++11 标准库设施,实现中可能也是使用到了 C++14、17 的东西。\"]},\"44\":{\"h\":\"的数据成员\",\"t\":[\"了解一个庞大的类,最简单的方式就是先看它的数据成员有什么。\",\"std::thread 只保有一个私有数据成员 _Thr:\",\"private: _Thrd_t _Thr; \",\"_Thrd_t 是一个结构体,它保有两个数据成员:\",\"using _Thrd_id_t = unsigned int; struct _Thrd_t { // thread identifier for Win32 void* _Hnd; // Win32 HANDLE _Thrd_id_t _Id; }; \",\"结构很明确,这个结构体的 _Hnd 成员是指向线程的句柄,_Id 成员就是保有线程的 ID。\",\"在64 位操作系统,因为内存对齐,指针 8 ,无符号 int 4,这个结构体 _Thrd_t 就是占据 16 个字节。也就是说 sizeof(std::thread) 的结果应该为 16。\"]},\"45\":{\"h\":\"的构造函数\",\"t\":[\"std::thread 有四个构造函数,分别是:\",\"默认构造函数,构造不关联线程的新 std::thread 对象。\",\"thread() noexcept : _Thr{} {} \",\"值初始化了数据成员 _Thr ,这里的效果相当于给其成员 _Hnd 和 _Id 都进行零初始化。\",\"移动构造函数,转移线程的所有权,构造 other 关联的执行线程的 std::thread 对象。此调用后 other 不再表示执行线程失去了线程的所有权。\",\"thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {} \",\"_STD 是一个宏,展开就是 ::std::,也就是 ::std::exchange,将 _Other._Thr 赋为 {} (也就是置空),返回 _Other._Thr 的旧值用以初始化当前对象的数据成员 _Thr。\",\"复制构造函数被定义为弃置的,std::thread 不可复制。两个 std::thread 不可表示一个线程,std::thread 对线程资源是独占所有权。\",\"thread(const thread&) = delete; \",\"构造新的 std::thread 对象并将它与执行线程关联。表示新的执行线程开始执行。\",\"template , thread>, int> = 0> _NODISCARD_CTOR_THREAD explicit thread(_Fn&& _Fx, _Args&&... _Ax) { _Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); } \",\"前三个构造函数都没啥要特别聊的,非常简单,只有第四个构造函数较为复杂,且是我们本章重点,需要详细讲解。(注意 MSVC 使用标准库的内容很多时候不加 std::,脑补一下就行)\",\"如你所见,这个构造函数本身并没有做什么,它只是一个可变参数成员函数模板,增加了一些 SFINAE 进行约束我们传入的可调用对象的类型不能是 std::thread。函数体中调用了一个函数 _Start,将我们构造函数的参数全部完美转发,去调用它,这个函数才是我们的重点,如下:\",\"template void _Start(_Fn&& _Fx, _Args&&... _Ax) { using _Tuple = tuple, decay_t<_Args>...>; auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{}); _Thr._Hnd = reinterpret_cast(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id)); if (_Thr._Hnd) { // ownership transferred to the thread (void) _Decay_copied.release(); } else { // failed to start thread _Thr._Id = 0; _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN); } } \",\"它也是一个可变参数成员函数模板,接受一个可调用对象 _Fn 和一系列参数 _Args... ,这些东西用来创建一个线程。\",\"using _Tuple = tuple, decay_t<_Args>...>\",\"定义了一个元组类型 _Tuple ,它包含了可调用对象和参数的类型,这里使用了 decay_t 来去除了类型的引用和 cv 限定。\",\"auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...)\",\"使用 make_unique 创建了一个独占指针,指向的是 _Tuple 类型的对象,存储了传入的函数对象和参数的副本。\",\"constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{})\",\"调用 _Get_invoke 函数,传入 _Tuple 类型和一个参数序列的索引序列(为了遍历形参包)。这个函数用于获取一个函数指针,指向了一个静态成员函数 _Invoke,用来实际执行线程。这两个函数都非常的简单,我们来看看:\",\" template _NODISCARD static constexpr auto _Get_invoke(index_sequence<_Indices...>) noexcept { return &_Invoke<_Tuple, _Indices...>; } template static unsigned int __stdcall _Invoke(void* _RawVals) noexcept /* terminates */ { // adapt invoke of user's callable object to _beginthreadex's thread procedure const unique_ptr<_Tuple> _FnVals(static_cast<_Tuple*>(_RawVals)); _Tuple& _Tup = *_FnVals.get(); // avoid ADL, handle incomplete types _STD invoke(_STD move(_STD get<_Indices>(_Tup))...); _Cnd_do_broadcast_at_thread_exit(); // TRANSITION, ABI return 0; } \",\"_Get_invoke 函数很简单,就是接受一个元组类型,和形参包的索引,传递给 _Invoke 静态成员函数模板,实例化,获取它的函数指针。\",\"它的形参类型我们不再过多介绍,你只需要知道 index_sequence 这个东西可以用来接收一个由 make_index_sequence 创建的索引形参包,帮助我们进行遍历元组即可。示例代码。\",\"_Invoke 是重中之重,它是线程实际执行的函数,如你所见它的形参类型是 void* ,这是必须的,要符合 _beginthreadex 执行函数的类型要求。虽然是 void*,但是我可以将它转换为 _Tuple* 类型,构造一个独占智能指针,然后调用 get() 成员函数获取底层指针,解引用指针,得到元组的引用初始化_Tup 。\",\"此时,我们就可以进行调用了,使用 std::invoke + std::move(默认移动) ,这里有一个形参包展开,_STD get<_Indices>(_Tup))...,_Tup 就是 std::tuple 的引用,我们使用 std::get<> 获取元组存储的数据,需要传入一个索引,这里就用到了 _Indices。展开之后,就等于 invoke 就接受了我们构造 std::thread 传入的可调用对象,调用可调用对象的参数,invoke 就可以执行了。\",\"_Thr._Hnd = reinterpret_cast(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id))\",\"调用 _beginthreadex 函数来启动一个线程,并将线程句柄存储到 _Thr._Hnd 中。传递给线程的参数为 _Invoker_proc(一个静态函数指针,就是我们前面讲的 _Invoke)和 _Decay_copied.get()(存储了函数对象和参数的副本的指针)。\",\"if (_Thr._Hnd) {\",\"如果线程句柄 _Thr._Hnd 不为空,则表示线程已成功启动,将独占指针的所有权转移给线程。\",\"(void) _Decay_copied.release()\",\"释放独占指针的所有权,因为已经将参数传递给了线程。\",\"} else { // failed to start thread\",\"如果线程启动失败,则进入这个分支\",\"_Thr._Id = 0;\",\"将线程ID设置为0。\",\"_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);\",\"抛出一个 C++ 错误,表示资源不可用,请再次尝试。\"]},\"46\":{\"h\":\"总结\",\"t\":[\"需要注意,libstdc++ 和 libc++ 可能不同,就比如它们 64 位环境下 sizeof(std::thread) 的结果就可能是 8。libstdc++ 的实现只保有一个 std::thread::id。参见。不过实测 gcc 不管是 win32 还是 POSIX 线程模型,线程对象的大小都是 8,宏 _GLIBCXX_HAS_GTHREADS 的值都为 1(GThread)。\",\" class thread { public: #ifdef _GLIBCXX_HAS_GTHREADS using native_handle_type = __gthread_t; #else using native_handle_type = int; #endif \",\"__gthread_t 即 void*。\",\"我们这里的源码解析涉及到的 C++ 技术很多,我们也没办法每一个都单独讲,那会显得文章很冗长,而且也不是重点。\",\"相信你也感受到了,不会模板,你阅读标准库源码,是无稽之谈,市面上很多教程教学,教导一些实现容器,过度简化了,真要去出错了去看标准库的代码,那是不现实的。不需要模板的水平有多高,也不需要会什么元编程,但是基本的需求得能做到,得会,这里推荐一下:现代C++模板教程。\"]},\"47\":{\"h\":\"std::scoped_lock 的源码实现与解析\",\"t\":[\"本单章专门介绍标准库在 C++17 引入的类模板 std::scoped_lock 的实现,让你对它再无疑问。\",\"这会涉及到不少的模板技术,这没办法,就如同我们先前聊 std::thread 的构造与源码分析最后说的:“不会模板,你阅读标准库源码,是无稽之谈”。建议学习现代C++模板教程。\",\"我们还是一样的,以 MSVC STL 实现的 std::scoped_lock 代码进行讲解,不用担心,我们也查看了 libstdc++ 、libc++的实现,并没有太多区别,更多的是一些风格上的。而且个人觉得 MSVC 的实现是最简单直观的。\"]},\"48\":{\"h\":\"的数据成员\",\"t\":[\"std::scoped_lock 是一个类模板,它有两个特化,也就是有三个版本,其中的数据成员也是不同的。并且它们都不可移动不可拷贝,“管理类”应该如此。\",\"主模板,是一个可变参数类模板,声明了一个类型形参包 _Mutexes,存储了一个 std::tuple,具体类型根据类型形参包决定。\",\"_EXPORT_STD template class _NODISCARD_LOCK scoped_lock { // class with destructor that unlocks mutexes public: explicit scoped_lock(_Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) { // construct and lock _STD lock(_Mtxes...); } explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened : _MyMutexes(_Mtxes...) {} // construct but don't lock ~scoped_lock() noexcept { _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes); } scoped_lock(const scoped_lock&) = delete; scoped_lock& operator=(const scoped_lock&) = delete; private: tuple<_Mutexes&...> _MyMutexes; }; \",\"对模板类型形参包只有一个类型情况的偏特化,是不是很熟悉,和 lock_guard 几乎没有任何区别,保有一个互斥量的引用,构造上锁,析构解锁,提供一个额外的构造函数让构造的时候不上锁。所以用 scoped_lock 替代 lock_guard 不会造成任何额外开销。\",\"template class _NODISCARD_LOCK scoped_lock<_Mutex> { public: using mutex_type = _Mutex; explicit scoped_lock(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock _MyMutex.lock(); } explicit scoped_lock(adopt_lock_t, _Mutex& _Mtx) noexcept // strengthened : _MyMutex(_Mtx) {} // construct but don't lock ~scoped_lock() noexcept { _MyMutex.unlock(); } scoped_lock(const scoped_lock&) = delete; scoped_lock& operator=(const scoped_lock&) = delete; private: _Mutex& _MyMutex; }; \",\"对类型形参包为空的情况的全特化,没有数据成员。\",\"template <> class scoped_lock<> { public: explicit scoped_lock() = default; explicit scoped_lock(adopt_lock_t) noexcept /* strengthened */ {} scoped_lock(const scoped_lock&) = delete; scoped_lock& operator=(const scoped_lock&) = delete; }; \",\"std::mutex m1,m2; std::scoped_locklc{ m1 }; // 匹配到偏特化版本 保有一个 std::mutex& std::scoped_locklc2{ m1,m2 }; // 匹配到主模板 保有一个 std::tuple std::scoped_lock<> lc3; // 匹配到全特化版本 空 \"]},\"49\":{\"h\":\"的构造与析构\",\"t\":[\"在上一节讲 scoped_lock 的数据成员的时候已经把这个模板类的全部源码,三个版本的代码都展示了,就不再重复。\",\"这三个版本中,只有两个版本需要介绍,也就是\",\"形参包元素数量为一的偏特化,只管理一个互斥量的。\",\"主模板,可以管理任意个数的互斥量。\",\"那这两个的共同点是什么呢?构造上锁,析构解锁。这很明显,明确这一点我们就开始讲吧。\",\"std::mutex m; void f(){ m.lock(); std::lock_guard lc{ m, std::adopt_lock }; } void f2(){ m.lock(); std::scoped_locksp{ std::adopt_lock,m }; } \",\"这段代码为你展示了 std::lock_guard 和 std::scoped_lock 形参包元素数量为一的偏特化的唯一区别:调用不会上锁的构造函数的参数顺序不同。那么到此也就够了。\",\"接下来我们进入 std::scoped_lock 主模板的讲解:\",\"explicit scoped_lock(_Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) { // construct and lock _STD lock(_Mtxes...); } \",\"这个构造函数做了两件事情,初始化数据成员 _MyMutexes让它保有这些互斥量的引用,以及给所有互斥量上锁,使用了 std::lock 帮助我们完成这件事情。\",\"explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened : _MyMutexes(_Mtxes...) {} // construct but don't lock \",\"这个构造函数不上锁,只是初始化数据成员 _MyMutexes让它保有这些互斥量的引用。\",\"~scoped_lock() noexcept { _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes); } \",\"析构函数就要稍微聊一下了,主要是用 std::apply 去遍历 std::tuple ,让元组保有的互斥量引用都进行解锁。简单来说是 std::apply 可以将元组存储的参数全部拿出,用于调用这个可变参数的可调用对象,我们就能利用折叠表达式展开形参包并对其调用 unlock()。\",\"不在乎其返回类型只用来实施它的副作用,显式转换为 (void) 也就是弃值表达式。在我们之前讲的 std::thread 源码中也有这种用法。\",\"不过你可能有疑问:“我们的标准库的那些互斥量unlock() 返回类型都是 void 呀,为什么要这样?”\",\"的确,这是个好问题,libstdc++ 和 libc++ 都没这样做,或许 MSVC STL 想着会有人设计的互斥量让它的 unlock() 返回类型不为 void,毕竟 互斥体(Mutex) 没有要求 unlock() 的返回类型。\",\"template< class F, class Tuple > constexpr decltype(auto) apply( F&& f, Tuple&& t ); \",\"这个函数模板接受两个参数,一个可调用(Callable)对象 f,以及一个元组 t,用做调用 f 。我们可以自己简单实现一下它,其实不算难,这种遍历元组的方式在之前讲 std::thread 的源码的时候也提到过。\",\"template constexpr decltype(auto) Apply_impl(Callable&& obj,Tuple&& tuple,std::index_sequence){ return std::invoke(std::forward(obj), std::get(std::forward(tuple))...); } template constexpr decltype(auto) apply(Callable&& obj, Tuple&& tuple){ return Apply_impl(std::forward(obj), std::forward(tuple), std::make_index_sequence>>{}); } \",\"其实就是把元组给解包了,利用了 std::index_sequence + std::make_index_sequence 然后就用 std::get 形参包展开用 std::invoke 调用可调用对象即可,非常经典的处理可变参数做法,这个非常重要,一定要会使用。\",\"举一个简单的调用例子:\",\"std::tupletuple{66,\\\"😅\\\",'c'}; ::apply([](const auto&... t) { ((std::cout << t << ' '), ...); }, tuple); \",\"运行测试。\",\"使用了折叠表达式展开形参包,打印了元组所有的元素。\"]},\"50\":{\"h\":\"总结\",\"t\":[\"如你所见,其实这很简单。至少使用与了解其设计原理是很简单的。唯一的难度或许只有那点源码,处理可变参数,这会涉及不少模板技术,既常见也通用。还是那句话:“不会模板,你阅读标准库源码,是无稽之谈”。\",\"相对于 std::thread 的源码解析,std::scoped_lock 还是简单的多。\"]},\"51\":{\"h\":\"st::async 与 std::future 源码解析\"},\"52\":{\"h\":\"详细分析\",\"t\":[\"放一些详细分析源码实现之类的内容。\"]},\"53\":{\"h\":\"\",\"t\":[\"404 Not Found\"]},\"54\":{\"h\":\"Md\"},\"55\":{\"h\":\"Image\"}},\"dirtCount\":0,\"index\":[[\"放一些详细分析源码实现之类的内容\",{\"1\":{\"52\":1}}],[\"放弃线程的所有权不是一种选择\",{\"1\":{\"12\":1}}],[\"放弃了对线程资源的所有权\",{\"1\":{\"12\":1}}],[\"既常见也通用\",{\"1\":{\"50\":1}}],[\"处理可变参数\",{\"1\":{\"50\":1}}],[\"处理步骤\",{\"1\":{\"24\":1}}],[\"唯一的难度或许只有那点源码\",{\"1\":{\"50\":1}}],[\"唯一需要注意的是我们使用了\",{\"1\":{\"39\":1}}],[\"利用了\",{\"1\":{\"49\":1}}],[\"互斥体\",{\"1\":{\"49\":1}}],[\"互斥量才会真正释放\",{\"1\":{\"31\":1}}],[\"互斥量只是其中一种常见的方式而已\",{\"1\":{\"29\":1}}],[\"互斥量满足互斥体\",{\"1\":{\"28\":1}}],[\"互斥量主要也就是为了保护共享数据\",{\"1\":{\"25\":1}}],[\"互斥量\",{\"1\":{\"22\":1,\"36\":1}}],[\"毕竟\",{\"1\":{\"49\":1}}],[\"毕竟是初学\",{\"1\":{\"10\":1}}],[\"想着会有人设计的互斥量让它的\",{\"1\":{\"49\":1}}],[\"想要在线程中执行异步任务\",{\"1\":{\"39\":1}}],[\"想要解决这个问题很简单\",{\"1\":{\"14\":1}}],[\"呀\",{\"1\":{\"49\":1}}],[\"去遍历\",{\"1\":{\"49\":1}}],[\"去调用它\",{\"1\":{\"45\":1}}],[\"三个版本的代码都展示了\",{\"1\":{\"49\":1}}],[\"空\",{\"1\":{\"48\":1}}],[\"空才需要初始化\",{\"1\":{\"29\":1}}],[\"匹配到全特化版本\",{\"1\":{\"48\":1}}],[\"匹配到主模板\",{\"1\":{\"48\":1}}],[\"匹配到偏特化版本\",{\"1\":{\"48\":1}}],[\"匹配次数后\",{\"1\":{\"31\":1}}],[\"替代\",{\"1\":{\"48\":1}}],[\"替换了库的版本\",{\"1\":{\"32\":1}}],[\"替换了库的全局版本\",{\"1\":{\"32\":1}}],[\"替换掉\",{\"1\":{\"26\":1}}],[\"替换为\",{\"1\":{\"12\":1,\"14\":1}}],[\"提供一个额外的构造函数让构造的时候不上锁\",{\"1\":{\"48\":1}}],[\"提供此函数的\",{\"1\":{\"26\":1}}],[\"几乎没有任何区别\",{\"1\":{\"48\":1}}],[\"几乎完全相同\",{\"1\":{\"23\":1}}],[\"真要去出错了去看标准库的代码\",{\"1\":{\"46\":1}}],[\"过度简化了\",{\"1\":{\"46\":1}}],[\"过一会儿再来判断是否完成\",{\"1\":{\"15\":1}}],[\"教导一些实现容器\",{\"1\":{\"46\":1}}],[\"教程\",{\"1\":{\"29\":1}}],[\"市面上很多教程教学\",{\"1\":{\"46\":1}}],[\"技术很多\",{\"1\":{\"46\":1}}],[\"宏\",{\"1\":{\"46\":1}}],[\"参见\",{\"1\":{\"46\":1}}],[\"参数传递的事\",{\"1\":{\"38\":1}}],[\"参数\",{\"1\":{\"26\":1}}],[\"位环境下\",{\"1\":{\"46\":1}}],[\"位操作系统\",{\"1\":{\"44\":1}}],[\"默认移动\",{\"1\":{\"45\":1}}],[\"默认构造函数\",{\"1\":{\"45\":1}}],[\"默认构造会上锁\",{\"1\":{\"27\":1}}],[\"默认构造\",{\"1\":{\"14\":1,\"16\":1,\"38\":1}}],[\"得会\",{\"1\":{\"46\":1}}],[\"得到元组的引用初始化\",{\"1\":{\"45\":1}}],[\"得不到正确的结果\",{\"1\":{\"29\":1}}],[\"虽然是\",{\"1\":{\"45\":1}}],[\"虽强调现代\",{\"1\":{\"0\":1}}],[\"示例代码\",{\"1\":{\"45\":1}}],[\"示例如下\",{\"1\":{\"40\":1}}],[\"静态成员函数模板\",{\"1\":{\"45\":1}}],[\"静态局部变量初始化在\",{\"1\":{\"29\":2}}],[\"限定\",{\"1\":{\"45\":1}}],[\"限制了对象\",{\"1\":{\"23\":1}}],[\"增加了一些\",{\"1\":{\"45\":1}}],[\"脑补一下就行\",{\"1\":{\"45\":1}}],[\"非常经典的处理可变参数做法\",{\"1\":{\"49\":1}}],[\"非常简单\",{\"1\":{\"45\":1}}],[\"非静态成员\",{\"1\":{\"14\":1}}],[\"赋为\",{\"1\":{\"45\":1}}],[\"赋值\",{\"1\":{\"36\":1}}],[\"分别是\",{\"1\":{\"45\":1}}],[\"分离的线程可能还在运行\",{\"1\":{\"12\":1}}],[\"分离\",{\"1\":{\"12\":2}}],[\"结构很明确\",{\"1\":{\"44\":1}}],[\"变量模板\",{\"1\":{\"43\":1}}],[\"变为\",{\"1\":{\"35\":2}}],[\"出于某些原因\",{\"1\":{\"43\":1}}],[\"值初始化了数据成员\",{\"1\":{\"45\":1}}],[\"值\",{\"1\":{\"40\":2}}],[\"值得注意的是\",{\"1\":{\"32\":1,\"39\":1}}],[\"产生异常\",{\"1\":{\"40\":1}}],[\"产生线程\",{\"1\":{\"18\":1}}],[\"已存储值\",{\"1\":{\"40\":1}}],[\"已经存储值或者异常\",{\"1\":{\"40\":1}}],[\"已经没有关联线程资源\",{\"1\":{\"12\":1}}],[\"已经结束\",{\"1\":{\"12\":1}}],[\"反之亦然\",{\"1\":{\"40\":1}}],[\"反面示例\",{\"1\":{\"26\":1}}],[\"二选一\",{\"1\":{\"40\":1}}],[\"来去除了类型的引用和\",{\"1\":{\"45\":1}}],[\"来替代\",{\"1\":{\"41\":1}}],[\"来自\",{\"1\":{\"40\":2}}],[\"来自线程的异常\",{\"1\":{\"40\":2}}],[\"来处理这种情况\",{\"1\":{\"29\":1}}],[\"制作任务\",{\"1\":{\"39\":1}}],[\"改成\",{\"1\":{\"39\":1}}],[\"改写一下\",{\"1\":{\"27\":1}}],[\"作函数形参\",{\"1\":{\"39\":1}}],[\"作为底层数据成员\",{\"1\":{\"18\":1}}],[\"作为\",{\"1\":{\"14\":1}}],[\"作为构造参数\",{\"1\":{\"14\":1}}],[\"作为它的可调用\",{\"1\":{\"9\":1}}],[\"展示可以使用\",{\"1\":{\"39\":1}}],[\"展开之后\",{\"1\":{\"45\":1}}],[\"展开就是\",{\"1\":{\"45\":1}}],[\"展开\",{\"1\":{\"19\":1}}],[\"明确这一点我们就开始讲吧\",{\"1\":{\"49\":1}}],[\"明确这一点\",{\"1\":{\"39\":1}}],[\"老老实实执行我们传递的任务\",{\"1\":{\"39\":1}}],[\"关联的执行线程的\",{\"1\":{\"45\":1}}],[\"关联的线程\",{\"1\":{\"11\":1}}],[\"关联的线程执行完毕\",{\"1\":{\"9\":1}}],[\"关联\",{\"1\":{\"39\":4}}],[\"目标\",{\"1\":{\"39\":1}}],[\"失去共享状态\",{\"1\":{\"38\":1}}],[\"失败就返回\",{\"1\":{\"24\":1}}],[\"惰性求值\",{\"1\":{\"38\":1}}],[\"y\",{\"1\":{\"38\":5}}],[\"yield\",{\"1\":{\"15\":5}}],[\"异步任务\",{\"1\":{\"38\":2}}],[\"异常之类的是例外\",{\"1\":{\"29\":1}}],[\"异常\",{\"1\":{\"29\":1,\"40\":1}}],[\"若每个线程都是通过其自身的\",{\"1\":{\"37\":1}}],[\"若所有权层数为\",{\"1\":{\"31\":1}}],[\"能关联多个事件\",{\"1\":{\"37\":1}}],[\"能在任何满足可基本锁定\",{\"1\":{\"35\":1}}],[\"共享状态的\",{\"1\":{\"40\":2}}],[\"共享的\",{\"1\":{\"37\":1}}],[\"共享数据\",{\"0\":{\"20\":1},\"1\":{\"1\":1}}],[\"独占的\",{\"1\":{\"37\":1}}],[\"车到站\",{\"1\":{\"37\":1}}],[\"玩手机等\",{\"1\":{\"37\":1}}],[\"观看\",{\"1\":{\"37\":1}}],[\"|\",{\"1\":{\"36\":60,\"38\":2}}],[\"队列为空\",{\"1\":{\"36\":2}}],[\"初始状态\",{\"1\":{\"36\":1}}],[\"初始化数据成员\",{\"1\":{\"49\":1}}],[\"初始化严格发生一次\",{\"1\":{\"29\":1}}],[\"初始化\",{\"1\":{\"29\":2}}],[\"初始化函数f\",{\"1\":{\"16\":2}}],[\"q\",{\"1\":{\"36\":4}}],[\"queue\",{\"1\":{\"36\":2}}],[\"queue\",{\"1\":{\"36\":1}}],[\"queue\",{\"1\":{\"36\":12}}],[\"消费\",{\"1\":{\"36\":1}}],[\"生产\",{\"1\":{\"36\":1}}],[\"生产者消费者模型\",{\"1\":{\"36\":1}}],[\"生存期结束\",{\"1\":{\"12\":1}}],[\"光写好了肯定不够\",{\"1\":{\"36\":1}}],[\"交换\",{\"1\":{\"36\":1}}],[\"交出\",{\"1\":{\"15\":1}}],[\"请再次尝试\",{\"1\":{\"45\":1}}],[\"请无视我们省略的构造\",{\"1\":{\"36\":1}}],[\"请勿对移动语义和转移所有权抱有错误的幻想\",{\"1\":{\"28\":1}}],[\"阻塞\",{\"1\":{\"40\":1}}],[\"阻塞直到队列不为空\",{\"1\":{\"36\":2}}],[\"阻塞当前线程\",{\"1\":{\"35\":1}}],[\"阻塞当前线程直到线程对象关联的线程执行完毕\",{\"1\":{\"13\":1}}],[\"阻塞当前线程直至\",{\"1\":{\"12\":1}}],[\"基于以上思考\",{\"1\":{\"36\":1}}],[\"基本都是多核\",{\"1\":{\"5\":1}}],[\"基本上是二者都有\",{\"1\":{\"5\":1}}],[\"基本概念\",{\"0\":{\"2\":1},\"1\":{\"1\":1}}],[\"操作\",{\"1\":{\"36\":4}}],[\"操作时队列不为空\",{\"1\":{\"36\":1}}],[\"操作时\",{\"1\":{\"36\":2}}],[\"操作受保护的数据\",{\"1\":{\"25\":1}}],[\"虚假唤醒\",{\"1\":{\"35\":1}}],[\"②等价于\",{\"1\":{\"35\":1}}],[\"唤醒了等待在条件变量上的线程\",{\"1\":{\"35\":1}}],[\"唤醒一个等待条件变量的线程\",{\"1\":{\"35\":1}}],[\"暂停当前线程\",{\"1\":{\"35\":1}}],[\"锁\",{\"1\":{\"35\":1}}],[\"锁定了\",{\"1\":{\"26\":2}}],[\"效果相同\",{\"1\":{\"35\":1}}],[\"版更加通用但是却又更多的性能开销\",{\"1\":{\"35\":1}}],[\"相对于\",{\"1\":{\"50\":1}}],[\"相对于只在\",{\"1\":{\"35\":1}}],[\"相比于之前\",{\"1\":{\"39\":1}}],[\"相信你也感受到了\",{\"1\":{\"46\":1}}],[\"相信你注意到了\",{\"1\":{\"18\":1}}],[\"相信这个时候就已经想到了\",{\"1\":{\"30\":1}}],[\"头文件中\",{\"1\":{\"35\":1,\"37\":1}}],[\"通知等待的线程\",{\"1\":{\"35\":1}}],[\"通过另一线程触发等待事件的机制是最基本的唤醒方式\",{\"1\":{\"35\":1}}],[\"通常它会和\",{\"1\":{\"39\":1}}],[\"通常它比裸调用\",{\"1\":{\"26\":1}}],[\"通常会使用同步机制\",{\"1\":{\"33\":1}}],[\"通常建议优先\",{\"1\":{\"27\":1}}],[\"通常用于描述锁定的范围大小\",{\"1\":{\"23\":1}}],[\"通常说某个特定的线程正持有这个互斥锁\",{\"1\":{\"22\":1}}],[\"通常非常不推荐使用\",{\"1\":{\"12\":1}}],[\"休眠\",{\"1\":{\"35\":1}}],[\"休眠②前函数对互斥量解锁①\",{\"1\":{\"35\":1}}],[\"循环中\",{\"1\":{\"35\":1}}],[\"忙等待\",{\"1\":{\"35\":1}}],[\"估算一下地铁到达目的地的时间\",{\"1\":{\"35\":1}}],[\"各个任务之间通常需要相互协调和等待\",{\"1\":{\"34\":1}}],[\"条件变量虚假唤醒是指在使用条件变量进行线程同步时\",{\"1\":{\"35\":1}}],[\"条件变量的\",{\"1\":{\"35\":1}}],[\"条件变量\",{\"1\":{\"34\":1,\"35\":1,\"36\":1}}],[\"条件变量等设施\",{\"1\":{\"33\":1}}],[\"条件竞争\",{\"0\":{\"21\":1}}],[\"例子\",{\"1\":{\"32\":1}}],[\"存在数据竞争\",{\"1\":{\"32\":2}}],[\"存储了一个\",{\"1\":{\"48\":1}}],[\"存储了函数对象和参数的副本的指针\",{\"1\":{\"45\":1}}],[\"存储了传入的函数对象和参数的副本\",{\"1\":{\"45\":1}}],[\"存储每个线程要执行的任务\",{\"1\":{\"39\":1}}],[\"存储每个线程的结果\",{\"1\":{\"10\":1}}],[\"存储关联线程的线程对象\",{\"1\":{\"10\":1,\"39\":1}}],[\"换句话说\",{\"1\":{\"32\":1}}],[\"换行并刷新\",{\"1\":{\"9\":1}}],[\"起\",{\"1\":{\"32\":1}}],[\"全局的\",{\"1\":{\"32\":1}}],[\"全局\",{\"1\":{\"32\":1}}],[\"全是\",{\"1\":{\"21\":1}}],[\"运算符\",{\"1\":{\"32\":1}}],[\"运算符的库函数实际是线程安全的\",{\"1\":{\"33\":1}}],[\"运算符的库版本\",{\"1\":{\"32\":2}}],[\"运算符的用户替换版本\",{\"1\":{\"32\":1}}],[\"运算符和\",{\"1\":{\"32\":2}}],[\"运行结果\",{\"1\":{\"15\":1,\"40\":2}}],[\"运行代码\",{\"1\":{\"14\":2}}],[\"运行测试\",{\"1\":{\"10\":2,\"14\":1,\"18\":2,\"24\":1,\"31\":2,\"35\":1,\"38\":4,\"39\":4,\"40\":1,\"49\":1}}],[\"帮助我们完成这件事情\",{\"1\":{\"49\":1}}],[\"帮助我们进行遍历元组即可\",{\"1\":{\"45\":1}}],[\"帮助写出\",{\"1\":{\"26\":1}}],[\"帮我们管理\",{\"1\":{\"31\":1}}],[\"递归锁\",{\"1\":{\"33\":1}}],[\"递归调用\",{\"1\":{\"31\":1}}],[\"递归函数\",{\"1\":{\"31\":1}}],[\"key\",{\"1\":{\"30\":4}}],[\"规则\",{\"1\":{\"30\":1}}],[\"做的一样\",{\"1\":{\"30\":1}}],[\"后缀\",{\"1\":{\"35\":1}}],[\"后者有更高的性能优势\",{\"1\":{\"30\":1}}],[\"后正常析构\",{\"1\":{\"16\":2}}],[\"读写锁\",{\"1\":{\"30\":1}}],[\"每一站都能知道\",{\"1\":{\"35\":1}}],[\"每次递归都会锁定互斥量\",{\"1\":{\"31\":1}}],[\"每次用户打开程序的时候\",{\"1\":{\"30\":1}}],[\"每个线程都有其自己的\",{\"1\":{\"32\":1}}],[\"每个线程都抢着完成自己的任务\",{\"1\":{\"21\":1}}],[\"每个线程只需要使用\",{\"1\":{\"29\":1}}],[\"每个线程只持有一个锁\",{\"1\":{\"26\":1}}],[\"每个线程的处理情况如下\",{\"1\":{\"10\":1}}],[\"此调用后\",{\"1\":{\"45\":1}}],[\"此处获取返回值\",{\"1\":{\"39\":1}}],[\"此处执行任务\",{\"1\":{\"39\":1}}],[\"此线程对\",{\"1\":{\"31\":1}}],[\"此方式也在单例中多见\",{\"1\":{\"29\":1}}],[\"此时线程\",{\"1\":{\"23\":1}}],[\"此时线程函数还持有函数局部对象的指针或引用\",{\"1\":{\"12\":1}}],[\"此时可以判断操作是否完成\",{\"1\":{\"15\":1}}],[\"此时我们的\",{\"1\":{\"12\":1}}],[\"此时分离的子线程可能没有执行完毕\",{\"1\":{\"12\":1}}],[\"此时\",{\"1\":{\"12\":1,\"14\":2,\"16\":1,\"21\":1,\"45\":1}}],[\"测试链接\",{\"1\":{\"29\":1,\"30\":1}}],[\"测试代码\",{\"1\":{\"12\":1,\"13\":1,\"26\":2}}],[\"次调用\",{\"1\":{\"29\":1}}],[\"次都是执行函数\",{\"1\":{\"23\":1}}],[\"第三种方式\",{\"1\":{\"35\":1}}],[\"第二种方法就是加个延时\",{\"1\":{\"35\":1}}],[\"第\",{\"1\":{\"29\":1}}],[\"第一个调用\",{\"1\":{\"22\":1}}],[\"特别是当初始化完成之后\",{\"1\":{\"29\":1}}],[\"特别是在需要保护临界区的同时\",{\"1\":{\"24\":1}}],[\"比显式使用互斥量消耗的资源更少\",{\"1\":{\"29\":1}}],[\"比起锁住互斥量并显式检查指针\",{\"1\":{\"29\":1}}],[\"比如学习现代\",{\"1\":{\"37\":1}}],[\"比如手机的地图\",{\"1\":{\"35\":1}}],[\"比如我们要讲的互斥量\",{\"1\":{\"33\":1}}],[\"比如我的\",{\"1\":{\"10\":1}}],[\"比如互斥量或原子操作\",{\"1\":{\"32\":1}}],[\"比如异常\",{\"1\":{\"29\":1}}],[\"比如对于共享数据的初始化过程的保护\",{\"1\":{\"29\":1}}],[\"比如当函数返回std\",{\"1\":{\"28\":1}}],[\"比如前面提到的\",{\"1\":{\"28\":1}}],[\"比如保护共享数据中的\",{\"1\":{\"26\":1}}],[\"比如一个遥控汽车\",{\"1\":{\"26\":1}}],[\"比如这段代码就是典型的恶性条件竞争\",{\"1\":{\"21\":1}}],[\"比如两个线程都要往标准输出输出一段字符\",{\"1\":{\"21\":1}}],[\"比如函数结束\",{\"1\":{\"12\":1}}],[\"比如重载了\",{\"1\":{\"12\":1}}],[\"比如\",{\"1\":{\"10\":2,\"21\":1,\"26\":1}}],[\"④\",{\"1\":{\"29\":1}}],[\"未被锁保护的读取操作①没有与其他线程里被锁保护的写入操作③进行同步\",{\"1\":{\"29\":1}}],[\"未定义行为\",{\"1\":{\"21\":2}}],[\"①\",{\"1\":{\"29\":1}}],[\"错误\",{\"1\":{\"29\":2,\"45\":1}}],[\"双检锁\",{\"1\":{\"29\":2}}],[\"举一个简单的调用例子\",{\"1\":{\"49\":1}}],[\"举一个使用\",{\"1\":{\"28\":1}}],[\"举个例子\",{\"1\":{\"10\":1,\"12\":1,\"37\":1}}],[\"故所有\",{\"1\":{\"28\":1}}],[\"除了\",{\"1\":{\"40\":1}}],[\"除了类成员或函数形参\",{\"1\":{\"28\":1}}],[\"除非我们写成\",{\"1\":{\"27\":1}}],[\"除非其中一个孩子决定让另一个先玩\",{\"1\":{\"26\":1}}],[\"除非\",{\"1\":{\"21\":1}}],[\"除非给参数\",{\"1\":{\"14\":1}}],[\"说明符只能搭配变量声明和函数声明\",{\"1\":{\"28\":1}}],[\"说实话挺麻烦的\",{\"1\":{\"15\":1}}],[\"切勿被词语迷惑\",{\"1\":{\"28\":1}}],[\"切勿将受保护数据的指针或引用传递到互斥量作用域之外\",{\"1\":{\"25\":1}}],[\"原来的置空\",{\"1\":{\"28\":1}}],[\"原因很简单\",{\"1\":{\"27\":1}}],[\"字节\",{\"1\":{\"27\":1}}],[\"字符串字面量具有静态存储期\",{\"1\":{\"14\":1}}],[\"字符串字面量的类型是\",{\"1\":{\"14\":1}}],[\"环境也就是\",{\"1\":{\"27\":1}}],[\"内存分配\",{\"1\":{\"32\":1}}],[\"内存对齐\",{\"1\":{\"27\":1}}],[\"内部会将保有的参数副本转换为右值表达式进行传递\",{\"1\":{\"14\":1,\"38\":1}}],[\"很早之前就不支持\",{\"1\":{\"43\":1}}],[\"很多线程在等待的时候\",{\"1\":{\"41\":1}}],[\"很简单\",{\"1\":{\"27\":1}}],[\"很诡异的设计对吧\",{\"1\":{\"27\":1}}],[\"别的标准库也都有类似设计\",{\"1\":{\"27\":1}}],[\"满足第二个\",{\"1\":{\"27\":1}}],[\"设计挺奇怪的对吧\",{\"1\":{\"27\":1}}],[\"设置值\",{\"1\":{\"40\":1}}],[\"设置\",{\"1\":{\"35\":1}}],[\"设置了一个标志位\",{\"1\":{\"35\":1}}],[\"设置条件变量为\",{\"1\":{\"35\":1}}],[\"设置为\",{\"1\":{\"27\":3}}],[\"设置时区麻烦\",{\"1\":{\"15\":1}}],[\"设置要等待的时间点为当前时间点之后的5秒\",{\"1\":{\"15\":1}}],[\"了解一个庞大的类\",{\"1\":{\"44\":1}}],[\"了解其实现\",{\"1\":{\"17\":1}}],[\"了\",{\"1\":{\"27\":2,\"41\":1,\"43\":1}}],[\"尤其它还可以与我们下一章要讲的条件变量一起使用\",{\"1\":{\"27\":1}}],[\"尤其是某些说法准确性也一般\",{\"1\":{\"7\":1}}],[\"灵活的锁\",{\"0\":{\"27\":1}}],[\"固定的顺序上锁就不存在问题\",{\"1\":{\"26\":1}}],[\"外部程序可能做任何事情\",{\"1\":{\"26\":1}}],[\"约束开发者的行为\",{\"1\":{\"26\":1}}],[\"程序都能正常完成工作\",{\"1\":{\"26\":1}}],[\"程序老老实实执行完毕\",{\"1\":{\"21\":1}}],[\"甚至估算错误导致坐过站\",{\"1\":{\"35\":1}}],[\"甚至难以复现\",{\"1\":{\"26\":1}}],[\"甚至深入阅读了部分的\",{\"1\":{\"20\":1}}],[\"新增了\",{\"1\":{\"26\":1}}],[\"哪个都没办法往下执行\",{\"1\":{\"26\":1}}],[\"往下执行需要上锁\",{\"1\":{\"26\":1}}],[\"完成前不开始\",{\"1\":{\"38\":1}}],[\"完整代码\",{\"1\":{\"30\":1}}],[\"完整代码测试\",{\"1\":{\"23\":1}}],[\"完全可能线程\",{\"1\":{\"26\":1}}],[\"😅\",{\"1\":{\"26\":1,\"49\":1}}],[\"😢\",{\"1\":{\"21\":1}}],[\"🤣🤣\",{\"1\":{\"28\":1}}],[\"🤣\",{\"1\":{\"26\":1}}],[\"考虑用户调用的时候将参数交换\",{\"1\":{\"26\":1}}],[\"依旧会产生问题\",{\"1\":{\"26\":1}}],[\"依然实际是拷贝\",{\"1\":{\"14\":1}}],[\"要符合\",{\"1\":{\"45\":1}}],[\"要求的锁上工作\",{\"1\":{\"35\":1}}],[\"要求下列函数是线程安全的\",{\"1\":{\"32\":1}}],[\"要求构造函数之前和构造函数之后都不能再次上锁\",{\"1\":{\"27\":1}}],[\"要求构造之后上锁\",{\"1\":{\"27\":1}}],[\"要求在构造之前互斥量上锁\",{\"1\":{\"27\":1}}],[\"要想调用\",{\"1\":{\"27\":1}}],[\"要么一个都不锁\",{\"1\":{\"26\":1}}],[\"要么将互斥量都上锁\",{\"1\":{\"26\":1}}],[\"要往下执行\",{\"1\":{\"26\":1}}],[\"要走\",{\"1\":{\"24\":1}}],[\"总而言之\",{\"1\":{\"32\":1}}],[\"总而言之就是\",{\"1\":{\"31\":1}}],[\"总在互斥量\",{\"1\":{\"26\":1}}],[\"总结\",{\"0\":{\"7\":1,\"19\":1,\"33\":1,\"46\":1,\"50\":1}}],[\"问题取决于程序和系统的具体实现\",{\"1\":{\"35\":1}}],[\"问题就出现了\",{\"1\":{\"26\":1}}],[\"问题与解决\",{\"0\":{\"26\":1}}],[\"遥控器和玩具车被放在两个不同的地方\",{\"1\":{\"26\":1}}],[\"他把自己的那个部分给另一个小孩\",{\"1\":{\"26\":1}}],[\"他就得等待另一个小孩玩完才行\",{\"1\":{\"26\":1}}],[\"他们都想玩这个玩具\",{\"1\":{\"26\":1}}],[\"试想一下\",{\"1\":{\"26\":1,\"30\":1}}],[\"死锁是多线程编程中令人相当头疼的问题\",{\"1\":{\"26\":1}}],[\"死锁\",{\"0\":{\"26\":1},\"1\":{\"26\":1,\"33\":1}}],[\"受保护的数据被传递\",{\"1\":{\"25\":1}}],[\"受保护数据传递给函数\",{\"1\":{\"25\":1}}],[\"修改指针\",{\"1\":{\"32\":1}}],[\"修改\",{\"1\":{\"26\":1}}],[\"修改数据成员等\",{\"1\":{\"25\":1}}],[\"修改的\",{\"1\":{\"23\":1}}],[\"上工作的\",{\"1\":{\"35\":1}}],[\"上锁互斥量\",{\"1\":{\"35\":2}}],[\"上锁时若抛出异常\",{\"1\":{\"26\":1}}],[\"上锁了\",{\"1\":{\"26\":1}}],[\"上锁\",{\"1\":{\"26\":2}}],[\"上一节的使用互斥量也已经为各位展示了一些\",{\"1\":{\"25\":1}}],[\"上亿的话差不多\",{\"1\":{\"10\":1}}],[\"释放独占指针的所有权\",{\"1\":{\"45\":1}}],[\"释放\",{\"1\":{\"35\":1}}],[\"释放操作是线程安全\",{\"1\":{\"32\":1}}],[\"释放锁\",{\"1\":{\"24\":1}}],[\"释放所有获取的资源并执行其它必要的清理操作\",{\"1\":{\"12\":1}}],[\"模式\",{\"1\":{\"43\":1}}],[\"模拟一些计算\",{\"1\":{\"40\":1}}],[\"模拟地铁到站\",{\"1\":{\"35\":2}}],[\"模拟临界区操作\",{\"1\":{\"24\":1}}],[\"模板教程\",{\"1\":{\"37\":1}}],[\"模板\",{\"1\":{\"23\":1}}],[\"临时量的析构函数等待\",{\"1\":{\"38\":1}}],[\"临时对象初始化\",{\"1\":{\"16\":1}}],[\"临时对象是右值表达式\",{\"1\":{\"16\":2}}],[\"临界区\",{\"1\":{\"33\":1}}],[\"临界区代码\",{\"1\":{\"24\":1}}],[\"获得的\",{\"1\":{\"38\":1}}],[\"获得锁\",{\"1\":{\"24\":1}}],[\"获取元组存储的数据\",{\"1\":{\"45\":1}}],[\"获取它的函数指针\",{\"1\":{\"45\":1}}],[\"获取\",{\"1\":{\"40\":2}}],[\"获取任务的返回值加起来即可\",{\"1\":{\"39\":1}}],[\"获取任务返回值罢了\",{\"1\":{\"39\":1}}],[\"获取访问权\",{\"1\":{\"30\":1}}],[\"获取锁失败\",{\"1\":{\"24\":1}}],[\"获取当前时间点\",{\"1\":{\"15\":1}}],[\"获取的值自然也会是\",{\"1\":{\"10\":1}}],[\"尝试加锁\",{\"1\":{\"24\":1}}],[\"添加了一个新的特性\",{\"1\":{\"23\":1}}],[\"添加元素\",{\"1\":{\"21\":1}}],[\"给两个互斥量上锁\",{\"1\":{\"26\":1}}],[\"给\",{\"1\":{\"26\":2}}],[\"给整个函数上锁\",{\"1\":{\"23\":1}}],[\"给互斥量上锁\",{\"1\":{\"22\":1,\"23\":1}}],[\"避免另一线程在第一次检查后再做初始化\",{\"1\":{\"29\":1}}],[\"避免在持有锁时调用外部代码\",{\"1\":{\"26\":1}}],[\"避免嵌套锁\",{\"1\":{\"26\":1}}],[\"避免死锁的一般建议是让两个互斥量以相同的顺序上锁\",{\"1\":{\"26\":1}}],[\"避免了数据竞争\",{\"1\":{\"23\":1}}],[\"避免程序被抛出的异常所终止\",{\"1\":{\"12\":1}}],[\"成功链接上\",{\"1\":{\"28\":1}}],[\"成功调用\",{\"1\":{\"23\":1}}],[\"成员就是保有线程的\",{\"1\":{\"44\":1}}],[\"成员是指向线程的句柄\",{\"1\":{\"44\":1}}],[\"成员函数获取底层指针\",{\"1\":{\"45\":1}}],[\"成员函数获取这个值\",{\"1\":{\"40\":1}}],[\"成员函数有两个版本\",{\"1\":{\"35\":1}}],[\"成员函数即可\",{\"1\":{\"28\":1}}],[\"成员函数\",{\"1\":{\"27\":2,\"38\":2,\"40\":1}}],[\"成员函数的时候执行任务\",{\"1\":{\"38\":1}}],[\"成员函数的时候\",{\"1\":{\"27\":1}}],[\"成员函数的代码\",{\"1\":{\"27\":1}}],[\"成员函数的\",{\"1\":{\"27\":1}}],[\"成员函数模板\",{\"1\":{\"25\":1}}],[\"成员函数指针也是可调用\",{\"1\":{\"14\":1}}],[\"成员指针不可以转换到函数指针单独使用\",{\"1\":{\"14\":1}}],[\"成员指针必须和对象一起使用\",{\"1\":{\"14\":1}}],[\"希望大家分清楚\",{\"1\":{\"23\":1}}],[\"希望您是已经较为熟练使用模板\",{\"1\":{\"0\":1}}],[\"包括支持使用\",{\"1\":{\"38\":1}}],[\"包括虚假唤醒\",{\"1\":{\"35\":1}}],[\"包括管理互斥量的管理类\",{\"1\":{\"33\":1}}],[\"包括获取锁\",{\"1\":{\"26\":1}}],[\"包装任何可调用\",{\"1\":{\"39\":1}}],[\"包装\",{\"1\":{\"26\":1,\"39\":1}}],[\"包起来了\",{\"1\":{\"23\":1}}],[\"包含在其中的代码是线程安全的\",{\"1\":{\"22\":1}}],[\"涉及读写共享资源\",{\"1\":{\"32\":1}}],[\"涉及到了对共享数据的修改\",{\"1\":{\"23\":1}}],[\"涉及共享资源的修改的代码\",{\"1\":{\"23\":1,\"27\":1}}],[\"先关联任务\",{\"1\":{\"39\":1}}],[\"先上锁了互斥量\",{\"1\":{\"27\":1}}],[\"先上锁\",{\"1\":{\"26\":2}}],[\"先锁定\",{\"1\":{\"26\":2}}],[\"先看\",{\"1\":{\"23\":1}}],[\"先执行\",{\"1\":{\"21\":1}}],[\"先执行完\",{\"1\":{\"14\":1}}],[\"较小的粒度意味着锁定的范围更小\",{\"1\":{\"23\":1}}],[\"较少\",{\"1\":{\"15\":1}}],[\"粒度\",{\"1\":{\"23\":1,\"33\":1}}],[\"离开作用域析构的时候解锁\",{\"1\":{\"23\":1}}],[\"式的管理\",{\"1\":{\"23\":1}}],[\"首先我们要明白\",{\"1\":{\"28\":1}}],[\"首先\",{\"1\":{\"27\":1}}],[\"首先管理类\",{\"1\":{\"23\":1}}],[\"首先顾名思义\",{\"1\":{\"23\":1}}],[\"管理类\",{\"1\":{\"23\":3,\"48\":1}}],[\"管理线程对象也就是管理线程\",{\"1\":{\"18\":1}}],[\"简而言之\",{\"1\":{\"22\":1,\"25\":1,\"26\":1,\"27\":1,\"29\":1,\"39\":1,\"40\":1}}],[\"简单来说是\",{\"1\":{\"49\":1}}],[\"简单使用一下\",{\"1\":{\"18\":1}}],[\"简单直观\",{\"1\":{\"15\":1}}],[\"简单的说就是不一定会调用析构\",{\"1\":{\"13\":1}}],[\"简单的说是\",{\"1\":{\"13\":1}}],[\"简单的测试运行的确没问题\",{\"1\":{\"12\":1}}],[\"简单点说\",{\"1\":{\"12\":1}}],[\"简单\",{\"1\":{\"0\":1}}],[\"直到被设置为止\",{\"1\":{\"40\":1}}],[\"直到结果可用\",{\"1\":{\"40\":1}}],[\"直到条件被满足\",{\"1\":{\"35\":1}}],[\"直到条件满足时被唤醒\",{\"1\":{\"35\":1}}],[\"直到线程执行\",{\"1\":{\"22\":1}}],[\"直接执行了\",{\"1\":{\"29\":1}}],[\"直接创建临时对象即可\",{\"1\":{\"12\":1}}],[\"直至获得锁\",{\"1\":{\"22\":1}}],[\"状态\",{\"1\":{\"22\":2}}],[\"标准输出可能交错\",{\"1\":{\"30\":1}}],[\"标准委员会也认为处理此问题很重要\",{\"1\":{\"29\":1}}],[\"标准库设施\",{\"1\":{\"43\":1}}],[\"标准库有两种\",{\"1\":{\"37\":1}}],[\"标准库有很多办法解决这个问题\",{\"1\":{\"26\":1}}],[\"标准库将这种事件称为\",{\"1\":{\"37\":1}}],[\"标准库则没有这个烦恼了\",{\"1\":{\"35\":1}}],[\"标准库对条件变量有两套实现\",{\"1\":{\"35\":1}}],[\"标准库提供的一种互斥量类型\",{\"1\":{\"31\":1}}],[\"标准库自然为我们提供了\",{\"1\":{\"30\":1}}],[\"标准库引入的\",{\"1\":{\"23\":1}}],[\"标准库进行多线程编程已经很久\",{\"1\":{\"19\":1}}],[\"标准库中\",{\"1\":{\"11\":1}}],[\"标量类型等都同理\",{\"1\":{\"21\":1}}],[\"另一种情况就是得显式使用\",{\"1\":{\"28\":1}}],[\"另一个拿到了玩具车\",{\"1\":{\"26\":1}}],[\"另一个\",{\"1\":{\"21\":1}}],[\"另外再聊一聊开销吧\",{\"1\":{\"27\":1}}],[\"另外提示一下\",{\"1\":{\"12\":1}}],[\"见\",{\"1\":{\"21\":2}}],[\"拥有两个冲突的求值的程序就有数据竞争\",{\"1\":{\"21\":1}}],[\"称这些表达式冲突\",{\"1\":{\"21\":1}}],[\"又不想线程因为等待锁而阻塞的情况下\",{\"1\":{\"24\":1}}],[\"又称为互斥锁\",{\"1\":{\"22\":1}}],[\"又去执行另一个线程\",{\"1\":{\"21\":1}}],[\"又或者闹钟没电了睡过站\",{\"1\":{\"35\":1}}],[\"又或者\",{\"1\":{\"21\":1}}],[\"又或者一边看电视一边吃零食\",{\"1\":{\"4\":1}}],[\"等操作\",{\"1\":{\"36\":1}}],[\"等标准库设施用作同步操作\",{\"1\":{\"34\":1}}],[\"等\",{\"1\":{\"28\":1}}],[\"等等\",{\"1\":{\"21\":1}}],[\"等待的同时也可以执行其它的任务\",{\"1\":{\"37\":1}}],[\"等待\",{\"1\":{\"35\":1,\"38\":1}}],[\"等待事件或条件\",{\"0\":{\"35\":1}}],[\"等待异步任务执行完毕\",{\"1\":{\"38\":1}}],[\"等待异步任务\",{\"1\":{\"34\":1}}],[\"等待线程执行\",{\"1\":{\"40\":4}}],[\"等待线程执行结束\",{\"1\":{\"18\":1}}],[\"等待线程对象\",{\"1\":{\"9\":1}}],[\"等待到指定的时间点\",{\"1\":{\"15\":1}}],[\"等待所有线程执行完毕\",{\"1\":{\"10\":1,\"39\":1}}],[\"导致抛出异常\",{\"1\":{\"21\":1}}],[\"恶性的条件竞争\",{\"1\":{\"21\":1}}],[\"从\",{\"1\":{\"40\":1}}],[\"从队列中弹出元素\",{\"1\":{\"36\":2}}],[\"从而允许主线程捕获并处理它\",{\"1\":{\"40\":1}}],[\"从而使得\",{\"1\":{\"35\":1}}],[\"从而造成死锁\",{\"1\":{\"26\":1}}],[\"从而引发未定义的结果\",{\"1\":{\"21\":1}}],[\"从而避免线程对象析构产生问题\",{\"1\":{\"12\":1}}],[\"从多个线程输出的单独字符可能交错\",{\"1\":{\"21\":1}}],[\"流保证是线程安全的\",{\"1\":{\"21\":1}}],[\"❤️\",{\"1\":{\"21\":1}}],[\"谁先谁后并不会有什么太大影响\",{\"1\":{\"21\":1}}],[\"源码中也有这种用法\",{\"1\":{\"49\":1}}],[\"源码\",{\"1\":{\"20\":1}}],[\"源码解析\",{\"0\":{\"43\":1,\"51\":1},\"1\":{\"1\":2,\"14\":1}}],[\"↩︎\",{\"1\":{\"19\":1,\"33\":1}}],[\"重载决议简单来说就是编译器必须要根据规则选择最合适的函数重载进行调用\",{\"1\":{\"19\":1}}],[\"才会考虑\",{\"1\":{\"35\":1}}],[\"才会真正解锁互斥量\",{\"1\":{\"31\":1}}],[\"才会导致\",{\"1\":{\"21\":1}}],[\"才需要获取锁\",{\"1\":{\"29\":1}}],[\"才可实际使用\",{\"1\":{\"19\":1}}],[\"才能传递引用\",{\"1\":{\"38\":1}}],[\"才能更好的使用它\",{\"1\":{\"17\":1}}],[\"才能调用\",{\"1\":{\"12\":1}}],[\"才能进行真正的并行\",{\"1\":{\"5\":1}}],[\"保有一个\",{\"1\":{\"48\":2}}],[\"保有一个互斥量的引用\",{\"1\":{\"48\":1}}],[\"保有的数据成员\",{\"1\":{\"27\":1}}],[\"保护不常更新的数据结构\",{\"0\":{\"30\":1},\"1\":{\"33\":1}}],[\"保护共享数据并非必须使用互斥量\",{\"1\":{\"29\":1}}],[\"保护共享数据的初始化过程\",{\"0\":{\"29\":1},\"1\":{\"33\":1}}],[\"保护共享数据的其它方案\",{\"1\":{\"20\":1}}],[\"保护共享数据\",{\"0\":{\"25\":1},\"1\":{\"19\":1,\"33\":1}}],[\"保证了\",{\"1\":{\"32\":1}}],[\"保证写线程的独占访问\",{\"1\":{\"30\":1}}],[\"保证线程安全的一次初始化\",{\"1\":{\"29\":1}}],[\"保证他们的执行\",{\"1\":{\"18\":1}}],[\"保证这里少一次移动构造的开销\",{\"1\":{\"16\":1}}],[\"至少使用与了解其设计原理是很简单的\",{\"1\":{\"50\":1}}],[\"至少也要学习到使用互斥量\",{\"1\":{\"19\":1}}],[\"至于到底哪个函数哪个线程会先执行\",{\"1\":{\"23\":1}}],[\"至于到底哪个线程才会成功调用\",{\"1\":{\"22\":1}}],[\"至于这个函数产生的异常\",{\"1\":{\"12\":1}}],[\"学完本章\",{\"1\":{\"19\":1}}],[\"没办法直接被那样传递使用\",{\"1\":{\"39\":1}}],[\"没提供直接接收返回值的机制\",{\"1\":{\"38\":1}}],[\"没事找事\",{\"1\":{\"29\":1}}],[\"没什么规律\",{\"1\":{\"18\":1}}],[\"没有要求\",{\"1\":{\"49\":1}}],[\"没有数据成员\",{\"1\":{\"48\":1}}],[\"没有所有权\",{\"1\":{\"38\":1}}],[\"没有所有权自然构造函数就不会上锁\",{\"1\":{\"27\":1}}],[\"没有被移动或绑定到引用\",{\"1\":{\"38\":1}}],[\"没有线程工作\",{\"1\":{\"26\":1}}],[\"没有线程资源\",{\"1\":{\"16\":1}}],[\"没有关联活跃线程\",{\"1\":{\"13\":1,\"16\":1}}],[\"没有问题\",{\"1\":{\"12\":1}}],[\"没有\",{\"1\":{\"12\":1,\"14\":1}}],[\"则进入这个分支\",{\"1\":{\"45\":1}}],[\"则表示线程已成功启动\",{\"1\":{\"45\":1}}],[\"则\",{\"1\":{\"40\":1}}],[\"则可以通过编译\",{\"1\":{\"38\":1}}],[\"则是安全的\",{\"1\":{\"37\":1}}],[\"则解锁互斥体\",{\"1\":{\"31\":1}}],[\"则在重抛前对任何已锁的对象调用\",{\"1\":{\"26\":1}}],[\"则要上锁\",{\"1\":{\"26\":1}}],[\"则毫无意义\",{\"1\":{\"26\":1}}],[\"则不会阻塞当前线程\",{\"1\":{\"24\":1}}],[\"则不需要最后的循环\",{\"1\":{\"18\":1}}],[\"则构造函数不会上锁\",{\"1\":{\"23\":1}}],[\"则一定要多思考\",{\"1\":{\"19\":1}}],[\"则会产生一个编译错误\",{\"1\":{\"14\":1}}],[\"退出\",{\"1\":{\"18\":1}}],[\"~scoped\",{\"1\":{\"48\":2,\"49\":1}}],[\"~unique\",{\"1\":{\"27\":1}}],[\"~lock\",{\"1\":{\"23\":1}}],[\"~joining\",{\"1\":{\"18\":1}}],[\"~thread\",{\"1\":{\"12\":1,\"13\":1}}],[\"稍微注意一下构造函数和赋值运算符的实现即可\",{\"1\":{\"18\":1}}],[\"阅读了\",{\"1\":{\"18\":1}}],[\"阅读源码可以帮助我们更轻松的理解标准库设施的使用与原理\",{\"1\":{\"0\":1}}],[\"实例化\",{\"1\":{\"45\":1}}],[\"实现中可能也是使用到了\",{\"1\":{\"43\":1}}],[\"实现的\",{\"1\":{\"43\":1,\"47\":1}}],[\"实现选择的执行方式\",{\"1\":{\"38\":1}}],[\"实现\",{\"0\":{\"18\":1}}],[\"实际存储数据的队列\",{\"1\":{\"36\":1}}],[\"实际上是默认\",{\"1\":{\"38\":1}}],[\"实际上就是使用条件变量了\",{\"1\":{\"35\":1}}],[\"实际上并没有按引用传递\",{\"1\":{\"14\":1}}],[\"实际需要的是一个\",{\"1\":{\"14\":1}}],[\"形参包展开用\",{\"1\":{\"49\":1}}],[\"形参包元素数量为一的偏特化的唯一区别\",{\"1\":{\"49\":1}}],[\"形参包元素数量为一的偏特化\",{\"1\":{\"49\":1}}],[\"形参\",{\"1\":{\"16\":2}}],[\"具有\",{\"1\":{\"30\":1}}],[\"具有线程资源的所有权\",{\"1\":{\"16\":1}}],[\"具体类型根据类型形参包决定\",{\"1\":{\"48\":1}}],[\"具体来说\",{\"1\":{\"21\":1}}],[\"具体什么时候执行\",{\"1\":{\"14\":1}}],[\"具体如何\",{\"1\":{\"13\":1}}],[\"选择到了移动构造转移线程资源的所有权\",{\"1\":{\"16\":1}}],[\"选择到了移动构造\",{\"1\":{\"16\":1}}],[\"堵塞让其线程执行完毕\",{\"1\":{\"16\":1}}],[\"堵塞什么\",{\"1\":{\"12\":1}}],[\"移动就是转移它的线程资源的所有权给别的\",{\"1\":{\"16\":1}}],[\"移动构造函数\",{\"1\":{\"45\":1}}],[\"移动构造转移线程资源的所有权到\",{\"1\":{\"16\":1}}],[\"移动构造\",{\"1\":{\"14\":1,\"38\":1}}],[\"两个线程分别运行\",{\"1\":{\"36\":1}}],[\"两个线程需要对它们所有的互斥量做一些操作\",{\"1\":{\"26\":1}}],[\"两个线程共享一个\",{\"1\":{\"21\":1}}],[\"两个函数共享了一个锁\",{\"1\":{\"23\":1}}],[\"两个冲突的求值都是原子操作\",{\"1\":{\"21\":1}}],[\"两个求值都在同一线程上\",{\"1\":{\"21\":1}}],[\"两个\",{\"1\":{\"16\":1,\"45\":1}}],[\"转移线程的所有权\",{\"1\":{\"45\":1}}],[\"转移线程资源的所有权到\",{\"1\":{\"16\":1}}],[\"转移\",{\"1\":{\"28\":1}}],[\"转移所有权\",{\"0\":{\"16\":1}}],[\"转换为了一个右值表达式\",{\"1\":{\"16\":1}}],[\"转换为\",{\"1\":{\"14\":1}}],[\"转换为std\",{\"1\":{\"14\":1}}],[\"输出语句\",{\"1\":{\"22\":1}}],[\"输出等待结束后的时间\",{\"1\":{\"15\":1}}],[\"输出等待的时间点\",{\"1\":{\"15\":1}}],[\"输出当前时间\",{\"1\":{\"15\":1}}],[\"输出的值会为\",{\"1\":{\"10\":1}}],[\"50\",{\"1\":{\"39\":1}}],[\"5s\",{\"1\":{\"15\":1}}],[\"5\",{\"1\":{\"15\":1,\"35\":3,\"36\":2,\"40\":1}}],[\"减少浪费的执行时间\",{\"1\":{\"35\":1}}],[\"减少错误发生\",{\"1\":{\"26\":1}}],[\"减少\",{\"1\":{\"15\":1}}],[\"命名空间中的四个函数的基本用法\",{\"1\":{\"15\":1}}],[\"命名空间中的时间对象\",{\"1\":{\"15\":1}}],[\"命名空间中\",{\"1\":{\"15\":1}}],[\"秒就是\",{\"1\":{\"15\":1}}],[\"秒\",{\"1\":{\"15\":1,\"35\":1}}],[\"延时\",{\"1\":{\"15\":1}}],[\"使得能异步调用它\",{\"1\":{\"39\":1}}],[\"使受保护数据传递给外部\",{\"1\":{\"25\":1}}],[\"使当前线程执行停止到指定的时间点\",{\"1\":{\"15\":1}}],[\"使当前线程停止执行指定时间\",{\"1\":{\"15\":1}}],[\"使用了折叠表达式展开形参包\",{\"1\":{\"49\":1}}],[\"使用了\",{\"1\":{\"49\":1}}],[\"使用标准库的内容很多时候不加\",{\"1\":{\"45\":1}}],[\"使用读写锁\",{\"1\":{\"33\":1}}],[\"使用它可以将之前使用\",{\"1\":{\"27\":1}}],[\"使用固定顺序获取锁\",{\"1\":{\"26\":1}}],[\"使用互斥量创建了一个独占锁\",{\"1\":{\"35\":1}}],[\"使用互斥量\",{\"0\":{\"22\":1}}],[\"使用互斥量保护共享数据\",{\"1\":{\"20\":1}}],[\"使用互斥量可以解决这些问题\",{\"1\":{\"18\":1}}],[\"使用我们这节实现的\",{\"1\":{\"18\":1}}],[\"使用容器管理线程对象\",{\"1\":{\"18\":1}}],[\"使用\",{\"0\":{\"37\":1,\"40\":1},\"1\":{\"10\":1,\"12\":2,\"15\":3,\"19\":1,\"23\":1,\"25\":1,\"26\":2,\"27\":2,\"29\":3,\"33\":1,\"35\":1,\"38\":1,\"45\":2}}],[\"使用硬件提高数据处理速度时\",{\"1\":{\"6\":1}}],[\"使用线程也就是使用\",{\"1\":{\"8\":1}}],[\"使用线程\",{\"0\":{\"8\":1},\"1\":{\"1\":1,\"19\":1}}],[\"建议学习现代c++模板教程\",{\"1\":{\"47\":1}}],[\"建议阅读std\",{\"1\":{\"26\":1}}],[\"建议实现重新调度各执行线程\",{\"1\":{\"15\":1}}],[\"建议使用时间字面量\",{\"1\":{\"15\":1}}],[\"建议使用\",{\"1\":{\"12\":1}}],[\"显式转换为\",{\"1\":{\"49\":1}}],[\"显式将\",{\"1\":{\"14\":1}}],[\"显而易见\",{\"1\":{\"35\":1}}],[\"显然逻辑不对\",{\"1\":{\"12\":1}}],[\"显然\",{\"1\":{\"12\":1}}],[\"取决于操作系统的调度\",{\"1\":{\"14\":1}}],[\"下一章\",{\"1\":{\"33\":1}}],[\"下可能为\",{\"1\":{\"14\":1}}],[\"下面我的\",{\"1\":{\"12\":1}}],[\"确保在多线程环境下的数据同步\",{\"1\":{\"35\":1}}],[\"确保全局对象的线程安全访问通常需要额外的同步措施\",{\"1\":{\"32\":1}}],[\"确保每个对象的互斥量都锁住自己要保护的区域\",{\"1\":{\"26\":1}}],[\"确保实参在按值传递时会退化\",{\"1\":{\"14\":1}}],[\"确保线程执行完成\",{\"1\":{\"13\":1}}],[\"确保线程正常执行完成\",{\"1\":{\"12\":1}}],[\"动态\",{\"1\":{\"14\":1}}],[\"被向后移植到了\",{\"1\":{\"43\":1}}],[\"被移动的\",{\"1\":{\"38\":1}}],[\"被设置为\",{\"1\":{\"35\":1}}],[\"被唤醒\",{\"1\":{\"35\":1}}],[\"被\",{\"1\":{\"22\":1}}],[\"被线程对象保存\",{\"1\":{\"14\":1}}],[\"被销毁\",{\"1\":{\"12\":1}}],[\"或许\",{\"1\":{\"49\":1}}],[\"或\",{\"1\":{\"21\":2,\"26\":1,\"28\":1,\"36\":2,\"38\":2}}],[\"或者直接在捐赠初始记录名单中进行评论\",{\"1\":{\"42\":1}}],[\"或者联系我\",{\"1\":{\"42\":1}}],[\"或者外部的\",{\"1\":{\"32\":1}}],[\"或者在同一信号处理函数中执行\",{\"1\":{\"21\":1}}],[\"或者以任何形式转换到a\",{\"1\":{\"14\":1}}],[\"或记住\",{\"1\":{\"19\":1}}],[\"或某个函数类型\",{\"1\":{\"12\":1}}],[\"注意\",{\"1\":{\"14\":1,\"27\":1,\"45\":1}}],[\"加上\",{\"1\":{\"14\":1}}],[\"xx\",{\"1\":{\"36\":1}}],[\"x64\",{\"1\":{\"27\":1}}],[\"x\",{\"1\":{\"14\":15,\"26\":11,\"27\":2,\"32\":4,\"38\":5}}],[\"解引用指针\",{\"1\":{\"45\":1}}],[\"解决\",{\"1\":{\"37\":1}}],[\"解决方案\",{\"1\":{\"14\":1}}],[\"解决方法很简单\",{\"1\":{\"12\":1,\"35\":1}}],[\"解释这段代码最简单的方式就是直接展示标准库的源码\",{\"1\":{\"27\":1}}],[\"解释清楚\",{\"1\":{\"14\":1}}],[\"解锁并释放所有权\",{\"1\":{\"27\":1}}],[\"解锁互斥量\",{\"1\":{\"27\":1,\"31\":1,\"35\":2}}],[\"解锁\",{\"1\":{\"23\":1,\"24\":1,\"26\":3}}],[\"记住\",{\"1\":{\"14\":1}}],[\"of\",{\"1\":{\"45\":1}}],[\"ownership\",{\"1\":{\"45\":1}}],[\"owns\",{\"1\":{\"27\":11,\"28\":3}}],[\"ok\",{\"1\":{\"38\":1}}],[\"one\",{\"1\":{\"35\":3,\"36\":1}}],[\"once\",{\"1\":{\"29\":13,\"33\":1}}],[\"only\",{\"1\":{\"14\":8,\"38\":8}}],[\"occur\",{\"1\":{\"27\":1}}],[\"operation\",{\"1\":{\"27\":1}}],[\"operator<<\",{\"1\":{\"21\":1}}],[\"operator=\",{\"1\":{\"13\":1,\"18\":1,\"23\":1,\"48\":3}}],[\"operator\",{\"1\":{\"12\":5,\"32\":9,\"38\":1,\"39\":3}}],[\"order\",{\"1\":{\"21\":1}}],[\"other\",{\"1\":{\"18\":6,\"28\":5,\"45\":6}}],[\"object\",{\"1\":{\"26\":8,\"27\":2,\"45\":1}}],[\"obj\",{\"1\":{\"14\":2,\"49\":4}}],[\"左值引用没办法引用右值表达式\",{\"1\":{\"14\":1,\"38\":1}}],[\"以\",{\"1\":{\"47\":1}}],[\"以上示例已经足够\",{\"1\":{\"36\":1}}],[\"以上代码使用的就是第二个版本\",{\"1\":{\"35\":1}}],[\"以上代码是线程安全的\",{\"1\":{\"32\":1}}],[\"以上代码\",{\"1\":{\"29\":1}}],[\"以上代码可能导致一些问题\",{\"1\":{\"14\":1}}],[\"以上代码void\",{\"1\":{\"14\":1}}],[\"以确保在执行\",{\"1\":{\"36\":1}}],[\"以确保数据的一致性和正确性\",{\"1\":{\"34\":1}}],[\"以巩固我们对条件变量的学习\",{\"1\":{\"36\":1}}],[\"以后多看\",{\"1\":{\"19\":1}}],[\"以下内容不会对您构成任何的难度\",{\"1\":{\"18\":1}}],[\"以及一个元组\",{\"1\":{\"49\":1}}],[\"以及一些问题\",{\"1\":{\"33\":1}}],[\"以及给所有互斥量上锁\",{\"1\":{\"49\":1}}],[\"以及移动的问题\",{\"1\":{\"38\":1}}],[\"以及传递调用参数\",{\"1\":{\"38\":1,\"39\":1}}],[\"以及特殊情况可能用到的互斥量\",{\"1\":{\"33\":1}}],[\"以及还有很多其他的时间类型\",{\"1\":{\"15\":1}}],[\"以及线程对象正确析构\",{\"1\":{\"12\":1}}],[\"以及为什么我们要两个\",{\"1\":{\"12\":1}}],[\"隐式转换为了指向这个数组的指针\",{\"1\":{\"14\":1}}],[\"隐式转换为\",{\"1\":{\"14\":2}}],[\"呢\",{\"1\":{\"14\":1}}],[\"顾名思义\",{\"1\":{\"14\":1}}],[\"意思也很简单\",{\"1\":{\"14\":1}}],[\"引用的就是\",{\"1\":{\"14\":2}}],[\"引用\",{\"1\":{\"14\":2}}],[\"引入\",{\"1\":{\"10\":1}}],[\"引入的类模板\",{\"1\":{\"47\":1}}],[\"引入的一种通用互斥包装器\",{\"1\":{\"27\":1}}],[\"引入的求和算法\",{\"1\":{\"10\":1}}],[\"引入的\",{\"1\":{\"10\":1,\"12\":1,\"43\":1}}],[\"引入线程支持头文件\",{\"1\":{\"9\":1}}],[\"需要注意\",{\"1\":{\"46\":1}}],[\"需要注意的是\",{\"1\":{\"14\":1}}],[\"需要传入一个索引\",{\"1\":{\"45\":1}}],[\"需要详细讲解\",{\"1\":{\"45\":1}}],[\"需要确保没有其他线程正在执行\",{\"1\":{\"36\":1}}],[\"需要进行额外的同步措施进行保护\",{\"1\":{\"32\":1}}],[\"需要进行保护\",{\"1\":{\"23\":1}}],[\"需要遥控器和玩具车才能玩\",{\"1\":{\"26\":1}}],[\"需要更高的标准会进行强调\",{\"1\":{\"0\":1}}],[\"向可调用对象或函数传递参数很简单\",{\"1\":{\"14\":1}}],[\"传递给线程的参数为\",{\"1\":{\"45\":1}}],[\"传递给\",{\"1\":{\"45\":1}}],[\"传递给这个函数\",{\"1\":{\"14\":1}}],[\"传递\",{\"1\":{\"38\":1}}],[\"传递了一个恶意的函数\",{\"1\":{\"25\":1}}],[\"传递参数\",{\"0\":{\"14\":1},\"1\":{\"17\":1}}],[\"传入的可调用对象\",{\"1\":{\"45\":1}}],[\"传入了一个谓词\",{\"1\":{\"35\":1}}],[\"传入可调用对象以及参数\",{\"1\":{\"16\":1}}],[\"传入成员函数指针\",{\"1\":{\"14\":1}}],[\"传入\",{\"1\":{\"12\":1,\"45\":1}}],[\"严格来说其实这里倒也不算\",{\"1\":{\"13\":1}}],[\"单纯的做好\",{\"1\":{\"13\":1}}],[\"单核机器的任务切换\",{\"1\":{\"5\":1}}],[\"拷贝赋值和拷贝构造定义为\",{\"1\":{\"13\":1}}],[\"看到无序的输出\",{\"1\":{\"32\":1}}],[\"看到它一定打印\",{\"1\":{\"13\":1}}],[\"看起来一点问题也没有\",{\"1\":{\"25\":1}}],[\"看一遍描述就可以了\",{\"1\":{\"22\":1}}],[\"看情况分析\",{\"1\":{\"13\":1}}],[\"难以掌控\",{\"1\":{\"13\":1}}],[\"前三个构造函数都没啥要特别聊的\",{\"1\":{\"45\":1}}],[\"前面的内容也都提到了\",{\"1\":{\"32\":1}}],[\"前者支持更多的操作方式\",{\"1\":{\"30\":1}}],[\"前提是你捕获了这个异常\",{\"1\":{\"13\":1}}],[\"前言\",{\"0\":{\"3\":1}}],[\"抛出一个\",{\"1\":{\"45\":1}}],[\"抛出了一个异常\",{\"1\":{\"13\":1}}],[\"抛出异常并设置\",{\"1\":{\"40\":4}}],[\"抛出异常\",{\"1\":{\"12\":2,\"38\":1}}],[\"抛出异常的代码\",{\"1\":{\"12\":1}}],[\"打印了元组所有的元素\",{\"1\":{\"49\":1}}],[\"打印函数\",{\"1\":{\"23\":1}}],[\"打印的时候\",{\"1\":{\"23\":1}}],[\"打印的是乱序的\",{\"1\":{\"18\":1}}],[\"打印的地址截然不同\",{\"1\":{\"14\":1}}],[\"打印地址完全相同\",{\"1\":{\"14\":1}}],[\"打印\",{\"1\":{\"13\":1,\"16\":2,\"23\":1}}],[\"析构解锁\",{\"1\":{\"48\":1,\"49\":1}}],[\"析构同理\",{\"1\":{\"32\":1}}],[\"析构函数就要稍微聊一下了\",{\"1\":{\"49\":1}}],[\"析构函数\",{\"1\":{\"32\":1}}],[\"析构函数不会再\",{\"1\":{\"27\":1}}],[\"析构函数中解锁\",{\"1\":{\"23\":1}}],[\"析构函数释放资源\",{\"1\":{\"13\":1}}],[\"析构\",{\"1\":{\"13\":2}}],[\"gthread\",{\"1\":{\"46\":3}}],[\"gthreads\",{\"1\":{\"46\":2}}],[\"glibcxx\",{\"1\":{\"46\":2}}],[\"get\",{\"1\":{\"49\":1}}],[\"get<>\",{\"1\":{\"45\":1}}],[\"get<\",{\"1\":{\"45\":2}}],[\"get\",{\"1\":{\"15\":5,\"16\":1,\"18\":4,\"22\":2,\"23\":1,\"28\":1,\"29\":2,\"30\":1,\"31\":2,\"38\":9,\"39\":11,\"40\":8,\"45\":10,\"49\":1}}],[\"g\",{\"1\":{\"13\":2,\"38\":1}}],[\"guard的区别在于\",{\"1\":{\"23\":1}}],[\"guard>\",{\"1\":{\"39\":1}}],[\">>tasks\",{\"1\":{\"39\":1}}],[\">second\",{\"1\":{\"30\":1}}],[\">unlock\",{\"1\":{\"27\":1}}],[\">lock\",{\"1\":{\"27\":2}}],[\">do\",{\"1\":{\"25\":1,\"29\":3}}],[\">=\",{\"1\":{\"10\":1}}],[\">\",{\"1\":{\"10\":1,\"31\":2,\"39\":9,\"45\":9,\"48\":2,\"49\":2}}],[\"package\",{\"1\":{\"39\":1}}],[\"packaged\",{\"1\":{\"39\":14}}],[\"posix\",{\"1\":{\"46\":1}}],[\"pow\",{\"1\":{\"39\":3}}],[\"pop\",{\"1\":{\"36\":20}}],[\"ptr<\",{\"1\":{\"45\":1}}],[\"ptrres\",{\"1\":{\"36\":1}}],[\"ptr\",{\"1\":{\"36\":1}}],[\"ptrptr\",{\"1\":{\"29\":1}}],[\"ptr\",{\"1\":{\"29\":8,\"36\":1,\"37\":2,\"40\":1}}],[\"ptrdiff\",{\"1\":{\"10\":1,\"39\":1}}],[\"permitted\",{\"1\":{\"27\":1}}],[\"pmtx\",{\"1\":{\"27\":5,\"28\":3}}],[\"push\",{\"1\":{\"23\":2,\"36\":15}}],[\"put\",{\"1\":{\"15\":3}}],[\"puts\",{\"1\":{\"13\":1,\"14\":2,\"38\":2}}],[\"public\",{\"1\":{\"12\":1,\"13\":1,\"18\":1,\"23\":1,\"25\":2,\"30\":1,\"36\":1,\"46\":1,\"48\":3}}],[\"p\",{\"1\":{\"12\":3,\"14\":4,\"25\":3,\"32\":11,\"38\":4}}],[\"pred\",{\"1\":{\"35\":2}}],[\"predicate\",{\"1\":{\"35\":1}}],[\"predicate>\",{\"1\":{\"35\":1}}],[\"procedure\",{\"1\":{\"45\":1}}],[\"process\",{\"1\":{\"25\":4,\"28\":3}}],[\"proc\",{\"1\":{\"45\":5}}],[\"prom\",{\"1\":{\"40\":12}}],[\"promised\",{\"1\":{\"40\":1}}],[\"promise只能移动\",{\"1\":{\"40\":1}}],[\"promiseobj\",{\"1\":{\"40\":2}}],[\"promise\",{\"1\":{\"40\":6}}],[\"promise\",{\"1\":{\"40\":16}}],[\"producer\",{\"1\":{\"36\":7}}],[\"protected\",{\"1\":{\"25\":2}}],[\"print\",{\"1\":{\"23\":8}}],[\"private\",{\"1\":{\"23\":1,\"26\":1,\"27\":1,\"30\":1,\"44\":1,\"48\":2}}],[\"pr\",{\"1\":{\"0\":1}}],[\"v\",{\"1\":{\"35\":1}}],[\"variable\",{\"1\":{\"35\":11,\"36\":1}}],[\"validate\",{\"1\":{\"27\":3}}],[\"value\",{\"1\":{\"10\":10,\"30\":2,\"36\":8,\"39\":9,\"40\":9}}],[\"v\",{\"1\":{\"21\":6,\"32\":2,\"43\":1}}],[\"vector\",{\"1\":{\"18\":1,\"21\":3}}],[\"vector\",{\"1\":{\"23\":1}}],[\"vectorv\",{\"1\":{\"21\":1}}],[\"vector\",{\"1\":{\"23\":3}}],[\"list\",{\"1\":{\"23\":28}}],[\"literals\",{\"1\":{\"15\":2}}],[\"lc3\",{\"1\":{\"48\":1}}],[\"lck\",{\"1\":{\"35\":5}}],[\"lc\",{\"1\":{\"23\":3,\"49\":1}}],[\"localtime\",{\"1\":{\"15\":3}}],[\"lock<>\",{\"1\":{\"48\":2}}],[\"lock<\",{\"1\":{\"48\":1}}],[\"lock\",{\"1\":{\"18\":1,\"39\":1,\"45\":6}}],[\"any\",{\"1\":{\"35\":7}}],[\"and\",{\"1\":{\"23\":1,\"48\":2,\"49\":1}}],[\"already\",{\"1\":{\"40\":2}}],[\"alloc\",{\"1\":{\"32\":1}}],[\"aligned\",{\"1\":{\"32\":1}}],[\"adl\",{\"1\":{\"45\":1}}],[\"adapt\",{\"1\":{\"45\":1}}],[\"addressof\",{\"1\":{\"27\":1}}],[\"add\",{\"1\":{\"23\":6}}],[\"adopt\",{\"1\":{\"23\":2,\"26\":3,\"27\":5,\"48\":3,\"49\":3}}],[\"after\",{\"1\":{\"15\":1}}],[\"a的引用只能引用a\",{\"1\":{\"14\":1}}],[\"a\",{\"1\":{\"14\":8,\"23\":6,\"25\":1,\"26\":10,\"39\":8}}],[\"acquisition\",{\"1\":{\"13\":1}}],[\"accumulate\",{\"1\":{\"10\":4,\"23\":1,\"39\":2}}],[\"auto\",{\"1\":{\"10\":6,\"15\":4,\"18\":1,\"21\":1,\"22\":2,\"23\":1,\"30\":1,\"38\":8,\"39\":4,\"45\":5,\"49\":4}}],[\"amd\",{\"1\":{\"10\":1}}],[\"async\",{\"0\":{\"51\":1},\"1\":{\"1\":1,\"38\":26,\"39\":2}}],[\"更多的是一些风格上的\",{\"1\":{\"47\":1}}],[\"更多的线程意味着可以并行完成更多的工作\",{\"1\":{\"10\":1}}],[\"更换为\",{\"1\":{\"35\":1}}],[\"更加灵活\",{\"1\":{\"27\":1}}],[\"更加的灵活\",{\"1\":{\"27\":1}}],[\"更加注重性能\",{\"1\":{\"6\":1}}],[\"更好\",{\"1\":{\"26\":1}}],[\"超线程技术被称为\",{\"1\":{\"10\":1}}],[\"超线程技术是一项硬件创新\",{\"1\":{\"10\":1}}],[\"超线程技术\",{\"1\":{\"10\":1}}],[\"支持任意可调用\",{\"1\":{\"38\":1}}],[\"支持的形式还有很多\",{\"1\":{\"12\":1}}],[\"支持\",{\"1\":{\"10\":1}}],[\"==\",{\"1\":{\"26\":3,\"27\":1}}],[\"=delete\",{\"1\":{\"13\":1}}],[\"=\",{\"1\":{\"10\":19,\"12\":4,\"13\":3,\"14\":12,\"15\":6,\"16\":4,\"18\":4,\"21\":2,\"22\":2,\"23\":4,\"25\":2,\"27\":3,\"28\":2,\"29\":1,\"30\":3,\"32\":5,\"35\":4,\"36\":3,\"38\":13,\"39\":16,\"40\":5,\"44\":1,\"45\":13,\"46\":2,\"48\":8}}],[\"has\",{\"1\":{\"46\":2}}],[\"handle\",{\"1\":{\"44\":1,\"45\":1,\"46\":2}}],[\"hardware\",{\"1\":{\"10\":5,\"39\":1}}],[\"hnd\",{\"1\":{\"44\":2,\"45\":7}}],[\"h\",{\"1\":{\"12\":2,\"15\":3}}],[\"hello\",{\"0\":{\"9\":1},\"1\":{\"9\":8,\"10\":1,\"12\":1,\"14\":2}}],[\"当我们想要获取任务的返回值的时候\",{\"1\":{\"39\":1}}],[\"当我们使用函数对象用于构造\",{\"1\":{\"12\":1}}],[\"当需要这个值的时候\",{\"1\":{\"38\":1}}],[\"当多个线程等待相同事件的结果时\",{\"1\":{\"41\":1}}],[\"当多个线程需要访问一个独立\",{\"1\":{\"37\":1}}],[\"当多个线程执行函数\",{\"1\":{\"22\":1}}],[\"当队列为空时\",{\"1\":{\"36\":2}}],[\"当执行\",{\"1\":{\"36\":1}}],[\"当条件不满足时\",{\"1\":{\"35\":1}}],[\"当同一线程多次对同一个\",{\"1\":{\"31\":1}}],[\"当获取锁后会再检查一次指针②\",{\"1\":{\"29\":1}}],[\"当它无法满足你的需求或者显得代码非常繁琐\",{\"1\":{\"27\":1}}],[\"当运行\",{\"1\":{\"27\":1}}],[\"当然\",{\"1\":{\"27\":1,\"28\":1}}],[\"当然了\",{\"1\":{\"10\":1,\"12\":1,\"14\":1,\"21\":1}}],[\"当写通用代码时\",{\"1\":{\"26\":1}}],[\"当有多个互斥量保护同一个类的对象时\",{\"1\":{\"26\":1}}],[\"当另一个小孩也想玩\",{\"1\":{\"26\":1}}],[\"当其中一个小孩拿到了遥控器和玩具车时\",{\"1\":{\"26\":1}}],[\"当某个表达式的求值写入某个内存位置\",{\"1\":{\"21\":1}}],[\"当两个线程同时尝试向\",{\"1\":{\"21\":1}}],[\"当前持有线程资源\",{\"1\":{\"16\":1}}],[\"当前没有关联活跃线程\",{\"1\":{\"16\":1}}],[\"当前关联了活跃线程\",{\"1\":{\"16\":1}}],[\"当前环境核心越多数据越多\",{\"1\":{\"10\":1}}],[\"当前环境支持并发线程数\",{\"0\":{\"10\":1}}],[\"当异常抛出时\",{\"1\":{\"13\":1}}],[\"当\",{\"1\":{\"10\":3,\"12\":1,\"15\":1,\"35\":1}}],[\"当关注重点在于任务分离或任务响应时\",{\"1\":{\"6\":1}}],[\"其返回值或所抛异常被存储于能通过\",{\"1\":{\"39\":1}}],[\"其它方面就得自己保证了\",{\"1\":{\"32\":1}}],[\"其中的数据成员也是不同的\",{\"1\":{\"48\":1}}],[\"其中的重中之重就是它的构造\",{\"1\":{\"17\":1}}],[\"其中不同的任务或操作按顺序执行\",{\"1\":{\"34\":1}}],[\"其中每个线程都有一个互斥量\",{\"1\":{\"26\":1}}],[\"其他所有的线程\",{\"1\":{\"22\":1}}],[\"其次是\",{\"1\":{\"15\":1}}],[\"其局部对象正常析构释放\",{\"1\":{\"12\":1}}],[\"其实这很简单\",{\"1\":{\"50\":1}}],[\"其实不算难\",{\"1\":{\"49\":1}}],[\"其实不同无非是定义了\",{\"1\":{\"39\":1}}],[\"其实不用感到奇怪\",{\"1\":{\"28\":1}}],[\"其实\",{\"1\":{\"39\":1}}],[\"其实到此基本就差不多了\",{\"1\":{\"38\":1}}],[\"其实第一种方法就是在说\",{\"1\":{\"35\":1}}],[\"其实也很简单\",{\"1\":{\"31\":1}}],[\"其实也就是回到了第一个示例的问题\",{\"1\":{\"26\":1}}],[\"其实也就是管理\",{\"1\":{\"11\":1}}],[\"其实也就是要\",{\"1\":{\"10\":1}}],[\"其实还有不少其他的做法或者反例\",{\"1\":{\"29\":1}}],[\"其实理论上你\",{\"1\":{\"28\":1}}],[\"其实只是传递了它们的指针或者引用罢了\",{\"1\":{\"28\":2}}],[\"其实倒也还好\",{\"1\":{\"27\":1}}],[\"其实上面的代码还不够简单直接\",{\"1\":{\"27\":1}}],[\"其实就是把元组给解包了\",{\"1\":{\"49\":1}}],[\"其实就是在传递可调用对象与参数之前传递枚举值罢了\",{\"1\":{\"38\":1}}],[\"其实就是异步\",{\"1\":{\"37\":1}}],[\"其实就是\",{\"1\":{\"14\":1}}],[\"其实可以直接简单理解为\",{\"1\":{\"3\":1}}],[\"其成员函数也很少\",{\"1\":{\"9\":1}}],[\"高度封装\",{\"1\":{\"9\":1}}],[\"如\",{\"1\":{\"43\":1}}],[\"如同没有线程资源所有权的\",{\"1\":{\"38\":1}}],[\"如同第一个示例那样\",{\"1\":{\"26\":1}}],[\"如下\",{\"1\":{\"27\":1,\"36\":1,\"45\":1}}],[\"如你所见它的形参类型是\",{\"1\":{\"45\":1}}],[\"如你所见很简单\",{\"1\":{\"27\":1}}],[\"如你所见\",{\"1\":{\"9\":1,\"27\":2,\"38\":3,\"45\":1,\"50\":1}}],[\"如果线程启动失败\",{\"1\":{\"45\":1}}],[\"如果线程句柄\",{\"1\":{\"45\":1}}],[\"如果先前调用了\",{\"1\":{\"40\":1}}],[\"如果promise的值还没有被设置\",{\"1\":{\"40\":1}}],[\"如果想要异步的获取返回值\",{\"1\":{\"39\":1}}],[\"如果从\",{\"1\":{\"38\":1}}],[\"如果构造函数\",{\"1\":{\"32\":1}}],[\"如果\",{\"1\":{\"32\":1,\"40\":1}}],[\"如果用外部代码要获取一个锁\",{\"1\":{\"26\":1}}],[\"如果必须要获取多个锁\",{\"1\":{\"26\":1}}],[\"如果按照前面的的选择一个固定的顺序上锁解锁\",{\"1\":{\"26\":1}}],[\"如果两个线程同时运行\",{\"1\":{\"26\":1}}],[\"如果上锁成功就返回\",{\"1\":{\"24\":1}}],[\"如果使用这个构造函数\",{\"1\":{\"23\":1}}],[\"如果互斥锁是锁定的\",{\"1\":{\"22\":1}}],[\"如果出现数据竞争\",{\"1\":{\"21\":1}}],[\"如果是\",{\"1\":{\"38\":1}}],[\"如果是自己重载\",{\"1\":{\"32\":1}}],[\"如果是第一次学习\",{\"1\":{\"19\":1}}],[\"如果是默认构造之类的\",{\"1\":{\"12\":1}}],[\"如果当前有活跃线程\",{\"1\":{\"18\":1}}],[\"如果您好好的学习了上一节的内容\",{\"1\":{\"18\":1}}],[\"如果您支持\",{\"1\":{\"15\":1}}],[\"如果标准达到\",{\"1\":{\"16\":1}}],[\"如果还没完成就调用\",{\"1\":{\"15\":1}}],[\"如果还有不理解\",{\"1\":{\"14\":1}}],[\"如果不使用\",{\"1\":{\"14\":1}}],[\"如果不想等待线程结束可以使用\",{\"1\":{\"13\":1}}],[\"如果对他们的实现感兴趣\",{\"1\":{\"14\":1}}],[\"如果我们的\",{\"1\":{\"14\":1}}],[\"如果有两个线程运行这段代码\",{\"1\":{\"24\":1}}],[\"如果有\",{\"1\":{\"13\":1}}],[\"如果异常被抛出但未被捕获那么就会调用\",{\"1\":{\"13\":1}}],[\"如果抛出异常\",{\"1\":{\"12\":1}}],[\"如果抛出了异常\",{\"1\":{\"12\":1}}],[\"如果代码里抛出了异常\",{\"1\":{\"12\":1}}],[\"如果你需要更多的操作\",{\"1\":{\"32\":1}}],[\"如果你需要多线程求和\",{\"1\":{\"10\":1}}],[\"如果你的标准达到\",{\"1\":{\"32\":1}}],[\"如果你的同事或上司写出此代码\",{\"1\":{\"29\":1}}],[\"如果你学过其它语言或者操作系统\",{\"1\":{\"30\":1}}],[\"如果你有需要\",{\"1\":{\"29\":1}}],[\"如果你有些地方无法理解\",{\"1\":{\"16\":1}}],[\"如果你觉得难以理解\",{\"1\":{\"29\":1}}],[\"如果你简单写一个\",{\"1\":{\"28\":1}}],[\"如果你自己编译了这些代码\",{\"1\":{\"18\":1}}],[\"如果你直接用一个循环不断判断这个操作是否完成就会使得这个线程占满\",{\"1\":{\"15\":1}}],[\"如果你是第一次见到成员指针\",{\"1\":{\"14\":1}}],[\"如果你传入的是一个临时对象\",{\"1\":{\"12\":1}}],[\"如果希望代码可以在\",{\"1\":{\"10\":1}}],[\"如果为\",{\"1\":{\"9\":1}}],[\"如果没有找到键返回空字符串\",{\"1\":{\"30\":1}}],[\"如果没有线程持有这个互斥量\",{\"1\":{\"22\":1}}],[\"如果没有\",{\"1\":{\"0\":1}}],[\"会抛出\",{\"1\":{\"40\":1}}],[\"会堵塞\",{\"1\":{\"38\":1}}],[\"会使用到\",{\"1\":{\"33\":1}}],[\"会按照互斥量的规则进行阻塞\",{\"1\":{\"31\":1}}],[\"会让传入的可调用对象被多次调用\",{\"1\":{\"29\":1}}],[\"会持有一个悬垂指针\",{\"1\":{\"28\":1}}],[\"会尝试上锁\",{\"1\":{\"24\":1}}],[\"会详细了解这个类\",{\"1\":{\"23\":1}}],[\"会打印出了一样的值\",{\"1\":{\"23\":1}}],[\"会导致问题\",{\"1\":{\"14\":1}}],[\"会自动调用栈上所有对象的析构函数\",{\"1\":{\"13\":1}}],[\"会产生未定义行为\",{\"1\":{\"12\":1}}],[\"会调用\",{\"1\":{\"9\":1}}],[\"会讨论程序的并发性\",{\"1\":{\"6\":1}}],[\"会讨论程序的并行性\",{\"1\":{\"6\":1}}],[\"判断线程对象目前是否有关联活跃线程\",{\"1\":{\"9\":1}}],[\"failed\",{\"1\":{\"45\":2}}],[\"false\",{\"1\":{\"9\":1,\"12\":2,\"24\":1,\"27\":3,\"28\":1,\"35\":3}}],[\"fx\",{\"1\":{\"45\":5}}],[\"fnvals\",{\"1\":{\"45\":2}}],[\"fn>\",{\"1\":{\"45\":6}}],[\"fn\",{\"1\":{\"45\":5}}],[\"f3\",{\"1\":{\"38\":1}}],[\"f1\",{\"1\":{\"38\":2}}],[\"front\",{\"1\":{\"36\":2}}],[\"free\",{\"1\":{\"32\":1}}],[\"friend\",{\"1\":{\"26\":1}}],[\"find\",{\"1\":{\"30\":1}}],[\"first\",{\"1\":{\"10\":7,\"39\":4}}],[\"flag\",{\"1\":{\"29\":8,\"35\":5}}],[\"found\",{\"1\":{\"53\":1}}],[\"foo\",{\"1\":{\"25\":1,\"29\":1}}],[\"forward\",{\"1\":{\"49\":2}}],[\"forward<\",{\"1\":{\"45\":6}}],[\"forward\",{\"1\":{\"18\":1,\"39\":1}}],[\"forward\",{\"1\":{\"18\":1,\"49\":2}}],[\"forwardit\",{\"1\":{\"10\":4,\"39\":2}}],[\"forwardit>\",{\"1\":{\"10\":2,\"39\":1}}],[\"for\",{\"1\":{\"10\":2,\"12\":1,\"15\":5,\"18\":3,\"22\":4,\"23\":1,\"24\":1,\"35\":5,\"36\":2,\"38\":1,\"39\":3,\"40\":1,\"44\":1}}],[\"f2\",{\"1\":{\"12\":3,\"13\":2,\"21\":7,\"26\":6,\"38\":3,\"49\":1}}],[\"f\",{\"1\":{\"12\":3,\"13\":2,\"14\":27,\"16\":7,\"21\":10,\"22\":5,\"23\":2,\"26\":6,\"27\":1,\"29\":4,\"32\":3,\"38\":15,\"49\":6}}],[\"fut\",{\"1\":{\"40\":4}}],[\"future\",{\"1\":{\"38\":2}}],[\"futurefuture\",{\"1\":{\"39\":2}}],[\"future\",{\"1\":{\"38\":1,\"39\":1,\"40\":3}}],[\"futures\",{\"1\":{\"33\":1,\"39\":4}}],[\"future\",{\"0\":{\"51\":1},\"1\":{\"1\":1,\"34\":2,\"37\":9,\"38\":11,\"39\":17,\"40\":11,\"41\":4}}],[\"function\",{\"1\":{\"25\":3,\"31\":6,\"40\":4}}],[\"func>\",{\"1\":{\"25\":1}}],[\"func\",{\"1\":{\"12\":9,\"13\":1,\"18\":2,\"25\":3}}],[\"返回类型不为\",{\"1\":{\"49\":1}}],[\"返回类型都是\",{\"1\":{\"49\":1}}],[\"返回类型推导是\",{\"1\":{\"10\":1}}],[\"返回执行结果\",{\"1\":{\"38\":1}}],[\"返回的临时\",{\"1\":{\"16\":1}}],[\"返回当前线程\",{\"1\":{\"15\":1}}],[\"返回\",{\"1\":{\"9\":1,\"14\":1,\"45\":1}}],[\"让元组保有的互斥量引用都进行解锁\",{\"1\":{\"49\":1}}],[\"让你对它再无疑问\",{\"1\":{\"47\":1}}],[\"让你彻底了解这个类\",{\"1\":{\"43\":1}}],[\"让它和\",{\"1\":{\"39\":1}}],[\"让另外的线程有机会获取锁并设置标识\",{\"1\":{\"35\":1}}],[\"让其选择到不上锁的构造函数\",{\"1\":{\"26\":1}}],[\"让其关联的线程执行成员函数\",{\"1\":{\"14\":1}}],[\"让持有线程资源的\",{\"1\":{\"16\":1}}],[\"让当前线程延迟到具体的时间\",{\"1\":{\"15\":1}}],[\"让对象的生命周期和资源绑定\",{\"1\":{\"13\":1}}],[\"让\",{\"1\":{\"9\":1}}],[\"然后就用\",{\"1\":{\"49\":1}}],[\"然后调用\",{\"1\":{\"45\":1}}],[\"然后\",{\"1\":{\"40\":1}}],[\"然后在循环中制造任务插入容器\",{\"1\":{\"39\":1}}],[\"然后我们来传递任务进行异步调用等操作\",{\"1\":{\"39\":1}}],[\"然后再获取返回值\",{\"1\":{\"39\":1}}],[\"然后再尝试锁定\",{\"1\":{\"26\":1}}],[\"然后使用\",{\"1\":{\"39\":1}}],[\"然后逐个执行\",{\"1\":{\"38\":1}}],[\"然后设置一个稍早的闹钟\",{\"1\":{\"35\":1}}],[\"然后讨论了面对不同情况保护共享数据的不同方式\",{\"1\":{\"33\":1}}],[\"然后传递\",{\"1\":{\"27\":1}}],[\"然后作用域结束\",{\"1\":{\"23\":1}}],[\"然后执行\",{\"1\":{\"21\":1}}],[\"然后这个临时对象再用来初始化\",{\"1\":{\"16\":1}}],[\"然后返回它\",{\"1\":{\"16\":1}}],[\"然后会修改对象的状态\",{\"1\":{\"9\":1}}],[\"然而\",{\"1\":{\"38\":1}}],[\"然而在某些情况下\",{\"1\":{\"31\":1}}],[\"然而使用\",{\"1\":{\"30\":1}}],[\"然而使用互斥量来保护共享数据也并不是在函数中加上一个\",{\"1\":{\"25\":1}}],[\"然而这显然没用\",{\"1\":{\"29\":1}}],[\"然而它们的帮助都是有限的\",{\"1\":{\"26\":1}}],[\"然而函数\",{\"1\":{\"26\":1}}],[\"然而此时我们必须再次\",{\"1\":{\"12\":1}}],[\"然而大多数是粗糙的\",{\"1\":{\"0\":1}}],[\"调用可调用对象即可\",{\"1\":{\"49\":1}}],[\"调用可调用对象的参数\",{\"1\":{\"45\":1}}],[\"调用不会上锁的构造函数的参数顺序不同\",{\"1\":{\"49\":1}}],[\"调用时执行\",{\"1\":{\"38\":1}}],[\"调用就阻塞了\",{\"1\":{\"23\":1}}],[\"调用了\",{\"1\":{\"21\":1,\"27\":1}}],[\"调用是线程安全的\",{\"1\":{\"21\":1}}],[\"调用和执行\",{\"1\":{\"14\":1}}],[\"调用成员函数的参数\",{\"1\":{\"14\":1}}],[\"调用析构函数\",{\"1\":{\"13\":1}}],[\"调用之前\",{\"1\":{\"12\":1}}],[\"调用\",{\"1\":{\"9\":1,\"12\":1,\"23\":1,\"28\":1,\"40\":1,\"45\":2}}],[\"否则将所有权层数减少\",{\"1\":{\"31\":1}}],[\"否则将一直堵塞\",{\"1\":{\"9\":1}}],[\"否则\",{\"1\":{\"9\":1}}],[\"就不再重复\",{\"1\":{\"49\":1}}],[\"就不可再次调用\",{\"1\":{\"40\":1}}],[\"就如同我们先前聊\",{\"1\":{\"47\":1}}],[\"就比如它们\",{\"1\":{\"46\":1}}],[\"就接受了我们构造\",{\"1\":{\"45\":1}}],[\"就等于\",{\"1\":{\"45\":1}}],[\"就需要使用\",{\"1\":{\"41\":1}}],[\"就\",{\"1\":{\"39\":1}}],[\"就休息\",{\"1\":{\"35\":1}}],[\"就得使用互斥量之类的方式保护了\",{\"1\":{\"32\":1}}],[\"就可能会导致数据竞争和未定义行为\",{\"1\":{\"32\":1}}],[\"就可以执行了\",{\"1\":{\"45\":1}}],[\"就可以\",{\"1\":{\"29\":1}}],[\"就可以尽情玩耍\",{\"1\":{\"26\":1}}],[\"就可以进去执行了\",{\"1\":{\"23\":1}}],[\"就别再获取第二个锁\",{\"1\":{\"26\":1}}],[\"就有死锁风险\",{\"1\":{\"26\":1}}],[\"就通常不会死锁\",{\"1\":{\"26\":1}}],[\"就应该是单纯的在受互斥量保护的情况下老老实实调用\",{\"1\":{\"25\":1}}],[\"就万事大吉了\",{\"1\":{\"25\":1}}],[\"就更简单了\",{\"1\":{\"23\":1}}],[\"就解锁了互斥量\",{\"1\":{\"22\":1}}],[\"就要避免其它线程修改\",{\"1\":{\"26\":1}}],[\"就要开始聊共享数据的那些事\",{\"1\":{\"20\":1}}],[\"就要特别注意了\",{\"1\":{\"14\":1}}],[\"就能让这种保护形同虚设\",{\"1\":{\"25\":1}}],[\"就能成功调用\",{\"1\":{\"14\":1}}],[\"就能正常的调用\",{\"1\":{\"12\":1}}],[\"就在\",{\"1\":{\"12\":1}}],[\"就会导致编译错误\",{\"1\":{\"38\":1}}],[\"就会阻塞直到\",{\"1\":{\"38\":1}}],[\"就会违反第一个指导意见\",{\"1\":{\"26\":1}}],[\"就会产生死锁\",{\"1\":{\"26\":1}}],[\"就会跳转到\",{\"1\":{\"12\":1}}],[\"就会被跳过\",{\"1\":{\"12\":1}}],[\"就手搓了一个\",{\"1\":{\"10\":1}}],[\"就是我们前面讲的\",{\"1\":{\"45\":1}}],[\"就是接受一个元组类型\",{\"1\":{\"45\":1}}],[\"就是占据\",{\"1\":{\"44\":1}}],[\"就是其关联的事件\",{\"1\":{\"37\":1}}],[\"就是调用移动构造\",{\"1\":{\"28\":1}}],[\"就是按引用传递了\",{\"1\":{\"14\":1}}],[\"就是返回了\",{\"1\":{\"14\":1}}],[\"就是\",{\"1\":{\"14\":1,\"45\":1}}],[\"就是构造\",{\"1\":{\"12\":1}}],[\"就是确保线程对象关联的线程已经执行完毕\",{\"1\":{\"9\":1}}],[\"就是为了区别多线程中不同的关注点\",{\"1\":{\"6\":1}}],[\"就自动在新线程开始执行函数\",{\"1\":{\"9\":1}}],[\"线程模型\",{\"1\":{\"46\":1}}],[\"线程知道预期结果\",{\"1\":{\"37\":1}}],[\"线程弹出元素\",{\"1\":{\"36\":5}}],[\"线程插入元素\",{\"1\":{\"36\":5}}],[\"线程可以等待\",{\"1\":{\"35\":1}}],[\"线程可以在递归互斥体上重复调用\",{\"1\":{\"31\":1}}],[\"线程对已经上锁的\",{\"1\":{\"31\":1}}],[\"线程对象的大小都是\",{\"1\":{\"46\":1}}],[\"线程对象代表了线程\",{\"1\":{\"18\":1}}],[\"线程对象\",{\"1\":{\"14\":1,\"16\":2}}],[\"线程对象现在是否有关联的活跃线程\",{\"1\":{\"13\":1}}],[\"线程对象正常析构\",{\"1\":{\"13\":1}}],[\"线程对象可以正常析构\",{\"1\":{\"12\":1}}],[\"线程对象放弃了线程资源的所有权\",{\"1\":{\"12\":1}}],[\"线程对象不再持有线程资源\",{\"1\":{\"12\":1}}],[\"线程对象调用了\",{\"1\":{\"12\":1}}],[\"线程对象没有关联线程的\",{\"1\":{\"12\":1}}],[\"线程对象关联了一个线程资源\",{\"1\":{\"9\":1}}],[\"线程安全的队列\",{\"0\":{\"36\":1}}],[\"线程安全的初始化过程\",{\"1\":{\"29\":1}}],[\"线程安全的一次初始化\",{\"1\":{\"29\":1}}],[\"线程不安全\",{\"1\":{\"29\":1}}],[\"线程获取一个锁时\",{\"1\":{\"26\":1}}],[\"线程资源的所有权转移给函数调用\",{\"1\":{\"16\":1}}],[\"线程资源\",{\"1\":{\"16\":1}}],[\"线程是一种系统资源\",{\"1\":{\"16\":1}}],[\"线程需要等待某个操作完成\",{\"1\":{\"15\":1}}],[\"线程开始执行\",{\"1\":{\"12\":1}}],[\"线程独立的运行\",{\"1\":{\"12\":1}}],[\"线程分离\",{\"1\":{\"12\":1}}],[\"线程库启动线程\",{\"1\":{\"12\":1}}],[\"线程管理\",{\"0\":{\"11\":1},\"1\":{\"11\":1}}],[\"线程\",{\"1\":{\"10\":1,\"23\":2,\"24\":3,\"26\":3}}],[\"线程其实是指所谓的逻辑处理器\",{\"1\":{\"10\":1}}],[\"线程的\",{\"1\":{\"10\":1}}],[\"线程更是上千\",{\"1\":{\"5\":1}}],[\"将线程id设置为0\",{\"1\":{\"45\":1}}],[\"将独占指针的所有权转移给线程\",{\"1\":{\"45\":1}}],[\"将我们构造函数的参数全部完美转发\",{\"1\":{\"45\":1}}],[\"将错误码设置为\",{\"1\":{\"40\":1}}],[\"将持有最终计算出来的结果\",{\"1\":{\"38\":1}}],[\"将互斥量的所有权转移给调用方\",{\"1\":{\"28\":1}}],[\"将数据成员赋给新对象\",{\"1\":{\"28\":1}}],[\"将临时对象的线程资源所有权移交给\",{\"1\":{\"16\":1}}],[\"将对象包装\",{\"1\":{\"14\":1}}],[\"将\",{\"1\":{\"9\":1,\"12\":1,\"14\":1,\"16\":3,\"45\":1}}],[\"joining\",{\"1\":{\"18\":11}}],[\"joinable\",{\"1\":{\"9\":2,\"12\":3,\"13\":1,\"16\":4,\"18\":4}}],[\"join\",{\"1\":{\"9\":3,\"10\":2,\"12\":26,\"13\":4,\"14\":7,\"15\":1,\"16\":11,\"18\":8,\"21\":4,\"22\":2,\"23\":4,\"24\":2,\"31\":2,\"38\":1,\"39\":3,\"40\":3}}],[\"写入到标准输出流\",{\"1\":{\"9\":1}}],[\"<>\",{\"1\":{\"48\":1}}],[\"\",{\"1\":{\"37\":1,\"38\":1}}],[\"\",{\"1\":{\"22\":1,\"31\":1}}],[\"<=\",{\"1\":{\"12\":1}}],[\"<\",{\"1\":{\"10\":5,\"18\":2,\"22\":2,\"36\":2,\"39\":3}}],[\"\",{\"1\":{\"9\":1,\"10\":1,\"12\":2,\"31\":1,\"38\":1}}],[\"<<\",{\"1\":{\"9\":4,\"10\":3,\"12\":2,\"14\":12,\"15\":13,\"16\":10,\"18\":6,\"21\":4,\"22\":4,\"23\":5,\"24\":12,\"29\":3,\"31\":10,\"35\":2,\"36\":6,\"38\":18,\"39\":6,\"40\":15,\"49\":2}}],[\"\",{\"1\":{\"9\":2,\"10\":1,\"12\":2,\"31\":1,\"38\":1}}],[\"md\",{\"0\":{\"54\":1}}],[\"mq白\",{\"1\":{\"37\":1}}],[\"m2\",{\"1\":{\"26\":8,\"48\":2}}],[\"m1\",{\"1\":{\"26\":7,\"48\":3}}],[\"make\",{\"1\":{\"36\":1,\"45\":6,\"49\":2}}],[\"malloc\",{\"1\":{\"32\":1}}],[\"malicious\",{\"1\":{\"25\":3}}],[\"map\",{\"1\":{\"48\":1}}],[\"mutexes\",{\"1\":{\"48\":6,\"49\":3}}],[\"mutex>sp\",{\"1\":{\"49\":1}}],[\"mutex>get\",{\"1\":{\"28\":1}}],[\"mutex>lk\",{\"1\":{\"28\":2,\"29\":1,\"35\":2,\"36\":4}}],[\"mutex>lock\",{\"1\":{\"27\":3}}],[\"mutex>lock2\",{\"1\":{\"26\":2,\"27\":1}}],[\"mutex>lock1\",{\"1\":{\"26\":2,\"27\":1}}],[\"mutex>lc2\",{\"1\":{\"26\":2,\"48\":1}}],[\"mutex>lc1\",{\"1\":{\"26\":2}}],[\"mutex>lc\",{\"1\":{\"23\":4,\"25\":1,\"31\":1,\"48\":1}}],[\"mutex>\",{\"1\":{\"23\":3,\"30\":3,\"35\":6,\"48\":2,\"49\":1}}],[\"mutex\",{\"1\":{\"22\":2,\"23\":9,\"24\":1,\"25\":1,\"26\":2,\"27\":7,\"28\":10,\"29\":1,\"30\":12,\"31\":6,\"33\":4,\"35\":3,\"36\":1,\"48\":10,\"49\":2}}],[\"multi\",{\"1\":{\"10\":1}}],[\"memory\",{\"1\":{\"21\":1}}],[\"move\",{\"1\":{\"14\":9,\"16\":5,\"18\":4,\"28\":1,\"38\":12,\"39\":3,\"40\":4,\"45\":2}}],[\"mymutexes让它保有这些互斥量的引用\",{\"1\":{\"49\":2}}],[\"mymutexes\",{\"1\":{\"48\":4,\"49\":3}}],[\"mymutex\",{\"1\":{\"23\":5,\"48\":5}}],[\"my\",{\"1\":{\"12\":10,\"29\":5}}],[\"m\",{\"1\":{\"12\":3,\"13\":4,\"15\":3,\"22\":3,\"23\":9,\"25\":2,\"26\":11,\"27\":8,\"29\":2,\"30\":2,\"35\":3,\"36\":5,\"49\":5}}],[\"msvc\",{\"1\":{\"0\":1,\"23\":1,\"27\":1,\"43\":2,\"45\":1,\"47\":2,\"49\":1}}],[\"image\",{\"0\":{\"55\":1}}],[\"impl\",{\"1\":{\"49\":2}}],[\"it\",{\"1\":{\"30\":3}}],[\"iterator\",{\"1\":{\"10\":1}}],[\"iter\",{\"1\":{\"10\":2,\"39\":1}}],[\"iota\",{\"1\":{\"23\":1}}],[\"identifier\",{\"1\":{\"44\":1}}],[\"id打印主线程和子线程的\",{\"1\":{\"15\":1}}],[\"id\",{\"1\":{\"15\":6,\"16\":1,\"18\":7,\"22\":2,\"23\":1,\"24\":4,\"31\":2,\"38\":6,\"44\":5,\"45\":5,\"46\":1}}],[\"isdone\",{\"1\":{\"15\":1}}],[\"is\",{\"1\":{\"13\":1,\"45\":1}}],[\"issues\",{\"1\":{\"0\":1}}],[\"i\",{\"1\":{\"10\":9,\"12\":8,\"18\":6,\"22\":4,\"23\":4,\"36\":5,\"39\":11}}],[\"ifdef\",{\"1\":{\"46\":1}}],[\"if\",{\"1\":{\"10\":1,\"13\":1,\"18\":2,\"24\":1,\"26\":3,\"27\":6,\"29\":2,\"31\":2,\"39\":1,\"45\":3}}],[\"i7\",{\"1\":{\"10\":1}}],[\"incomplete\",{\"1\":{\"45\":1}}],[\"include\",{\"1\":{\"9\":3,\"10\":2,\"12\":4,\"22\":1,\"31\":3,\"38\":3}}],[\"indices\",{\"1\":{\"45\":3}}],[\"indices>\",{\"1\":{\"45\":4}}],[\"index>\",{\"1\":{\"49\":1}}],[\"index\",{\"1\":{\"45\":5,\"49\":4}}],[\"invoke<\",{\"1\":{\"45\":3}}],[\"invoker\",{\"1\":{\"45\":5}}],[\"invoke\",{\"1\":{\"43\":1,\"45\":13,\"49\":2}}],[\"invocable\",{\"1\":{\"14\":1,\"45\":1}}],[\"int\",{\"1\":{\"9\":2,\"10\":2,\"12\":16,\"13\":1,\"14\":24,\"15\":4,\"16\":4,\"18\":3,\"21\":3,\"22\":2,\"23\":2,\"24\":1,\"25\":1,\"27\":2,\"29\":2,\"31\":3,\"32\":2,\"36\":3,\"38\":17,\"39\":18,\"40\":5,\"44\":2,\"45\":1,\"46\":1}}],[\"what\",{\"1\":{\"40\":2}}],[\"while\",{\"1\":{\"15\":1,\"35\":3}}],[\"would\",{\"1\":{\"27\":1}}],[\"work\",{\"1\":{\"18\":3}}],[\"world\",{\"0\":{\"9\":1},\"1\":{\"9\":3,\"10\":1,\"12\":1}}],[\"win32\",{\"1\":{\"44\":2,\"46\":1}}],[\"windows\",{\"1\":{\"14\":1}}],[\"with\",{\"1\":{\"23\":1,\"48\":1}}],[\"waitforarrival\",{\"1\":{\"35\":3}}],[\"wait\",{\"1\":{\"35\":8,\"36\":2,\"38\":6}}],[\"waiting\",{\"1\":{\"15\":2,\"35\":1}}],[\"wakeup\",{\"1\":{\"15\":5,\"35\":1}}],[\"wrapper\",{\"1\":{\"14\":1}}],[\"wrapper\",{\"1\":{\"45\":5,\"49\":1}}],[\"tuple\",{\"1\":{\"39\":1,\"45\":12,\"48\":1,\"49\":12}}],[\"t>>\",{\"1\":{\"49\":1}}],[\"t<\",{\"1\":{\"45\":6}}],[\"t\",{\"1\":{\"10\":1,\"39\":1}}],[\"ts\",{\"1\":{\"39\":3}}],[\"t4\",{\"1\":{\"23\":2,\"38\":1}}],[\"t参数的构造函数\",{\"1\":{\"23\":1}}],[\"t3\",{\"1\":{\"21\":1,\"23\":2,\"38\":1}}],[\"t1\",{\"1\":{\"21\":1,\"23\":2,\"24\":2,\"31\":2,\"38\":1}}],[\"t重载决议\",{\"1\":{\"16\":1}}],[\"t2\",{\"1\":{\"16\":14,\"21\":5,\"23\":2,\"24\":2,\"26\":1,\"31\":2,\"38\":1}}],[\"timed\",{\"1\":{\"30\":2}}],[\"time\",{\"1\":{\"15\":19}}],[\"t>\",{\"1\":{\"14\":1,\"36\":1}}],[\"the\",{\"1\":{\"27\":2,\"45\":1}}],[\"that\",{\"1\":{\"23\":1,\"48\":1}}],[\"thrd\",{\"1\":{\"44\":6}}],[\"thr\",{\"1\":{\"44\":2,\"45\":17}}],[\"throw\",{\"1\":{\"12\":4,\"27\":2,\"29\":1,\"40\":6,\"45\":2}}],[\"threadfunction\",{\"1\":{\"24\":3}}],[\"thread2\",{\"1\":{\"18\":1}}],[\"thread>threads\",{\"1\":{\"18\":2,\"22\":2}}],[\"thread>\",{\"1\":{\"10\":1,\"39\":1,\"45\":1}}],[\"threadsafe\",{\"1\":{\"36\":5}}],[\"threads\",{\"1\":{\"10\":9,\"18\":3,\"22\":4,\"39\":9}}],[\"threading\",{\"1\":{\"10\":1}}],[\"thread\",{\"0\":{\"43\":1},\"1\":{\"1\":1,\"8\":2,\"9\":6,\"10\":6,\"11\":3,\"12\":35,\"13\":16,\"14\":21,\"15\":8,\"16\":25,\"17\":1,\"18\":27,\"19\":2,\"20\":1,\"21\":5,\"22\":6,\"23\":5,\"24\":3,\"26\":2,\"31\":6,\"35\":3,\"37\":1,\"38\":9,\"39\":6,\"40\":4,\"43\":2,\"44\":3,\"45\":21,\"46\":3,\"47\":1,\"49\":2,\"50\":1}}],[\"this\",{\"1\":{\"12\":1,\"15\":7,\"16\":1,\"18\":3,\"22\":2,\"23\":1,\"24\":1,\"31\":2,\"35\":3,\"36\":2,\"38\":5,\"40\":1}}],[\"to\",{\"1\":{\"15\":3,\"23\":7,\"45\":4}}],[\"todo\",{\"1\":{\"12\":4,\"14\":3,\"39\":1}}],[\"total\",{\"1\":{\"10\":4,\"39\":5}}],[\"tasks\",{\"1\":{\"39\":4}}],[\"task\",{\"1\":{\"10\":1}}],[\"try块中的代码抛出了异常\",{\"1\":{\"12\":1}}],[\"trycatch\",{\"1\":{\"12\":1}}],[\"try\",{\"1\":{\"12\":4,\"24\":3,\"27\":1,\"29\":2,\"36\":1,\"40\":4,\"45\":2}}],[\"true\",{\"1\":{\"9\":1,\"12\":1,\"24\":1,\"27\":5,\"35\":6}}],[\"types\",{\"1\":{\"45\":1}}],[\"type>>\",{\"1\":{\"39\":1}}],[\"type>>futures\",{\"1\":{\"39\":1}}],[\"type>results\",{\"1\":{\"10\":1}}],[\"typename\",{\"1\":{\"10\":1,\"18\":1,\"39\":2}}],[\"type\",{\"1\":{\"10\":8,\"23\":1,\"39\":8,\"46\":2,\"48\":1}}],[\"test\",{\"1\":{\"14\":4}}],[\"terminates\",{\"1\":{\"45\":1}}],[\"terminate\",{\"1\":{\"9\":2,\"13\":1}}],[\"template<\",{\"1\":{\"49\":1}}],[\"template\",{\"1\":{\"36\":1}}],[\"shared\",{\"1\":{\"29\":1,\"30\":11,\"33\":1,\"36\":3,\"37\":4,\"41\":1}}],[\"some\",{\"1\":{\"28\":4,\"29\":4}}],[\"something\",{\"1\":{\"25\":4,\"29\":3}}],[\"swap\",{\"1\":{\"18\":2,\"26\":10,\"27\":3}}],[\"s\",{\"1\":{\"15\":3,\"45\":2}}],[\"system\",{\"1\":{\"15\":5,\"27\":2}}],[\"sleep\",{\"1\":{\"15\":11,\"24\":1,\"35\":3,\"38\":1,\"40\":1}}],[\"simulatearrival\",{\"1\":{\"35\":3}}],[\"simultaneous\",{\"1\":{\"10\":1}}],[\"sizeof\",{\"1\":{\"44\":1,\"45\":2,\"46\":1}}],[\"size\",{\"1\":{\"10\":9,\"18\":3,\"21\":1,\"22\":2,\"26\":1,\"32\":1,\"39\":7,\"45\":2,\"49\":2}}],[\"sum\",{\"1\":{\"10\":8,\"23\":4,\"39\":7}}],[\"summary\",{\"0\":{\"1\":1}}],[\"smt\",{\"1\":{\"10\":1}}],[\"scoped\",{\"0\":{\"47\":1},\"1\":{\"1\":1,\"23\":3,\"26\":4,\"47\":2,\"48\":26,\"49\":6,\"50\":1}}],[\"st\",{\"0\":{\"51\":1}}],[\"static\",{\"1\":{\"29\":1,\"45\":3}}],[\"start\",{\"1\":{\"10\":8,\"39\":5,\"45\":5}}],[\"str\",{\"1\":{\"26\":2}}],[\"strengthened\",{\"1\":{\"23\":1,\"48\":3,\"49\":1}}],[\"string>\",{\"1\":{\"30\":1}}],[\"string\",{\"1\":{\"14\":10,\"25\":1,\"26\":2,\"30\":5,\"49\":1}}],[\"struct\",{\"1\":{\"12\":2,\"14\":3,\"26\":1,\"32\":1,\"38\":3,\"44\":1}}],[\"stdcall\",{\"1\":{\"45\":1}}],[\"std\",{\"0\":{\"43\":1,\"47\":1,\"51\":1},\"1\":{\"1\":4,\"8\":2,\"9\":12,\"10\":24,\"11\":3,\"12\":25,\"13\":7,\"14\":57,\"15\":32,\"16\":35,\"17\":1,\"18\":25,\"19\":2,\"20\":1,\"21\":13,\"22\":10,\"23\":34,\"24\":11,\"25\":5,\"26\":30,\"27\":30,\"28\":15,\"29\":20,\"30\":19,\"31\":16,\"32\":7,\"33\":4,\"34\":1,\"35\":27,\"36\":14,\"37\":7,\"38\":66,\"39\":50,\"40\":42,\"41\":4,\"43\":2,\"44\":2,\"45\":30,\"46\":2,\"47\":3,\"48\":13,\"49\":32,\"50\":2}}],[\"stl\",{\"1\":{\"0\":1,\"23\":1,\"27\":1,\"43\":1,\"47\":1,\"49\":1}}],[\"++n\",{\"1\":{\"26\":2,\"27\":1,\"29\":1}}],[\"++i\",{\"1\":{\"10\":1,\"12\":1,\"18\":2,\"22\":2,\"36\":2,\"39\":2}}],[\"+=\",{\"1\":{\"12\":1,\"32\":1,\"39\":1}}],[\"+\",{\"1\":{\"0\":2,\"10\":2,\"15\":1,\"23\":1,\"36\":66,\"39\":3,\"45\":3,\"49\":1}}],[\"本单章专门介绍标准库在\",{\"1\":{\"47\":1}}],[\"本章将讨论如何使用条件变量等待事件\",{\"1\":{\"34\":1}}],[\"本章的主要内容有\",{\"1\":{\"34\":1}}],[\"本章的内容并不少见\",{\"1\":{\"19\":1}}],[\"本章讨论了多线程的共享数据引发的恶性条件竞争会带来的问题\",{\"1\":{\"33\":1}}],[\"本章也完全不用担心\",{\"1\":{\"20\":1}}],[\"本章节主要内容\",{\"1\":{\"20\":1}}],[\"本章节的内容围绕着\",{\"1\":{\"19\":1}}],[\"本身是重载了\",{\"1\":{\"39\":1}}],[\"本身就是右值表达式\",{\"1\":{\"16\":1}}],[\"本身就是生成了一个函数对象\",{\"1\":{\"12\":1}}],[\"本身设置使用很简单\",{\"1\":{\"15\":1}}],[\"本节内容总体来说是很简单的\",{\"1\":{\"16\":1}}],[\"本节代码只是为了学习\",{\"1\":{\"10\":1}}],[\"本节其实是要普及一下计算机常识\",{\"1\":{\"10\":1}}],[\"本教程假设开发者的最低水平为\",{\"1\":{\"0\":1}}],[\"本仓库用来存放\",{\"1\":{\"0\":1}}],[\"准确的方式进行教学\",{\"1\":{\"0\":1}}],[\"不在乎其返回类型只用来实施它的副作用\",{\"1\":{\"49\":1}}],[\"不需要模板的水平有多高\",{\"1\":{\"46\":1}}],[\"不为空\",{\"1\":{\"45\":1}}],[\"不然就会抛出异常\",{\"1\":{\"40\":1}}],[\"不然保护将形同虚设\",{\"1\":{\"25\":1}}],[\"不能复制\",{\"1\":{\"39\":1}}],[\"不能调用\",{\"1\":{\"38\":1}}],[\"不堵塞\",{\"1\":{\"39\":1}}],[\"不创建线程\",{\"1\":{\"38\":2}}],[\"不执行\",{\"1\":{\"36\":1}}],[\"不应该执行\",{\"1\":{\"36\":1}}],[\"不包括用户定义的\",{\"1\":{\"32\":1}}],[\"不要\",{\"1\":{\"29\":1}}],[\"不再表示执行线程失去了线程的所有权\",{\"1\":{\"45\":1}}],[\"不再强调\",{\"1\":{\"28\":1}}],[\"不再管理此线程\",{\"1\":{\"12\":1}}],[\"不同线程对同一个互斥量进行锁定时\",{\"1\":{\"31\":1}}],[\"不同\",{\"1\":{\"24\":1}}],[\"不存在一个对象同时被多个线程修改\",{\"1\":{\"23\":1}}],[\"不会造成任何额外开销\",{\"1\":{\"48\":1}}],[\"不会模板\",{\"1\":{\"46\":1,\"47\":1,\"50\":1}}],[\"不会被其它线程的执行所打断\",{\"1\":{\"22\":1}}],[\"不会被打断\",{\"1\":{\"21\":1}}],[\"不会有任何其他的线程打断这个操作\",{\"1\":{\"22\":1}}],[\"不是同一个\",{\"1\":{\"23\":1}}],[\"不是线程安全的容器\",{\"1\":{\"21\":1}}],[\"不是真的返回引用\",{\"1\":{\"14\":1}}],[\"不可能看到正在被add\",{\"1\":{\"23\":1}}],[\"不可能每一个任务都单独给一个核心\",{\"1\":{\"5\":1}}],[\"不可表示一个线程\",{\"1\":{\"16\":1,\"45\":1}}],[\"不可复制不可移动\",{\"1\":{\"28\":1}}],[\"不可复制\",{\"1\":{\"16\":1,\"40\":1,\"45\":1}}],[\"不用为数据竞争而担心\",{\"1\":{\"29\":1}}],[\"不用调用\",{\"1\":{\"16\":1}}],[\"不用着急\",{\"1\":{\"15\":1,\"16\":1}}],[\"不用担心生存期的问题\",{\"1\":{\"14\":1}}],[\"不用担心\",{\"1\":{\"14\":1,\"47\":1}}],[\"不用在乎\",{\"1\":{\"13\":1}}],[\"不用在意\",{\"1\":{\"3\":1}}],[\"不允许这些操作主要在于\",{\"1\":{\"13\":1}}],[\"不掩盖错误\",{\"1\":{\"12\":1}}],[\"不等待线程结束\",{\"1\":{\"12\":1}}],[\"不过你可能有疑问\",{\"1\":{\"49\":1}}],[\"不过实测\",{\"1\":{\"46\":1}}],[\"不过这个重载的返回类型是\",{\"1\":{\"39\":1}}],[\"不过这也不是问题\",{\"1\":{\"16\":1}}],[\"不过也可以单独使用\",{\"1\":{\"39\":1}}],[\"不过引用的并非是局部的n\",{\"1\":{\"38\":1}}],[\"不过引用的不是我们传递的局部对象\",{\"1\":{\"38\":1}}],[\"不过对于处理一个互斥量的情况\",{\"1\":{\"23\":1}}],[\"不过一般不推荐这样显式的\",{\"1\":{\"23\":1}}],[\"不过需要注意\",{\"1\":{\"14\":1}}],[\"不过有件事情需要注意\",{\"1\":{\"12\":1}}],[\"不过我们说了\",{\"1\":{\"10\":1}}],[\"不过使用类似\",{\"1\":{\"10\":1}}],[\"不过\",{\"1\":{\"9\":1,\"37\":1,\"41\":1}}],[\"不够准确\",{\"1\":{\"0\":1}}],[\"不管是\",{\"1\":{\"46\":1}}],[\"不管是书籍\",{\"1\":{\"0\":1}}],[\"不管是否购买课程\",{\"1\":{\"0\":1}}],[\"视频\",{\"1\":{\"0\":1}}],[\"博客\",{\"1\":{\"0\":1}}],[\"国内的\",{\"1\":{\"0\":1}}],[\"均会在致谢列表中铭记您的贡献\",{\"1\":{\"0\":1}}],[\"捐赠\",{\"1\":{\"0\":1}}],[\"协议均可随意使用学习\",{\"1\":{\"0\":1}}],[\"0>\",{\"1\":{\"45\":1}}],[\"0\",{\"1\":{\"0\":1,\"10\":5,\"12\":4,\"13\":1,\"14\":4,\"16\":2,\"18\":2,\"21\":2,\"22\":2,\"23\":3,\"29\":1,\"31\":2,\"36\":7,\"38\":2,\"39\":3,\"40\":1,\"45\":7}}],[\"404\",{\"1\":{\"53\":1}}],[\"4\",{\"1\":{\"0\":1,\"10\":1,\"23\":1,\"29\":1,\"36\":7,\"44\":1}}],[\"native\",{\"1\":{\"46\":2}}],[\"namespace\",{\"1\":{\"15\":1}}],[\"new\",{\"1\":{\"28\":1,\"29\":3,\"32\":14,\"33\":1,\"36\":3}}],[\"next\",{\"1\":{\"10\":2,\"39\":1}}],[\"nullptr\",{\"1\":{\"25\":1,\"27\":1,\"28\":1,\"32\":1,\"45\":2}}],[\"numbers\",{\"1\":{\"23\":6}}],[\"num\",{\"1\":{\"10\":6,\"39\":6,\"40\":6}}],[\"notify\",{\"1\":{\"35\":3,\"36\":1}}],[\"not\",{\"1\":{\"27\":1,\"53\":1}}],[\"nodiscard\",{\"1\":{\"23\":1,\"28\":1,\"45\":2,\"48\":2}}],[\"now\",{\"1\":{\"15\":11}}],[\"noexcept\",{\"1\":{\"14\":1,\"18\":9,\"23\":2,\"27\":2,\"28\":1,\"38\":1,\"45\":4,\"48\":5,\"49\":2}}],[\"n\",{\"1\":{\"10\":3,\"12\":15,\"13\":2,\"14\":33,\"15\":2,\"16\":5,\"18\":3,\"21\":3,\"22\":2,\"23\":5,\"26\":1,\"29\":3,\"32\":2,\"38\":24,\"39\":3,\"40\":5}}],[\"nd\",{\"1\":{\"0\":1}}],[\"nc\",{\"1\":{\"0\":1}}],[\"cnd\",{\"1\":{\"45\":1}}],[\"cnt++\",{\"1\":{\"21\":1}}],[\"cnt\",{\"1\":{\"21\":1}}],[\"cpp\",{\"1\":{\"45\":2}}],[\"cpu\",{\"1\":{\"5\":2,\"10\":3,\"15\":4,\"36\":1}}],[\"cstd\",{\"1\":{\"45\":2}}],[\"csapp\",{\"1\":{\"10\":1}}],[\"cerr\",{\"1\":{\"40\":2}}],[\"cvref\",{\"1\":{\"45\":1}}],[\"cv\",{\"1\":{\"35\":8,\"45\":1}}],[\"ctor\",{\"1\":{\"28\":1,\"45\":1}}],[\"current\",{\"1\":{\"15\":1,\"40\":3}}],[\"clock\",{\"1\":{\"15\":5}}],[\"class\",{\"1\":{\"12\":1,\"13\":1,\"18\":1,\"23\":2,\"25\":2,\"29\":6,\"30\":1,\"36\":1,\"45\":2,\"46\":1,\"48\":4,\"49\":4}}],[\"clang\",{\"1\":{\"0\":1}}],[\"check\",{\"1\":{\"27\":1}}],[\"chrono\",{\"1\":{\"15\":10,\"24\":1,\"35\":3,\"38\":1,\"40\":1}}],[\"char>tuple\",{\"1\":{\"49\":1}}],[\"char\",{\"1\":{\"14\":5}}],[\"chunk\",{\"1\":{\"10\":5,\"39\":2}}],[\"c\",{\"1\":{\"14\":1,\"49\":1}}],[\"cref\",{\"1\":{\"14\":3,\"23\":2}}],[\"cast<\",{\"1\":{\"45\":1}}],[\"cast[e,zt(t,{fields:["h","t","c"],storeFields:["h","t","c"]})]));self.onmessage=({data:{type:e="all",query:t,locale:s,options:n,id:o}})=>{const u=bt[s];e==="suggest"?self.postMessage([e,o,tt(t,u,n)]):e==="search"?self.postMessage([e,o,Z(t,u,n)]):self.postMessage({suggestions:[e,o,tt(t,u,n)],results:[e,o,Z(t,u,n)]})}; +//# sourceMappingURL=index.js.map