转载
一. 多线程并发
1. 与 C++11 多线程相关的头文件
C++11
语言本身支持多线程,和平台无关;c++11
新标准中引入了四个头文件来支持多线程编程,他们分别是<atomic>
, <thread>
, <mutex>
, <condition_variable>
和<future>
;<atomic>
:该头文主要声明了两个类, ·std::atomic· 和 ·std::atomic_flag·,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。<thread>
:该头文件主要声明了 std::thread
类,另外 std::this_thread
命名空间也在该头文件中。<mutex>
:该头文件主要声明了与互斥量(mutex)
相关的类,包括 std::mutex
系列类,std::lock_guard
, std::unique_lock
, 以及其他的类型和函数。<condition_variable>
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable
和 std::condition_variable_any
。<future>
:该头文件主要声明了 std::promise, std::package_task
两个 Provider
类,以及 std::future
和 std::shared_future
两个 Future
类,另外还有一些与之相关的类型和函数,std::async()
函数就声明在此头文件中。- 和有些语言中定义的线程不同,C++11 所定义的线程是和操作系的线程是一一对应的,也就是说我们生成的线程都是直接接受操作系统的调度的,通过操作系统的相关命令(比如 ps -M 命令)是可以看到的,一个进程所能创建的线程数目以及一个操作系统所能创建的总的线程数目等都由运行时操作系统限定。
thread
类是一个特殊的类,它不能被拷贝,只能被转移或者互换- 线程的转移使用
move
函数,如thread t2 = move(t);
thread t2 = move(t); // 改为 t2 = t 将不能编译。
t2.join();
- 如果将 t2.join() 改为 t.join() 将会导致整个进程被结束,因为忘记了调用 t2 也就是被转移的线程的 join() 方法,从而导致整个进程被结束,而 t 则因为已经被转移,其 id 已被置空。
- 线程实例互换使用
swap
函数,如线程实例互换使用 swap 函数
。 - 在进行线程实例转移的时候,要注意判断目的实例的
id
是否为空值。 std::this_thread::sleep_for(chrono::milliseconds(10))
:表示当前线程休眠一段时间(10ms),休眠期间不与其他线程竞争 CPU,根据线程需求,等待若干时间。需要引入头文件#include <chrono>
。
2. join() 函数与 detach() 函数
join()
函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束;detach()
函数称为分离线程函数,使用detach()
函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束。- 用
join()
函数,主线程就会等待子线程运行结束,然后主线程再运行,直到结束,传统编程就是如此; detach()
函数,detach
即分离的意思,一旦线程detach
,也就是说将子线程交给系统托管,与进程,主线程无关了,这种情况下,就很可能主线程结束,子线程还在运行,所以使用detach()
就引发出问题,编程的难度也加大了。- 线程
detach
以后,子线程会成为孤儿线程,线程之间将无法通信。 auto n = thread::hardware_concurrency();//获取cpu核心个数
3. mutex 互斥量
- 引入头文件
#include <mutex>
- 定义两个全局变量
int num(0);
和mutex m;
- 在线程中要对
num
操作前后,分别对m
加锁和解锁。
void run() {
m.lock();
num++;
m.unlock();
}
-
- 通过互斥量后运算结果正确,但是计算速度很慢,原因主要是互斥量加解锁需要时间。
#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;
}
4. 原子变量
- 引入头文件
#include <atomic>
- 定义一个全局的原子变量:
atomic_int num{0};
则不会发生线程冲突,即线程安全。
void run() {
for(int i = 0; i < 8; i++)
num++;
}
#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;
}
5. 使用 join() 函数
- 定义全局变量
int num = 0;
- 在
mian()
函数中对线程t1
使用join()
函数
int main() {
...
thread t1(run);
t1.join();
...
return 0;
}
6. 时间等待相关问题
#include<iostream>
#include<thread>
#include<chrono>
using namespace std;
int main()
{
thread th1([]()
{
this_thread::sleep_for(chrono::seconds(3));
this_thread::yield();
cout << this_thread::get_id() << endl;
});
return 0;
}