多线程与多核
无论是手机还是pc,都存在着多核,多核cpu可以同时处理多种事情。
尤其是存在着线程可能会阻塞的情况下,使用多线程对于效率有很高的帮助,举个形象的例子,在烧开水的时候这段时间如果是单线程执行,明明在烧开水的时候可以完成其他事情,但是没有办法,单线程只能按照事情的顺序一件件取做。
目前网络上关于c++多线程相关的教程和讲解并不是很多,甚至在c++的绝大多数书籍中都没有涉及到相关知识的讲解。
因为对于多线程的使用一般都集中在java等更加高级的语言中,这些语言一般也会封装的很友好,像在java中,两个volatile和synchronized关键字就基本上解决了大多数的问题,开发者也不需要太过将精力集中在底层。但是c++之所以还没有被淘汰甚至在编程语言中占有重要的一个席位,就是因为c++一方面可以操控底层的寄存器等,另一方面,在对性能要求比较高的情况下,c++的效率是要高于java的,因为java虚拟机就是用c++来编写的。所以在一些比如服务器等场合,c++依然是主流。
从这段时间的学习来看,还没有一个权威的中文教程可以权威、准确的将c++多线程的知识全部解释清楚,甚至错误还有点多。当然我在学习的时候也会出现一些问题,所以在读者不明白的时候请参考官方c++网站。http://www.cplusplus.com/reference/atomic/atomic/
https://en.cppreference.com/w/cpp/atomic/atomic/
多线程的使用
#include <iostream>
#include <thread>
using namespace std;
void print_task(char n){
for(int i = 0; i < 500; i++){
cout << n ;
}
cout << endl;
}
int main(){
std::thread t1(print_task, '+'); // 线程t1执行print_task,参数为'+'
std::thread t2(print_task, '-');
t1.join(); // 必须使用join不然会core dump
t2.join();
return 0;
}
上图的例子中就是多线程的使用,在函数构建的时候没有什么区别,但是main中,调用了成员函数thread,生成了两个对象t1和t2,这两个线程将同时开启运行,且开启和运行的时间是不确定的。运行的结果如下:
从结果可以看出来,两个线程确实是符合多线程的特性,即穿插打印出了+和-。
在使用多线程的时候需要注意的点:
- 编译的时候需要加上-pthread参数,比如编译上图的test1命令为
g++ test1_simple_case_of_multi_threads.cc -o test1_simple_case_of_multi_threads -pthread
- 每一个开启的线程必须都要使用join释放,比如上述的例子,假如t2忘记释放了,就会core dump。这个与java有着显著的区别,首先java是不需要手动释放每一个线程的,而且java中join的作用是等上一个线程结束了再运行下一个线程。至于c++中join的作用到底是什么,暂时还不是很清楚,猜测是资源的释放。但不用,很有可能出错,这里可以看做一种使用的规范。
- 多线程需要加上头文件
#include <thread>
使用多线程会出现的问题
从上面的例子可以看出,多个线程的执行是没有规律的,可能是t1执行了一会儿之后立马切换到t2执行,这就会导致某些需要不被打断的操作出现问题,比如多个线程对一个全局变量进行++操作,假如线程1对变量a(0)进行++操作之后为1,但是线程2在获得变量a的值时,是线程1在执行++操作之前的值,那么线程2也对变量a(0)进行了++操作,结果也是为1,于是两个线程对a进行++操作之后,a的结果依然为1。这就与我们的预期不符合。
再举个例子,假如每个线程需要输出一段字符串,如下:
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex> // 需要添加mutex头文件
using namespace std;
mutex mtx;
void thread_task(int n){
for(int i = 0; i < 10000; i++){} // 假设此处为各个线程要执行的代码空间,此部分各个线程互不干扰
cout << "I am thread " << n << '\n'; // 但是该部分字符串是需要在终端上不被打断地输出的,假设此处为多个线程需要独立执行的部分
}
int main(){
thread threads[5]; // 创建一个线程数组
for(int i = 0; i < 5; i++){
threads[i] = thread(thread_task, i); // 用数组的方式创建线程
}
for(auto& t: threads){ // c++的循环方式,用于遍历数组中的元素
t.join();
}
return 0;
}
输出的内容对于人来说自然希望是完整的,但是现实的情况却是混乱的:
那么能不能有一种方法可以让多线程在运行的时候,针对某部分的内容加上锁,让该部分内容在运行的时候是不被打断的呢?于是多线程另外一个重要的概念出来了——mutex互斥量。