本文整理自:https://www.cnblogs.com/lidabo/p/7852033.html
1. C++中的并发与多线程
C++标准并没有提供对多进程并发的原生支持,所以C++的多进程并发要靠其他API——这需要依赖相关平台。
C++11 标准提供了一个新的线程库,内容包括了管理线程、保护共享数据、线程间的同步操作、低级原子操作等各种类。标准极大地提高了程序的可移植性,以前的多线程依赖于具体的平台,而现在有了统一的接口进行实现。
C++11 新标准中引入了几个头文件来支持多线程编程:
- < thread > :包含std::thread类以及std::this_thread命名空间。管理线程的函数和类在 中声明.
- < atomic > :包含std::atomic和std::atomic_flag类,以及一套C风格的原子类型和与C兼容的原子操作的函数。
- < mutex > :包含了与互斥量相关的类以及其他类型和函数
- < future > :包含两个Provider类(std::promise和std::package_task)和两个Future类(std::future和std::shared_future)以及相关的类型和函数。
- < condition_variable > :包含与条件变量相关的类,包括std::condition_variable和std::condition_variable_any。
知识链接:
C++11 并发之std::atomic
本文概要:
1、成员类型和成员函数。
2、std::thread 构造函数。
3、异步。
4、多线程传递参数。
5、join、detach。
6、获取CPU核心个数。
7、CPP原子变量与线程安全。
8、lambda与多线程。
9、时间等待相关问题。
10、线程功能拓展。
11、多线程可变参数。
12、线程交换。
13、线程移动。
std::thread 在 #include<thread> 头文件中声明,因此使用 std::thread 时需要包含 #include<thread> 头文件。
1、成员类型和成员函数。
成员类型:
- id
- Thread id (public member type ) id
- native_handle_type
- Native handle type (public member type )
成员函数:
- (constructor)
- Construct thread (public member function ) 构造函数
- (destructor)
- Thread destructor (public member function ) 析构函数
- operator=
- Move-assign thread (public member function ) 赋值重载
- get_id
- Get thread id (public member function ) 获取线程id
- joinable
- Check if joinable (public member function ) 判断线程是否可以加入等待
- join
- Join thread (public member function ) 加入等待
- detach
- Detach thread (public member function ) 分离线程
- swap
- Swap threads (public member function ) 线程交换
- native_handle
- Get native handle (public member function ) 获取线程句柄
- hardware_concurrency [static]
- Detect hardware concurrency (public static member function ) 检测硬件并发特性
Non-member overloads:
- swap (thread)
- Swap threads (function )
2、std::thread 构造函数。
如下表:
- default (1)
- thread() noexcept;
- initialization(2)
- template <class Fn, class... Args> explicit thread (Fn&& fn, Args&&... args);
- copy [deleted] (3)
- thread (const thread&) = delete;
- move [4]
- hread (thread&& x) noexcept;
(1).默认构造函数,创建一个空的 thread 执行对象。
(2).初始化构造函数,创建一个 thread 对象,该 thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
(3).拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
(4).move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached。
std::thread 各种构造函数例子如下:
#include<iostream> #include<thread> #include<chrono> using namespace std; void fun1(int n) //初始化构造函数 { cout << "Thread " << n << " executing\n"; n += 10; this_thread::sleep_for(chrono::milliseconds(10)); } void fun2(int & n) //拷贝构造函数 { cout << "Thread " << n << " executing\n"; n += 20; this_thread::sleep_for(chrono::milliseconds(10)); } int main() { int n = 0; thread t1; //t1不是一个thread thread t2(fun1, n + 1); //按照值传递 t2.join(); cout << "n=" << n << '\n'; n = 10; thread t3(fun2, ref(n)); //引用 thread t4(move(t3)); //t4执行t3,t3不是thread t4.join(); cout << "n=" << n << '\n'; return 0; }
输出结果:
Thread 1 executing
n=0
Thread 10 executing
n=30
3、异步。
例如:
1 #include<iostream> 2 #include<thread> 3 using namespace std; 4 void show() 5 { 6 cout << "hello cplusplus!" << endl; 7 } 8 int main() 9 { 10 //栈上 11 thread t1(show); //根据函数初始化执行 12 thread t2(show); 13 thread t3(show); 14 //线程数组 15 thread th[3]{thread(show), thread(show), thread(show)}; 16 //堆上 17 thread *pt1(new thread(show)); 18 thread *pt2(new thread(show)); 19 thread *pt3(new thread(show)); 20 //线程指针数组 21 thread *pth(new thread[3]{thread(show), thread(show), thread(show)}); 22 return 0; 23 }
4、多线程传递参数。
例如:
#include<iostream> #include<thread> using namespace std; void show(const char *str, const int id) { cout << "线程 " << id + 1 << " :" << str << endl; } int main() { thread t1(show, "hello cplusplus!", 0); thread t2(show, "你好,C++!", 1); thread t3(show, "hello!", 2); return 0; }
发现,线程 t1、t2、t3 都执行成功!
5、join、detach。
join例子如下:
1 #include<iostream> 2 #include<thread> 3 #include<array> 4 using namespace std; 5 void show() 6 { 7 cout << "hello cplusplus!" << endl; 8 } 9 int main() 10 { 11 array<thread, 3> threads = { thread(show), thread(show), thread(show) }; 12 for (int i = 0; i < 3; i++) 13 { 14 cout << threads[i].joinable() << endl;//判断线程是否可以join 15 threads[i].join();//主线程等待当前线程执行完成再退出 16 } 17 return 0; 18 }
输出结果:
hello cplusplus!
hello cplusplus!
hello cplusplus!
1
1
1
总结:
join 是让当前主线程等待所有的子线程执行完,才能退出。
detach例子如下:
1 #include<iostream> 2 #include<thread> 3 using namespace std; 4 void show() 5 { 6 cout << "hello cplusplus!" << endl; 7 } 8 int main() 9 { 10 thread th(show); 11 //th.join(); 12 th.detach();//脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。 13 //detach以后,子线程会成为孤儿线程,线程之间将无法通信。 14 cout << th.joinable() << endl; 15 return 0; 16 }
输出结果:
hello cplusplus!0
结论:
线程 detach 脱离主线程的绑定,主线程挂了,子线程不报错,子线程执行完自动退出。
线程 detach以后,子线程会成为孤儿线程,线程之间将无法通信。
6、获取CPU核心个数。
例如:
#include<iostream> #include<thread> using namespace std; int main() { auto n = thread::hardware_concurrency();//获取cpu核心个数 cout << n << endl; return 0; }
结论:
通过 thread::hardware_concurrency() 获取 CPU 核心的个数。
7、CPP原子变量与线程安全。
问题例如:
#include<iostream> #include<thread> using namespace std; const int N = 100000000; int num = 0; void run() { for (int i = 0; i < N; i++) { num++; } } int main() { clock_t start = clock(); thread t1(run); thread t2(run); t1.join(); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用时 " << end - start << " ms" << endl; return 0; }
从上述代码执行的结果,发现结果并不是我们预计的200000000,这是由于线程之间发生冲突,从而导致结果不正确。
为了解决此问题,有以下方法:
(1)互斥量。
例如:
#include<iostream> #include<thread> #include<mutex> using namespace std; const int N = 100000000; int num(0); mutex m; void run() { for (int i = 0; i < N; i++) { m.lock(); num++; m.unlock(); } } int main() { clock_t start = clock(); thread t1(run); thread t2(run); t1.join(); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用时 " << end - start << " ms" << endl; return 0; }
不难发现,通过互斥量后运算结果正确,但是计算速度很慢,原因主要是互斥量加解锁需要时间。
互斥量详细内容 请参考
C++11 并发之std::mutex。
(2)原子变量。
例如:
#include<iostream> #include<thread> #include<atomic> using namespace std; const int N = 100000000; atomic_int num{ 0 };//不会发生线程冲突,线程安全 void run() { for (int i = 0; i < N; i++) { num++; } } int main() { clock_t start = clock(); thread t1(run); thread t2(run); t1.join(); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用时 " << end - start << " ms" << endl; return 0; }
不难发现,通过原子变量后运算结果正确,计算速度一般。
原子变量详细内容 请参考C++11 并发之std::atomic。
(3)加入 join 。
例如:
#include<iostream> #include<thread> using namespace std; const int N = 100000000; int num = 0; void run() { for (int i = 0; i < N; i++) { num++; } } int main() { clock_t start = clock(); thread t1(run); t1.join(); thread t2(run); t2.join(); clock_t end = clock(); cout << "num=" << num << ",用时 " << end - start << " ms" << endl; return 0; }
输出结果:
num=200000000,用时 431 ms
不难发现,通过原子变量后运算结果正确,计算速度也很理想。
8、lambda与多线程。
例如:
#include<iostream> #include<thread> using namespace std; int main() { auto fun = [](const char *str) {cout << str << endl; }; thread t1(fun, "hello world!"); thread t2(fun, "hello beijing!"); return 0; }
9、时间等待相关问题。
例如:
#include<iostream> #include<thread> #include<chrono> using namespace std; int main() { thread th1([]() { //让线程等待3秒 this_thread::sleep_for(chrono::seconds(3)); //让cpu执行其他空闲的线程 this_thread::yield(); //线程id cout << this_thread::get_id() << endl; }); return 0; }
10、线程功能拓展。
例如:
#include<iostream> #include<thread> using namespace std; class MyThread :public thread //继承thread { public: //子类MyThread()继承thread()的构造函数 MyThread() : thread() { } //MyThread()初始化构造函数 template<typename T, typename...Args> MyThread(T&&func, Args&&...args) : thread(forward<T>(func), forward<Args>(args)...) { } void showcmd(const char *str) //运行system { system(str); } }; int main() { MyThread th1([]() { cout << "hello" << endl; }); th1.showcmd("calc"); //运行calc //lambda MyThread th2([](const char * str) { cout << "hello" << str << endl; }, " this is MyThread"); th2.showcmd("notepad");//运行notepad return 0; }
11、多线程可变参数。
例如:
#include<iostream> #include<thread> #include<cstdarg> using namespace std; int show(const char *fun, ...) { va_list ap;//指针 va_start(ap, fun);//开始 vprintf(fun, ap);//调用 va_end(ap); return 0; } int main() { thread t1(show, "%s %d %c %f", "hello world!", 100, 'A', 3.14159); return 0; }
12、线程交换。
例如:
#include<iostream> #include<thread> using namespace std; int main() { thread t1([]() { cout << "thread1" << endl; }); thread t2([]() { cout << "thread2" << endl; }); cout << "thread1' id is " << t1.get_id() << endl; cout << "thread2' id is " << t2.get_id() << endl; cout << "swap after:" << endl; swap(t1, t2);//线程交换 cout << "thread1' id is " << t1.get_id() << endl; cout << "thread2' id is " << t2.get_id() << endl; return 0; }
两个线程通过 swap 进行交换。
13、线程移动。
例如:
#include<iostream> #include<thread> using namespace std; int main() { thread t1([]() { cout << "thread1" << endl; }); cout << "thread1' id is " << t1.get_id() << endl; thread t2 = move(t1);; cout << "thread2' id is " << t2.get_id() << endl; return 0; }