C++多线程应用
一个进程就是一个程序,一个程序里不止一个功能,每个功能的实现就可以交给一个线程去完成。一个进程就像是一个工程,这个工程里,有设计,有监理,有施工,就相当于三个线程,各干各的又相互配合。
https://cplusplus.com/reference/thread/thread/thread/
是C++的官方参考,个人觉得比较权威,比经过一些人去了解更好。每个人的思维方式不一样,直接看原始文档是最好的,何必吃别人嚼过的饭呢。
下面是我的心得,也算个参考吧。
一、创建一个线程大致有两种方法,一是先定义一个空线程,然后再实例化,二是直接实例化一个线程,直接干活。我个人倾向第二种,感觉第一种没什么意义。
二、对于join的理解。我的理解是:一个线程就是一个人,给这个人派任务后,有两种状态,一个是需要他回复,另一种是不需要。需要回复的,就用join,不需要回复的,就用detach。还拿设计,监理,施工,三人举例。施工前需要设计好才能开始施工,设计线程需要join,施工和监理是同时进行的,不能等施工完了,监理才检查,所以施工尤其不能join,只能detach,监理就可以马上开始了,监理作为最后一个工序可以join,也可以detach
三、joinable,就是表示这个线程的状态,一个线程,既有任务,又需要回复,就是joinable()== true,其余状态都是joinable()== false
四、当joinable()== true时,才对线程进行join或detach操作。否则不要进行这两种操作。即if(th.joinable()){th.join();或th.detach();}
五、std:🧵:hardware_concurrency()是CPU的核心数,按理说是一个核心对应一个线程。但是一个线程在执行完任务后就自动销毁了,CPU核心就空出来了。让线程等待新任务的话,也行,但我在实际运行过程中,会报错,也许是我学艺不精。我就简单粗暴,不断创建新线程。
六、线程池的概念我也不知道是谁发明的,就是先创建几个空线程,没事就等着,有事就干,干完也不消毁,继续等,说是能节约时间,我是觉得没什么用,而且官方参考里并没有这个说法。
上代码,用注释说明。注释是关键哦。
//此程序用多线程去调查学生的情况并记录。
#include <atomic> // 自锁型变量,防止多线程交叉操作,只能一个一个来,本例子并未使用,好像不支持string。
#include <thread> // 想用多线程,就要包含此包
#include <vector> // 使用多线程的辅助工具,任意类型的队列
#include <mutex> // 防止线程交叉操作同一代码用的,线程锁
mutex m; //定义一个线程锁
string a=“”; //用于记录的字符串
std::vector<std::thread> threaders;//创建一个线程队列,没有上限
//次子程序,用于获取学生的属性并记录,参数是学号,用很多线程执行这个子程序,一个线程调查一个学生,调查完不需要回复。这里假设,获取属性是个费时的工作,记录是个很快的动作,四个get_XXX()是预先写好的其他子程序。
void get_students_status(int student_number) //学号是学生的唯一标识
{
string name; //学生的姓名
string age; //学生的年龄
string height; //学生的身高
string weight; //学生的体重
name = get_name(student_number);
age = get_age(student_number);
height = get_height(student_number);
weight = get_weight(student_number);
//前面是调查的工作,可以多线程同步进行。后面是记录的工作,不能多线程同步进行,必须一个一个来,就需要加锁。这会让程序整体变慢,但没办法,记录是配套的,否则就乱了,比如名字后面应该是年龄,如果不加锁,可能会是另一个人的名字,
m.lock(); //加锁
a = a + name+": "+age +" "+height+" "+weight +"||";
m.unlock(); //解锁
}
main()
{
//对一万个学生进行调查并记录
for(int i = 1;i<10000;i++)
{
threaders.push_back(std::thread(get_students_status,i)); //针对每个学号创建一个线程,std::thread是创建线程的语句,其第一个参数是线程执行的子程序,第二个参数是该子程序的第一个参数,以此类推。threaders.push_back是把这个新创建的线程放在线程队列的队尾。
//遍历线程队列里的每个线程
for (auto& th : threaders)
{
if(th.joinable())th.detach();//如果此线程处于有任务,且需要回复的状态,就告诉他不用回复了。此处用join,就是等待其回复。那样就失去了多线程的意义,一个一个干,跟一个人干,没什么区别。
}
}
m.lock(); //加锁
cout<<a<<endl;//有可能还有线程没完成就输出了,可以想别的办法,比如前面加sleep(),或加锁。主线程也是线程啊。也可以在前面遍历线程那步,把最后一个线程join
m.unlock(); //解锁
}
后记,某些从服务器获取资源的操作,各线程之间会有冲突,虽然可以用锁解决,但是没什么意义,不如就在主线程里依次完成。多线程更适用于需要独立且量大的计算或问题处理。