看了网上一些说法,总结以下几点:
1:从C++11开始,标准库里已经包含了对线程的支持,即:
std::thread
2:C++本身还支持pthread这个API来进行多线程编程。
3:自己常用Windows编程,还是拥抱一下C++11吧,简单的学习一下使用方法。(虽然知乎上std::thread被黑成翔了~)
开始实践
环境:WIN7、VS2015
构造新线程
1:std::thread的默认构造函数:
thread() noexcept
构造出的线程对象不会执行。
2:正常的初始化函数:
templateT,class ...Args> explicit thread(T&& t,Args&&... args);
函数功能:构造出一个新的线程,并直接开始执行,新执行的线程调用函数 t ,并传递 args 作为参数。
t 可以绑定函数对象,成员函数、移动构造函数
args 可以绑定一些函数的参数,若 t 是一个成员指针,那么args的第一个参数就必须是一个对象,或是该对象的引用,或是该对象的指针。
3:删除线程:
thread(const thread&) = delete;
一些函数的介绍
1:join() 函数
该函数会阻塞主线程,直到调用该函数的线程执行完毕才会继续执行主线程
if(t1.joinable())
t1.join();
使用前最好加个判断
2:detach函数
将子线程从主线程中分离,独立运行,不会阻塞主线程
detach方式不会对当前代码造成影响,当前代码继续执行,而创建的线程会同时并发执行
t1.detach
参数问题
创建的新线程对当前作用于的变量的使用:创建完之后,离开作用域,但是线程可能仍然在执行,那么此时线程如果使用的是之前作用域中定义的局部变量的引用或者指针,则会出现意想不到的错误。(值传递就不会出现这个问题)
转移线程的所有权
thread是可移动的(movable)的,但不可复制(copyable)。可以通过move来改变线程的所有权,灵活的决定线程在什么时候join或者detach。
thread t1(f1);
thread t3(move(t1));
将线程从t1转移给t3,这时候t1就不再拥有线程的所有权,调用t1.join或t1.detach会出现异常,要使用t3来管理线程。这也就意味着thread可以作为函数的返回类型,或者作为参数传递给函数,能够更为方便的管理线程。
获取线程的ID
线程的标识类型为std::thread::id,有两种方式获得到线程的id。
* 通过thread的实例调用get_id()直接获取
* 在当前线程上调用this_thread::get_id()获取
t1.get_id();
互斥锁
针对数据竞争的情况,可以使用线程互斥对象mutex保持数据同步。
mutex类的使用需要包含头文件mutex
void thread01()
{
while (totalNum > 0)
{
mu.lock(); //同步数据锁
cout << totalNum << endl;
totalNum--;
Sleep(100);
mu.unlock(); //解除锁定
}
}
异常情况的处理
当决定以detach方式让线程在后台运行时,可以在创建thread的实例后立即调用detach,这样线程就会后thread的实例分离,即使出现了异常thread的实例被销毁,仍然能保证线程在后台运行。
但线程以join方式运行时,需要在主线程的合适位置调用join方法,如果调用join前出现了异常,thread被销毁,线程就会被异常所终结。
为了避免异常将线程终结,或者由于某些原因,例如线程访问了局部变量,就要保证线程一定要在函数退出前完成,就要保证要在函数退出前调用join。
一种比较好的方法是资源获取即初始化(RAII,Resource Acquisition Is Initialization),该方法提供一个类,在析构函数中调用join。无论是何种情况,当函数退出时,局部变量g调用其析构函数销毁,从而能够保证join一定会被调用。
class thread_guard
{
thread &t;
public:
explicit thread_guard(thread& _t) :
t(_t) {}
~thread_guard()
{
if (t.joinable())
t.join();
}
thread_guard(const thread_guard&) = delete;
thread_guard& operator=(const thread_guard&) = delete;
};
void func() {
thread t([] {
cout << "Hello thread" << endl;
});
thread_guard g(t);
}
lambda表达式与thread
lambda本身就相当于一个函数(匿名函数),所以也可以当做参数来赋给线程对象
void TestLambda()
{
for (int i = 0; i < 100; i++)
{
std::thread t([i]() {
std::cout << i << std::endl;
});
t.detach();
}
}
好了,代码奉上:
/*time:20180605
author:MISAYAONE
*/
#include
#include
#include
#include
using namespace std;
mutex mu;//互斥锁对象
int num = 100;
void thread_func1()
{
for (int i=0;i<10;i++)
{
cout <<"thread 1's "<< i << endl;
Sleep(100);
}
while (num>0)
{
mu.lock();//互斥锁加锁
cout << num << endl;
num--;
mu.unlock();//互斥锁解锁
}
}
void thread_func2(int n)
{
for (int i = 0; icout <<"thread 2's "<< i << endl;
Sleep(200);
}
while (num>0)
{
mu.lock();
cout << num << endl;
num--;
mu.unlock();
}
}
class A
{
public :
void thread_func3()
{
for (int i = 0; i<10; i++)
{
cout << "class A thread 3's " << i << endl;
Sleep(200);
}
}
};
class thread_guard
{
thread &t;
public:
explicit thread_guard(thread& _t):t(_t){}
~thread_guard()
{
if (t.joinable())
{
t.join();
}
}
//拷贝赋值默认去除
thread_guard(const thread_guard&) = delete;
thread_guard& operator=(const thread_guard&) = delete;
};
int main()
{
A a;
thread task00();//默认构造函数
thread task01(thread_func1);//正常构造函数
thread_guard g(task01);//出现异常后,函数退出,g局部对象对销毁,此时g调用析构函数,会自动执行task01的join函数,从而能够保证join一定会被调用
thread task02(thread_func2,10);//带args的线程构造函数
thread task_i(move(task02));//转移线程所有权,此时再利用task02调用join和detach报错
thread task03(&A::thread_func3,a);//传入类的成员函数作为线程构造函数的参数
//join函数顺序执行线程
task01.join();
//task02.join();
//task03.join();
//不影响当前代码的执行,几个线程后台自行执行,不阻塞主线程
//task01.detach();
//task02.detach();
task03.detach();
//lambda匿名函数
for (int j=0;j<20;j++)
{
thread t([j]() {cout << j << endl; });
t.detach();
}
for (int i = 0; i<10; i++)
{
cout << "main thread's " << i << endl;
Sleep(200);
}
cout << "task01.get_id() = " << task01.get_id() << endl;//获取线程ID
cin.get();
return 0;
}