最新研究下C++11中线程的知识,基本内容如下:
1、C++11中创建线程的几种方式
在C11中,我们可以通过创建std::thread类的对象来创建额外的线程。每个thread对象可以跟具体的某个线程关联,从而达到多线程并发的目的。
必须 #include
那么,std::thread 对象如何执行呢?很简单,给它一个回调入口(callback),当线程启动时就会自动执行callback。
简单来说,callback可以有一下三种模式。
- 函数指针(Function pointer)
- 函数对象(Function Objece)
- Lambda 表达式
std::thread可以以这种方式来创建一个对象: std::thread throbj();
throbj对象创建后,即可以开始执行了,调用callback。
如果某个线程A需要等待其它线程B结束,那么可以在A线程中调用join来实现。
接下来看几个示例。
1.1、使用函数指针来创建(Function Pointer)
#include <unistd.h>
#include <iostream>
#include <thread>
using namespace std;
// 普通函数作为线程入口
void thread_function(const std::string& data) {
for (int i = 0; i <= 5; i++) {
cout << "in thread_function, data=" << data << ", i=" << i << endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
// 成员函数指针作为线程入口
class Task {
public:
Task() {}
~Task() {}
// 普通成员函数
void execute(const std::string& data) {
for (int i = 0; i <= 3; i++) {
cout << "in Task::execute"
<< ", data:" << data << ", i:" << i << endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
// 静态成员函数
static void execute_static(const std::string& data) {
for (int i = 0; i <= 3; i++) {
cout << "in Task::execute_staticF"
<< ", data:" << data << ", i:" << i << endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
int main() {
std::string data = "hello";
// 以普通函数作为线程入口
std::thread thrObj1(&thread_function, data);
// 以普通成员函数作为线程入口
Task* task = new Task();
std::thread thrObj2(&Task::execute, task, data);
// 以静态成员函数作为程序入口
std::thread thrObj3(&Task::execute_static, data);
thrObj1.join();
thrObj2.join();
thrObj3.join();
delete task;
return 0;
}
1.2、使用函数对象来创建(Function Object)
// 函数对象作为线程入口
class DisplayThread {
public:
void operator()() {
for (int i = 0; i <= 3; i++) {
cout << "in DisplayThread(), i=" << i << endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
// in main threae
std::thread thrObj4((DisplayThread()));
thrObj4.join();
1.3、使用Lambda来创建(Lambda)
std::string data = "hello";
std::thread thrObj5([data]() {
cout << "thread in Lambda, data=" << data << endl;
for (int i = 0; i <= 3; i++) {
cout << "thread runs in Lambda, i=" << i << endl;
}
});
thrObj5.join();
2、线程的join和detach
2.1、线程的join
主线程创建子线程A后,可以调用A.join()来等待其完成,这是有点简单而粗暴的方法,因为在线程A完成之前,主线程会一直阻塞在A.join()而什么都不能做。当线程A完成所有执行任务后其join()才会返回。主线程调用A.join()也包含了对线程A的内存空间清理等工作(每个线程有自己的调用栈)。
在调用join()之前,子线程的状态是joinable的,而调用之后变成non-joinable了,因此线程只能调用**1次**join操作,否则会出现异常。
void func() {
// do lots of work here....
return;
}
int main() {
std::thread thr(func);
// do other work in main
thr.join(); // OK, 等待thr的结束,清理其空间,使其可以安全销毁
thr.join(); // error! 已经调用过一次join了,thr变成 non-joinable 了,会导致程序出错!
return 0;
}
假如主线程未调用thr.join(不管是未显式调用,还是因为异常原因导致未调用),都会使thr线程对象仍然处于joinable状态,那么thr对象析构时会调用std::terminate(),默认行为会终止程序执行。
int main() {
std::thread thr([] {
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "child thread return" << endl;
});
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
上面的程序中,main线程结束前thr虽然早已执行完毕,但由于thr的join未被调用,使得thr仍然是joinable状态,thr的析构函数就会调用std::terminate(),导致程序异常终止,而不是正常结束。
2.2、线程的detach
如果在主线程里不希望因为调用join而被阻塞(例如,想让子线程自己成为一个后台线程,自己负责资源回收;或者主线程不知道该何时调用join),这种情况可以调用线程的detach()。
int main() {
std::thread thr([] {
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "child thread return" << endl;
});
thr.detach(); // 调用了detach后,主线程就能正常退出
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
调用了thr.detach()后,主线程就可以正常退出。同样的,detach也最多只能调用1次,如多长调用会导致程序异常。
简而言之,对于std::thread对象,一定要确保如下:
- 要么调用其join,要么调用其detach,不能两者都不调用
- join和detach 都不能多次调用
这样才能确保线程对象析构时不会调用std::terminate()异常终止程序。
为保证这点,该如何做比较合适呢?
很容易想到(RAII),即RESOURCE ACQUISITION IS INITIALIZATION。主要思想是在类对象构造时初始化资源,在对象析构时做资源清理工作。
如下示例:
class ThreadRAII {
std::thread& m_thread;
public:
ThreadRAII(std::thread& threadObj) : m_thread(threadObj) {}
~ThreadRAII() {
// Check if thread is joinable then detach the thread
if (m_thread.joinable()) {
m_thread.detach();
}
}
};
void thread_func() {
for (int i = 0; i <= 3; i++) {
std::cout << "thread_function Executing" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
std::thread threadObj(&thread_func);
// wrapperObj初始化时获得threadObj的引用
// wrapperObj析构时自动调用threadObj.detach()
ThreadRAII wrapperObj(threadObj);
return 0;
}
3、给线程传递参数
3.1、简单参数传递
给线程传递参数,只需要在构造std::thread对象的时候把参数列表写上即可。
每个线程都有自己的栈空间,默认情况下,传给线程的所有参数都会被拷贝到该线程的私有栈空间,再使用,即使用的是它们的拷贝。
void threadCallback(int x, std::string str) {
std::cout << "x = " << x << std::endl; // 10
std::cout << "str = " << str << std::endl; // Sample String
}
int main() {
int x = 10;
std::string str = "Sample String";
std::thread threadObj(threadCallback, x, str);
threadObj.join();
return 0;
}
3.2、传递指针务必要注意
当然也可以给线程传递指针作为参数。不过千万要注意的是,要确保指针所指对象的生存期。否则会出问题。如下示例:
#include <iostream>
#include <thread>
void newThreadCallback(int* p) {
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
*p = 19;
}
int main() {
{
int i = 10;
std::thread t(newThreadCallback, &i);
t.detach();
}
// 变量i的生存期已经结束,但是线程t还在执行
// 里面使用的指针p已经无效
std::chrono::milliseconds dura(3000);
std::this_thread::sleep_for(dura);
return 0;
}
3.3、如果给线程传递引用
安装函数调用时传引用的用法,很容易想到给线程传递引用的方式如下:
#include <iostream>
#include <thread>
void threadCallback(int const &x) {
int &y = const_cast<int &>(x);
y++;
std::cout << "Inside Thread x = " << x << std::endl;
}
int main() {
int x = 9;
std::thread threadObj(threadCallback, x);
threadObj.join();
std::cout << "Outside Thread x = " << x << std::endl;
return 0;
}
然而,很遗憾,这种传递没达到预期。
程序实际输出:
Inside Thread x = 10
Outside Thread x = 9
虽然threadCallback线程接收引用,但是线程内部对变量的改动对线程外还是不可见。原因很简单,每个线程有自己的调用栈,线程内使用的这是参数的拷贝(副本)。就上面例子而言,threadCallback的x参数只是对一个临时对象的引用而已,该临时对象是main线程中x的拷贝。
那么,如何做才能解决传引用的问题?
很简单,使用 std::ref()
#include <iostream>
#include <thread>
void threadCallback(int const &x) {
int &y = const_cast<int &>(x);
y++;
std::cout << "Inside Thread x = " << x << std::endl;
}
int main() {
int x = 9;
std::thread threadObj(threadCallback, std::ref(x));
threadObj.join();
std::cout << "After Thread Joins x = " << x << std::endl;
return 0;
}
3.4、给类的成员函数传递参数
将类成员函数地址作为线程的回调地址,将对象地址作为第二个参数。其它要传递的参数紧跟其后即可。
如果是类的静态成员变量,则只需要将静态函数地址作为线程的回调地址,不需要传对象地址。
示例:
// 成员函数指针作为线程入口
class Task {
public:
Task() {}
~Task() {}
// 普通成员函数
void execute(const std::string& data) {
for (int i = 0; i <= 3; i++) {
cout << "in Task::execute"
<< ", data:" << data << ", i:" << i << endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
// 静态成员函数
static void execute_static(const std::string& data) {
for (int i = 0; i <= 3; i++) {
cout << "in Task::execute_staticF"
<< ", data:" << data << ", i:" << i << endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
int main() {
Task task;
std::thread thrObj1(&Task::execute, &task, "execute");
std::thread thrObj2(&Task::execute_static, "execute_static");
thrObj1.join();
thrObj2.join();
}
转至:https://blog.csdn.net/weixin_40747540/article/details/78759359