经常会遇到以下场景:
1、在界面程序中执行某个耗时操作:
界面的UI消息循环运行在某一个单独的线程中,一般是主线程,这个时候如果有一个耗时的操作,比如说是下载,如果也放到UI 线程中去,那么界面线程就会阻塞在下载操作那儿,导致界面卡死,这个时候就要将下载操作放到线程中去。
2、守护线程:
我们希望监视某件事情是否发生,比如监视某一个服务是否停止,如果停止就将它重新启动,一般情况下会去新开一个进程去做这些事情,我们将它称为守护进程,但是有的时候仅仅只是心跳检测而已(每个一段时间检查一下),开启一个线程不划算(主要指进程间通信),这个时候可以将这个心跳任务放到线程里。
3、加速线程:
现在的cpu很少有单核单线程的cpu,比如我现在的电脑就是4核8线程的,一个线程最多可以占用1/8cpu,这个时候就可以开8 个线程来加速运算。
这些场景都有一个共同的特点:在同一时刻(不考虑并行并发,指的是看上去是同一时刻)运行多个程序段。
使用C11的线程
从C11开始,C++加入了对线程的支持,意味着可以使用std库编写平台无关的多线程代码了。
第一个线程程序:
#include <thread>
#include "color_print.h"
/** 不带参数的线程回调
*/
void ThreadCallBack()
{
for (int i = 0; i < 100; ++i)
{
ColorPrintf(Red, "ThreadCallBack\r\n");
}
}
int main()
{
std::thread td(ThreadCallBack);
for (int i = 0; i < 100; ++i)
{
ColorPrintf(Green, "main\r\n");
}
td.join();
return 1;
}
使用std::thread td(ThreadCallBack1);创建一个线程对象并指定回调函数,需要注意的是该线程类是没有shart这个函数的,也就是说创建好了的时候线程就开始运行了。结果如下:
好了第一个多线程程序就算完成了,看起来也不是很难嘛~~
再次观察std::thread类,(默认构造, 就是不带参数的那种不多讨论,这里只说结论:它不会去创建线程。)发现他的构造函数除了指定回调函数外还可以指定额外参数,这些参数会被传入回调函数中去。比如:
/** 带参数的线程回调
@param [in] pirntCount 循环次数
*/
void ThreadCallBack(int pirntCount)
{
for (int i = 0; i < pirntCount; ++i)
{
ColorPrintf(Red, "ThreadCallBack\r\n");
}
}
int main()
{
std::thread td(ThreadCallBack, 10);
for (int i = 0; i < 100; ++i)
{
ColorPrintf(Green, "main\r\n");
}
td.join();
return 1;
}
结果如下:
数一下确实只有10个线程输出,途中方框中的颜色显示不正确放到下一篇讨论。
回调类函数
线程回调函数如果是全局函数或者类的静态函数没有什么意思,如何回调某一个类的成员函数呢?
1、实际上只要将类对象的指针传入即可,比如:
class CallBack
{
public:
/** 线程中运行
*/
void ThreadRun()
{
ColorPrintf(Green, "ThreadRun\r\n");
}
};
int main()
{
// 下面代码原理不多讨论,照着用即可
// 有兴趣的可以参考https://en.cppreference.com/w/cpp/named_req/Callable
// 其原理和functional类似
CallBack* pCallBack = new CallBack();
std::thread td(&CallBack::ThreadRun, pCallBack);
td.join();
delete pCallBack;
pCallBack = NULL;
return 1;
}
结果如下:
2、thread构造函数中可以传入参数给回调函数,那么我们很容易想到把类的对象指针传给回调函数,由回调函数间接的调用类的成员函数。比如
#include <thread>
class CallBack
{
public:
/** 线程中运行
*/
void ThreadRun()
{
ColorPrintf(Green, "ThreadRun\r\n");
}
};
void ThreadCallBack(CallBack* p)
{
if (p != nullptr)
{
p->ThreadRun();
}
}
int main()
{
CallBack* pCallBack = new CallBack();
std::thread td(ThreadCallBack, pCallBack);
for (int i = 0; i < 100; ++i)
{
ColorPrintf(Green, "main\r\n");
}
td.join();
delete pCallBack;
pCallBack = NULL;
return 1;
}
结果和上面的一样,这里不截图了。
需要注意的是要保证pCallBack对象的生命周期要比线程的长,可以使用week_ptr,但是本文不作讨论。
回调类的成员函数这样做也是可以成功的,但是总感觉这样做有些,嗯,不够优雅(在那寒冷的日子里,回调函数一般都是这么干的)?C11支持线程的同时还支持了另外两个东西,一个叫做lambda,一个叫做functional,可以这样使用:
3、对于lambda:
#include <thread>
class CallBack
{
public:
/** 线程中运行
*/
void ThreadRun()
{
ColorPrintf(Green, "ThreadRun\r\n");
}
};
int main()
{
CallBack* pCallBack = new CallBack();
std::thread td([](CallBack* p)
{
if (p != nullptr)
{
p->ThreadRun();
}
}, pCallBack);
for (int i = 0; i < 100; ++i)
{
ColorPrintf(Green, "main\r\n");
}
td.join();
delete pCallBack;
pCallBack = NULL;
return 1;
}
嗯,这下子舒服多了~~,关于lambda本文不多讨论,结果和上面的一样,这里不截图了。
4、对于functional:
#include <thread>
#include <functional>
class CallBack
{
public:
/** 线程中运行
*/
void ThreadRun()
{
ColorPrintf(Green, "ThreadRun\r\n");
}
};
int main()
{
CallBack* pCallBack = new CallBack();
std::function<void()> func = std::bind(&CallBack::ThreadRun, pCallBack);
std::thread td(func);
for (int i = 0; i < 100; ++i)
{
ColorPrintf(Green, "main\r\n");
}
td.join();
delete pCallBack;
pCallBack = NULL;
return 1;
}
结果同上,同样的关于functional的用法本文不作讨论。
线程回调类成员函数一般会使用这4中方法。对于后三种方法,实际上和线程回调的关系不大,将C++的类函数传给一个C风格的回调一般都是这么干的。
最后
本文仅仅介绍了线程的最基本的用法,对于同步和安全问题留作以后~~
来源:CSDN
作者:我叫侯万楼
链接:https://blog.csdn.net/langwang122/article/details/103742912