C++11的线程库(一)
来源:互联网 发布:人工智能伏羲觉醒豆瓣 编辑:程序博客网 时间:2024/05/21 19:37
[TOC]
个人认为C++11最明智的举动之一就是加入了线程库,在多核CPU越来越普及,在多线程编程日益,我们可以说是”疯狂“的时代,一个不支持多线程(我是指本身不支持,在编程过程中需要依赖线程库)的语言,如何能普遍适用?
在C++11之前在C/C++中使用多线程编程并非鲜见,这样的代码主要是使用POSIX线程Pthread和OpenMP编译器指令两种编程模型来完成程序的线程化。其中POSIX线程是POSIX标准中关于线程的部分,程序员可以通过一些Pthread线程的API来完成线程的创建、数据的共享、同步等功能。Pthread主要用于C语言,在类UNIX系统上,如FreeBSD、NetBSD、OpenBSD、GNU/Linux、Mac OS X,甚至在Windows上也都有实现,不过Windows上Pthread的实现并非”原生“,主要还是包装为Windows的线程库。不过在使用的便利性上,Pthread不如后来者OpenMP。
原子操作与C++11原子类型
// 原子类型.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <thread>#include <atomic>using namespace std;atomic_llong total{ 0 };void func(int){ for (long long i = 0; i < 100000000LL; ++i){ total += i; }}int _tmain(int argc, _TCHAR* argv[]){ thread t1(func, 0); thread t2(func, 0); t1.join(); t2.join(); cout << total << endl; system("pause"); return 0;}
相比于基于C语言以及过程编程的pthread“原子操作API”而言,C++11对于“原子操作”概念的抽象遵从了面向对象的思想——C++11标准定义的都是所谓的“原子类型”。编译器可以保证原子类型在线程间被互斥的访问,这样设计从并行编程的角度看,是由于需要同步的总是数据而不是代码,因此C++11对数据进行了抽象,会有利于产生行为更为良好的并行代码。而进一步地,一些琐碎的概念,比如互斥锁、临界区则可被C++11的抽象所掩盖,因此并行代码的编写也会变得更加简单。我们可以在看到内置类型的原子类型的定义:
atomic_bool abool; //对应bool atomic_char achar; //char atomic_schar aschar; //signed char atomic_uchar auchar; //unsigned char atomic_int aint; //int atomic_uint auint; //unsigned int atomic_short ashort; //short atomic_ushort aushort; //unsigned short atomic_long along; //long atomic_ulong aulong; //unsigned long atomic_llong allong; //long long atomic_ullong aullong; //unsigned long long atomic_char16_t achar16_t; //char16_t atomic_char32_t achar32_t; //char32_t; atomic_wchar_t awchar_t; //wchar_t
不过更为普遍的应该是使用atomic类模板。通过该模板,可以定义出任意需要的原子类型:
std::atomic t;
如上,声明可一个类型为T的原子类型变量t。编译器会保证产生并行情况下行为良好的代码,以避免线程之间对于数据t的竞争。对于线程而言,原子类型通常属于“资源型”的数据,这意味着多个线程通常只能访问的原子类型的拷贝。因此在C++11中,原子类型只能从其模板参数类型中进行构造,标准不允许原子类型经行拷贝构造、移动构造,以及operator=等,防止以外发生如下面:
atomic<float> af{ 1.2f }; //atomic<float> af1{ af }; //这里无法编译
从上面可以看到,af1{ af }的构造方式在C++11中是不允许的,我们可以通过以前的经验轻松知道如何在类的代码中禁止这些行为,事实上,atomic模板类的拷贝构造函数、移动构造函数、operator=等总是默认被删除的。不过从atomic类型的变量来构造其他模板参数类型T的变量则是可以的,比如:
atomic<float> af{ 1.2f }; //atomic<float> af1{ af }; //这里无法编译 //下面的都是正确的 float af2 = af; vector<float> vfl{ af }; stack<float> sfl; sfl.push(af);
这是由于atomic类模板总是定义了从atomic到T的类型转换函数的缘故,在需要的时候,编译器会隐式地完成完成原子类型到其对应的类型的转化。能够实现在线程间保持原子性的原因是编译器能够保证针对原子类型的操作都是原子操作。正如之前所说,原子操作都是平台相关的,因此有必要为常见的原子操作进行抽象,定义统一的结构,并根据编译选项,并根据编译选项(或环境)产生其平台相关的实现。在C++11中,标准将原子操作定义为atomic模板类的成员函数,这囊括了绝大多数典型的操作,如读、写、交换等,当然,对于内置类型而言,主要是通过重载一些全局操作符来完成的,在编译的时候,会产生一条特殊的lock前缀的x86指令,lock能够控制总线及实现x86平台上的原子性。下面是atomic类型及相关的操作:
这里的atomic-integral-type和integraltype指的是前面提到的所有的原子类型的整型,而class-type则是指自定义类型。可以看到,对于大多数的原子类型而言,都可以执行读(load)、写(store)、交换(exchange)、比较并交换(compare_exchange_weak/compare_exchange_stronge)等操作。通常情况下,这些原子操作已经足够使用了。如下:
atomic<int> a; a = 1; //a.store(1); int b = a; //b = a.load();
这里的赋值语句b=a其实就等价b=a.load()。同样,a=1也相当于a.store(1),由于这些操作都是原子的,所以原来的从操作也是原子的,从而避免了线程间关于a的竞争。由于封装内部的实现和平台相关,所以,一般来说这些接口都封装了平台上最高性能的实现。
这里首先要注意的是atomic_flag,这个类型和其他不同,atomic_flag是无锁的(lock-free),级线程对其访问不需要加锁,因此对于atomic_flag而言,也就不需要使用load、store等成员函数进行读写,但是可以通过atomic_flag的成员test_and_set以及clear实现一个自旋锁(spinlock):
// 自旋锁实现.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <atomic>#include <thread>#include <Windows.h>using namespace std;std::atomic_flag lock = ATOMIC_FLAG_INIT; //声明了全局变量,初始化为值ATOMIC_FLAG_INIT,即false状态void func(int n){ while (lock.test_and_set(std::memory_order_acquire)) //尝试获得自旋锁 { cout << "Waiting from thread : " << n << endl; //自旋 } cout << "Thread " << n << " starts working" << endl;}void foo(int n){ cout << " Thread " << n << " is going to start" << endl; lock.clear(); cout << " Thread " << n << " starting working" << endl;}int _tmain(int argc, _TCHAR* argv[]){ lock.test_and_set(); thread t1(func, 1); thread t2(foo, 2); t1.join(); Sleep(100); t2.join(); Sleep(100); system("pause"); return 0;}
在上面的程序,我们先是申明了一个全局变量的atomic_flag变量lock,并且初始化为ATOMIC_FLAG_INIT,也就是false的状态。在线程t1中,通过不停地通过lock的成员test_and_set()来设置lock为true。这里的test_and_set是一种原子操作,用于在一个内存空间原子地写入新植并返回旧值。因此test_and_set会返回之前的lock值。由于31行中设置了lock的值为true,所以线程t1会一直自旋在while循环中,知道线程t2将该设置位通过成员函数clear设置为false,此时线程t1自旋结束,开始后面的代码。当然还可以将lock封装为锁操作,比如:
void Lock(atomic_flag *lock) { while (lock->test_and_set()); }void Ublock(atomic_flag *lock) { lock->clear(); }
这样一来,就可以想以前那样互斥的访问临界区了。除此之外,很多时候,了解底层的程序员会考虑使用无锁编程,以最大限度地挖掘并行编程的性能,而C++11的无锁机制为这样的实现提供了高级语言的支持。事实上,C++11中,原子操作还可以包含一个参数:memory_order,使用该参数能够进一步释放并行的潜在的性能。
如果只是简单地想在线程间进行数据的同步的话,上面说到的原子类型已经为程序员提供了一些同步保障。不过这样做的线程安全是基于一种假设,即所谓的顺序一致性(sequential consistent)的内存模型(memory model)。如下:
// 内存模型顺序一致性.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <thread>#include <atomic>using namespace std;atomic<int> a{ 0 };atomic<int> b{ 0 };void ValueSet(int){ int t = 1; a = t; b = 2;}void Observer(int){ cout << "(" << a << "," << b << ")" << endl; //可能有多少输出}int _tmain(int argc, _TCHAR* argv[]){ thread t1(ValueSet, 0); thread t2(Observer, 0); t1.join(); t2.join(); cout << "Got (" << a << "," << b << ")" << endl; //Got(1,2) getchar(); //pause return 0;}
在上面的代码中,我们创建了两个线程t1和t2,分别执行ValueSet和Observer函数。在ValueSet中,为a和b的值分别是1和2,而在Observer中,只打印出a和b的值。其实由于两个线程的中关于a和b顺序的不确定性,因此,Observer输出的结果可能是多种可能性,如下所示:
在本例中Observer只是试图一窥线程ValueSet的执行状况,不过在 本例中Observer的窥探对于最终结果并不是必须的,因此赋值语句的执行顺序,也就是ValueSet的执行时间顺序并不会对最后的结果有什么影响。如果编译器认定a、b的赋值语句的执行先后顺序对输出结果有任何影响的话,则可以依情况将指令重排列(reorder)以提高性能,如果a、b赋值语句的执行顺序必须是先a后b,则编译器不会执行这样的优化。如果原子操作的先后顺序置之不理,很可能发生错误:
// 线程顺序.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <thread>#include <atomic>using namespace std;atomic<int> a;atomic<int> b;void Thread1(int){ int t = 1; a = t; b = 2;}void Thread2(int){ while (b != 2); //自旋等待 cout << a << endl; //总是期待a的值为1}int _tmain(int argc, _TCHAR* argv[]){ thread t1(Thread1, 0); thread t2(Thread2, 0); t1.join(); t2.join(); system("pause"); return 0;}
在默认情况下,C++11中原子类型的变量在线程中总是保持着顺序执行的特点(非原子类型则没有必要,因为不需要在线程之间进行同步),我们称这样的特性为顺序一致的,即代码在线程中运行的顺序与程序员看到的代码顺序一致。事实上,顺序一致性只是C++11中多种内存模型中的一种,而在C++11中,并不是只支持顺序一致单个内存模型的原子变量,以为顺序一致性往往意味着最低效的同步方式,C++11提供了更高效的原子类型变量同步方式。
内存模型通常是一个硬件上的概念,表示的是机器指令是以什么样的顺序被处理器执行的。内存模型分为强顺序和弱顺序。多线程总是共享代码的,那么强顺序意味着:对于多个线程而言,其看到的指令执行顺序是一致的。具体而言,对于共享内存的处理器而言,需要看到内存中的数据被改变的顺序与机器指令中的一致,反之,如果线程间看到的内存数据被改变的顺序与机器指令中声明的不一致的话,则是弱顺序。在现实实现中采用强顺序的内存模型的平台,对于任何一个线程而言,其看到的原子操作都是顺序。而采用弱顺序内存模型的平台,比如PowerPC、Alpha、Itanlium、ArmV7,如果想要保证指令执行的顺序,通常要在汇编中加入一条所谓的内存栅栏(memory barrier)指令。为什么会有弱顺序内存模型?简单地说,弱顺序内存模型可以使得处理器进一步发掘指令中的并行性,使得指令执行的性能高效。
上面都是硬件上的一些可能的内存模型的描述。而C++11中定义的内存模型和顺序一致性跟硬件的内存模型的强顺序、弱顺序之间其实没有什么太强的关系。事实上,在高级语言与机器指令之间还有一层隔离,这层隔离是由编译器来完成的。编译器处于代码优化的考虑,会将指令前后移动,从而获得最佳的机器指令的排列以及产生最佳的运行时性能。究其C++11而言,要保证代码的顺序一致性,就必须同时做到:
编译器保证原子操作的指令间顺序不变,即保证产生的读写原子类型的变量的机器指令与代码编写者可拿到的是一致的。
处理器对原子操作的汇编指令的执行顺序不变。这对于x86这样的强顺序的体系结构而言没有任何问题;对于弱顺序的体系结构而言则需要编译器在每次原子操作后加入内存栅栏。
那么,C++11中,对于原子类型的成员函数(原子操作)总是保证了顺序一致性。这对于x86这些平台只需要禁止编译器对原子类型变量间的重排序优化。而对于PowerPC这样的平台来说,不仅禁止了编译器的优化,还加入了大量的内存栅栏。在C++11中总共定义了7中memory_order的枚举值:
memory_order_seq_cst表示该原子操作必须是顺序一致的,这是C++11中所有atomic原子操作的默认值,不带memory_order参数的原子操作就是使用该值。而memory_order_relaxed则表示该原子操作是松散的,可以被任意重排序的。通常我们可以把atomic成员函数可使用的memory_order的值分为以下3种:
原子存储操作(store)可以使用memory_order_relaxed、memory_order_release、memory_order_seq_cst。
原子读取操作(load)可以使用memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst。
RMW操作(read-modify-write),即一些需要同时读写的操作,比如之前提过的atomic_flag类型的test_and_set()操作。又比如atomic类模板的atomic_compare_exchange()操作等都是同时需要读写的。RMW操作可以使用memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst。
一些形如“operator =”、“operator +=”的函数,事实上都是memory_order_seq_cst作为memory_order参数的原子操作的简单封装。这些操作都是采用顺序一致性的内存模型。如果要指定内存顺序的话,应该使用load、atomic_fetch_add这样的版本。
// 内存模型_memory_order.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <thread>#include <atomic>using namespace std;atomic<int> a;atomic<int> b;int Thread1(int){ int t = 1; a.store(t, memory_order_relaxed); b.store(2, memory_order_relaxed); return 0;}int Thread2(int){ while (b.load(memory_order_relaxed) != 2); //自旋等待 cout << a.load(memory_order_relaxed) << endl; return 0;}int _tmain(int argc, _TCHAR* argv[]){ thread t1(Thread1, 0); thread t2(Thread2, 0); t1.join(); t2.join(); system("pause"); return 0;}
上面的代码采用了memory_order_relaxed最为memory_order参数。那么排除顺序一致和松散两种方式,如何保证程序既快又对的运行呢?仔细分析上面的程序,发现只需要保证a.store先于b.store发生,b.load先于a.load发生就行。下面我们使用其他的memory_order实现这种原子操作:
// 内存模型_memory_order_1.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <atomic>#include <thread>using namespace std;atomic<int> a;atomic<int> b;int Thread1(int){ int t = 1; a.store(t, memory_order_relaxed); //不对执行顺序做任何保证 b.store(2, memory_order_release); //本原子操作前多有的写原子操作必须完成 return 0;}int Thread2(int){ while (b.load(memory_order_acquire) != 2); //本原子操作必须完成才能执行之后的所有读原子操作 cout << a.load(memory_order_relaxed) << endl; //不对执行顺序做任何保证 return 0;}int _tmain(int argc, _TCHAR* argv[]){ thread t1(Thread1, 0); thread t2(Thread2, 0); t1.join(); t2.join(); system("pause"); return 0;}
上面的代码中我们只是确定了代码执行的部分顺序。
线程局部存储
线程局部存储(TLS,thread local storage)是一个已经有的概念。所谓的局部存储变量就是拥有线程生命期及线程可见性的变量。C++11只是对TLS做了语法上的统一,而对其实现并没有做任何性能上的规定:
thread_local int rage=1;
从这里https://msdn.microsoft.com/en-us/library/hh567368.aspx#featurelist,可以看到vs并不完全支持。从cppreference(http://en.cppreference.com/w/cpp/language/storage_duration) 上面可以看到一些简单的例子:
#include <iostream>#include <string>#include <thread>#include <mutex>thread_local unsigned int rage = 1; std::mutex cout_mutex;void increase_rage(const std::string& thread_name){ ++rage; std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';}int main(){ std::thread a(increase_rage, "a"), b(increase_rage, "b"); { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "Rage counter for main: " << rage << '\n'; } a.join(); b.join(); return 0;}
可以得到输出:
Rage counter for main: 1Rage counter for a: 2Rage counter for b: 2
上面的代码中使用了thread_local的变量限定,使得变量rage只在每个线程中初始化,并且生命周期与属于线程相同,并且在线程中的操作并不会影响到其他线程中的值,我们可以认为这个变量是有了“归属”。
thread的成员变量
为了更好的使用thread,我们在下面讨论下其成员变量:
// thread C++11 header#pragma once#ifndef _THREAD_#define _THREAD_#ifndef RC_INVOKED #ifdef _M_CEE #error <thread> is not supported when compiling with /clr or /clr:pure. #endif /* _M_CEE */#include <exception>#include <iosfwd>#include <functional>#include <chrono> #include <concrt.h>#include <thr/xthread>#include <thr/xtimec.h> #pragma pack(push,_CRT_PACKING) #pragma warning(push,3) #pragma push_macro("new") #undef new #pragma warning(disable: 4521 4522 4800)#define __STDCPP_THR__ 1 /* nonzero if threads supported */_STD_BEGINclass thread { // class for observing and managing threadspublic: class id; typedef void *native_handle_type; thread() _NOEXCEPT { // construct with no thread _Thr_set_null(_Thr); } template<class _Fn, class... _Args> explicit thread(_Fn&& _Fx, _Args&&... _Ax) { // construct with _Fx(_Ax...) _Launch(&_Thr, _STD bind(_Decay_copy(_STD forward<_Fn>(_Fx)), _Decay_copy(_STD forward<_Args>(_Ax))...)); } ~thread() _NOEXCEPT { // clean up if (joinable()) _XSTD terminate(); } thread(thread&& _Other) _NOEXCEPT : _Thr(_Other._Thr) { // move from _Other _Thr_set_null(_Other._Thr); } thread& operator=(thread&& _Other) _NOEXCEPT { // move from _Other return (_Move_thread(_Other)); } thread(const thread&) = delete; thread& operator=(const thread&) = delete; void swap(thread& _Other) _NOEXCEPT { // swap with _Other _STD swap(_Thr, _Other._Thr); } bool joinable() const _NOEXCEPT { // return true if this thread can be joined return (!_Thr_is_null(_Thr)); } void join(); void detach() { // detach thread if (!joinable()) _Throw_Cpp_error(_INVALID_ARGUMENT); _Thrd_detachX(_Thr); _Thr_set_null(_Thr); } id get_id() const _NOEXCEPT; static unsigned int hardware_concurrency() _NOEXCEPT { // return number of hardware thread contexts return (::Concurrency::details::_GetConcurrency()); } native_handle_type native_handle() { // return Win32 HANDLE as void * return (_Thr._Hnd); }private: thread& _Move_thread(thread& _Other) { // move from _Other if (joinable()) _XSTD terminate(); _Thr = _Other._Thr; _Thr_set_null(_Other._Thr); return (*this); } _Thrd_t _Thr; }; namespace this_thread {thread::id get_id() _NOEXCEPT;inline void yield() _NOEXCEPT { // give up balance of time slice if (::Concurrency::details::_CurrentScheduler::_Id() != -1) { // yield, then quit ::Concurrency::details::_Context::_Yield(); return; } _Thrd_yield(); }inline void sleep_until(const stdext::threads::xtime *_Abs_time) { // sleep until _Abs_time if (::Concurrency::details::_CurrentScheduler::_Id() != -1) { stdext::threads::xtime _Now; stdext::threads::xtime_get(&_Now, stdext::threads::TIME_UTC); ::Concurrency::wait(_Xtime_diff_to_millis2(_Abs_time, &_Now)); return; } _Thrd_sleep(_Abs_time); }template<class _Clock, class _Duration> inline void sleep_until( const chrono::time_point<_Clock, _Duration>& _Abs_time) { // sleep until time point stdext::threads::xtime _Tgt; _Tgt.sec = chrono::duration_cast<chrono::seconds>( _Abs_time.time_since_epoch()).count(); _Tgt.nsec = (long)chrono::duration_cast<chrono::nanoseconds>( _Abs_time.time_since_epoch() - chrono::seconds(_Tgt.sec)).count(); sleep_until(&_Tgt); }template<class _Rep, class _Period> inline void sleep_for(const chrono::duration<_Rep, _Period>& _Rel_time) { // sleep for duration stdext::threads::xtime _Tgt = _To_xtime(_Rel_time); sleep_until(&_Tgt); } } // namespace this_threadclass thread::id { // thread idpublic: id() _NOEXCEPT { // id for no thread _Thr_set_null(_Thr); } template<class _Ch, class _Tr> basic_ostream<_Ch, _Tr>& _To_text( basic_ostream<_Ch, _Tr>& _Str) { // insert representation into stream return (_Str << _Thr_val(_Thr)); } size_t hash() const { // hash bits to size_t value by pseudorandomizing transform return (_STD hash<size_t>()((size_t)_Thr_val(_Thr))); }private: id(const thread& _Thrd) : _Thr(_Thrd._Thr) { // construct from thread object } id(_Thrd_t _Thrd) : _Thr(_Thrd) { // construct from thread identifier } _Thrd_t _Thr; friend thread::id thread::get_id() const _NOEXCEPT; friend thread::id this_thread::get_id() _NOEXCEPT; friend bool operator==(thread::id _Left, thread::id _Right) _NOEXCEPT; friend bool operator<(thread::id _Left, thread::id _Right) _NOEXCEPT; };inline void thread::join() { // join thread if (!joinable()) _Throw_Cpp_error(_INVALID_ARGUMENT); if (_Thr_is_null(_Thr)) _Throw_Cpp_error(_INVALID_ARGUMENT); if (get_id() == _STD this_thread::get_id()) _Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR); if (_Thrd_join(_Thr, 0) != _Thrd_success) _Throw_Cpp_error(_NO_SUCH_PROCESS); _Thr_set_null(_Thr); }inline thread::id thread::get_id() const _NOEXCEPT { // return id for this thread return (id(*this)); }inline thread::id this_thread::get_id() _NOEXCEPT { // return id for current thread return (_Thrd_current()); }inline void swap(thread& _Left, thread& _Right) _NOEXCEPT { // swap _Left with _Right _Left.swap(_Right); }inline bool operator==(thread::id _Left, thread::id _Right) _NOEXCEPT { // return true if _Left and _Right identify the same thread return (_Thrd_equal(_Left._Thr, _Right._Thr)); }inline bool operator!=(thread::id _Left, thread::id _Right) _NOEXCEPT { // return true if _Left and _Right do not identify the same thread return (!(_Left == _Right)); }inline bool operator<(thread::id _Left, thread::id _Right) _NOEXCEPT { // return true if _Left precedes _Right return (_Thrd_lt(_Left._Thr, _Right._Thr)); }inline bool operator<=(thread::id _Left, thread::id _Right) _NOEXCEPT { // return true if _Left precedes or equals _Right return (!(_Right < _Left)); }inline bool operator>(thread::id _Left, thread::id _Right) _NOEXCEPT { // return true if _Left follows _Right return (_Right < _Left); }inline bool operator>=(thread::id _Left, thread::id _Right) _NOEXCEPT { // return true if _Left follows or equals _Right return (!(_Left < _Right)); }template<class _Ch, class _Tr> basic_ostream<_Ch, _Tr>& operator<<( basic_ostream<_Ch, _Tr>& _Str, thread::id _Id) { // insert id into stream return (_Id._To_text(_Str)); } // TEMPLATE STRUCT SPECIALIZATION hashtemplate<> struct hash<thread::id> : public unary_function<thread::id, size_t> { // hash functor for thread::id typedef thread::id _Kty; size_t operator()(const _Kty& _Keyval) const { // hash _Keyval to size_t value by pseudorandomizing transform return (_Keyval.hash()); } };_STD_END #pragma pop_macro("new") #pragma warning(pop) #pragma pack(pop)#endif /* RC_INVOKED */#endif /* _THREAD_ *//* * Copyright (c) 1992-2012 by P.J. Plauger. ALL RIGHTS RESERVED. * Consult your license regarding permissions and restrictions.V6.00:0009 */
上面是从vs2013中摘取的关于thread的部分源码。虽然不是完全支持整个C++11,但是毕竟好用一些。
首先是成员类型(Member types):
成员类
id : 用来描述线程的id,成员级别是public。
成员函数
非成员函数
std::swap(std::thread):专业的std::swap算法。
简单的用法如下:
#include <iostream>#include <thread>#include <mutex>#include <atomic>using namespace std;void func1(int a){ cout << "this is function 1" << endl; cout << " the input of function 1 is: " << a << endl;}void func2(int a){ cout << "this is function 2" << endl; cout << " the input of function 2 is: " << a << endl;}int main(){ cout << thread::hardware_concurrency() << endl; //返回实际支持的并发线程的数量 thread t1(func1, 0); thread t2(func2, 10); cout << t1.get_id() << endl; cout << t2.get_id() << endl; /*t1.join(); t2.join();*/ system("pause"); return 0;}
当然这么写程序是有问题的,多线程编程中,如果想要程序能够按照自己的设计的发展,就必须要使用互斥、锁等操作,使得整个程序的运行脉络更加清晰。下面分析mutex、atomic类等的成员以及使用。
- C++11的线程库(一)
- Linux C 线程(一)
- 关于线程(一)线程的基础
- 线程(一)-线程的创建
- Java的线程(一)
- 线程的学习(一)
- 线程概述——基于POSIX的C语言多线程基础知识(一)
- linux下C语言多线程(一)线程的创建与取消
- linux下C语言多线程(一)线程的创建与取消
- linux下C语言多线程(一)线程的创建与取消
- 一只简单的网络爬虫(基于linux C/C++)————线程相关
- 浅谈.Net(C#)中的线程同步(一)
- Linux C 线程同步实例分析(一)
- C#.NET中的线程同步类(一)
- c++11的多线程支持一(线程启动)
- C++封装POSIX 线程库(一)互斥锁的封装
- 线程(一)线程的锁与同步
- Java中的线程(一)-线程的定义与创建
- Mysql:在已有数据的情况下进行的主从复制
- 搜索栏UISearchBar和UISearchController
- 乐视视频播放器 v7.2.1.482 官方版下载
- java中blob大字段使用
- java中四种引用类型
- C++11的线程库(一)
- SAT语法考试答题要求
- 根据用指定的月份,打印该月所属的季节(if else)3,4,5 春季 6,7,8 为夏季 9,10,11 秋季 12,1,2为冬季
- 第13周 【项目1-动物这样叫】(2)
- Codeforces Round #305 (Div. 1) A && B
- 新GRE和旧GRE的区别?
- 第十三周阅读项目(2):虚析构函数
- block
- Json格式解析