C++11多线程
Posted 老码囧途
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11多线程相关的知识,希望对你有一定的参考价值。
1、std::thread
在C++11之前,C++语言层面是不支持多线程的,想利用C++实现并发程序,借助操作系统的API实现跨平台的并发程序存在着诸多不便,当C++11在语言层面支持多线程后,编写跨平台的多线程代码就方便了许多。
C++11提供的std::thread在开发多线程方面带来了便捷。
void threadfunc()
{
std::cout << "thread func" << std::endl;
}
int main()
{
std::thread t1(threadfunc);
t1.join(); //等待threadfunc运行结束
return 0;
}
首先定义线程对象t1,线程函数threadfunc运行在线程对象t1中,当线程创建成功并执行线程函数后,一定要保证线程函数运行结束才能退出,这里调用了join函数阻塞线程,直到threadfunc运行结束,回收对应创建线程的资源。如果不阻塞线程,就不能保证线程对象t1在threadfunc运行期间有效,下面不调用join阻塞线程。
void threadfunc()
{
std::cout << "thread func" << std::endl;
}
int main()
{
std::thread t1(threadfunc);
//t1.join(); //等待threadfunc运行结束
return 0;
}
在运行时引起了程序崩溃。
除了调用join阻塞线程,保证线程对象在线程函数运行期间的有效性,还可以通过线程分离的手段实现,调用detach函数使得线程对象与线程函数分离,这样,在线程函数运行期间,线程对象与线程函数就没有联系了,此时的线程是作为后台线程去执行,detach后就无法再和线程发生联系,也不能通过join来等待线程执行完毕,线程何时执行完无法控制,它的资源会被init进程回收,所以,通常不采用detach方法。
void threadfunc()
{
std::cout << " detach thread func" << std::endl;
}
int main()
{
std::thread t1(threadfunc);
t1.detach(); //线程分离
return 0;
}
这里调用detach实现线程分离,但是运行后,主线程退出的时候threadfunc还没有输出“detach thread func”,threadfunc什么时候运行结束也无法确定,为了看到所创建的线程运行结果,在主线程等待一下再退出。
void threadfunc()
{
std::cout << "detach thread func" << std::endl;
}
int main()
{
std::thread t1(threadfunc);
t1.detach();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
此时运行结果:
detach thread func
通过std::thread创建的线程是不可以复制的,但是可以移动。
void threadfunc()
{
std::cout << "move thread func" << std::endl;
}
int main()
{
std::thread t1(threadfunc);
std::thread t2(std::move(t1));
t2.join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
输出结果:
move thread func
移动后t1就不代表任何线程了,t2对象代表着线程threadfunc。另外,还可以通过std::bind来创建线程函数。
class A {
public:
void threadfunc()
{
std::cout << "bind thread func" << std::endl;
}
};
int main()
{
A a;
std::thread t1(std::bind(&A::threadfunc,&a));
t1.join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
创建一个类A,然后再main函数中将类A中的成员函数绑定到线程对象t1上,运行结果:
bind thread func
每个线程都有自己的线程标识,也就是线程ID,当线程创建成功后,可以通过get_id来获取线程的ID。
class A {
public:
void threadfunc()
{
std::cout << "bind thread func" << std::endl;
}
};
int main()
{
A a;
std::thread t1(std::bind(&A::threadfunc,&a));
std::cout << "main thread ID is : " << std::this_thread::get_id() << std::endl;
std::cout << "t1 thread ID is : " << t1.get_id() << std::endl;
t1.join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
std::this_thread::get_id获取的是当前线程的ID,t1.get_id获取的是所创建的t1对象中运行的线程ID,对应的ID分别为:
main thread ID is : 11932
t1 thread ID is : 12076
bind thread func
虽然get_id可以获取线程的ID,但是其返回类型是thread::id,通过cout可以输出线程ID,但是这样使用似乎不太方面,要是能转换为整形就好了。其实可以将得到的线程ID写入到ostreamstring流中,转换成string类型,再转换成整形。
class A {
public:
void threadfunc()
{
std::cout << "bind thread func" << std::endl;
}
};
int main()
{
A a;
std::thread t1(std::bind(&A::threadfunc, &a));
std::ostringstream os1;
os1 << t1.get_id() << std::endl;
std::string strID = os1.str(); //转换成string类型
int threadID = atoi(strID.c_str()); //转换成int类型
std::cout << "t1 thread ID is : " << threadID << std::endl;
t1.join();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));//睡眠1000毫秒
break;
}
return 0;
}
输出结果:
t1 thread ID is : 6956
bind thread func
2、std::mutex
进入多线程编程的世界,除了要牢牢掌握std::thread使用方法,还要掌握互斥量(锁)的使用,这是一种线程同步机制,在C++11中提供了4中互斥量。
std::mutex; //非递归的互斥量
std::timed_mutex; //带超时的非递归互斥量
std::recursive_mutex; //递归互斥量
std::recursive_timed_mutex; //带超时的递归互斥量
从各种互斥量的名字可以看出其具有的特性,在实际开发中,常用就是std::mutex,它就像是一把锁,我们需要做的就是对它进行加锁与解锁。
std::mutex g_mutex;
void func()
{
std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
std::thread t4(func);
std::thread t5(func);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
创建了5个线程,然后分别调用func函数,得到结果:
entry func test thread ID is : entry func test thread ID is : 19180
entry func test thread ID is : 3596
13632
entry func test thread ID is : 9520
entry func test thread ID is : 4460
leave func test thread ID is : 13632
leave func test thread ID is : 19180
leave func test thread ID is : leave func test thread ID is : 9520
3596
leave func test thread ID is : 4460
可以看出,并没有按顺序去执行线程函数,后面创建的线程并没有等待前面的线程执行完毕,导致结果混乱,下面用mutex进行控制:
std::mutex g_mutex;
void func()
{
g_mutex.lock();
std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
g_mutex.unlock();
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
std::thread t4(func);
std::thread t5(func);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
只要线程进入func函数就进行加锁处理,当线程执行完毕后进行解锁,保证每个线程都能按顺序执行,输出结果:
entry func test thread ID is : 8852
leave func test thread ID is : 8852
entry func test thread ID is : 15464
leave func test thread ID is : 15464
entry func test thread ID is : 17600
leave func test thread ID is : 17600
entry func test thread ID is : 16084
leave func test thread ID is : 16084
entry func test thread ID is : 4156
leave func test thread ID is : 4156
虽然通过lock与unloc可以解决线程之间的资源竞争问题,但是这里也存在不足。
func()
{
//加锁
执行逻辑处理; //如果该过程抛出异常导致程序退出了,就没法unlock
//解锁
}
int main()
{
......
}
func中咋执行逻辑处理中程序因为某些原因退出了,此时就无法unlock了,这样其他线程也就无法获取mutex,造成死锁现象。实际开发中,通常也不会这样写代码,而是采用lock_guard来控制mutex。
template <class _Mutex>
class lock_guard {
public:
using mutex_type = _Mutex;
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx)
{
_MyMutex.lock(); //构造函数加锁
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx)
{
}
~lock_guard() noexcept
{
_MyMutex.unlock(); //析构函数解锁
}
lock_guard(const lock_guard&) = delete;
lock_guard& operator=(const lock_guard&) = delete;
private:
_Mutex& _MyMutex;
};
lock_guard是类模板,在其构造函数中自动给mutex加锁,在退出作用域的时候自动解锁,这样就可以保证mutex的正确操作,这也是RAII(获取资源便初始化)技术的体现。
std::mutex g_mutex;
void func()
{
std::lock_guard<std::mutex> lock(g_mutex); //加锁
std::cout << "entry func test thread ID is : " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::microseconds(1000));
std::cout << "leave func test thread ID is : " << std::this_thread::get_id() << std::endl;
//退出作用域后,lock_guard对象析构就自动解锁
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
std::thread t4(func);
std::thread t5(func);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
运行结果:
entry func test thread ID is : 19164
leave func test thread ID is : 19164
entry func test thread ID is : 15124
leave func test thread ID is : 15124
entry func test thread ID is : 2816
leave func test thread ID is : 2816
entry func test thread ID is : 17584
leave func test thread ID is : 17584
entry func test thread ID is : 15792
leave func test thread ID is : 15792
3、thread_local
C++11中提供了thread_local,thread_local定义的变量在每个线程都保存一份副本,而且互不干扰,在线程退出的时候自动销毁。
thread_local int g_k = 0;
void func1()
{
while (true)
{
++g_k;
}
}
void func2()
{
while (true)
{
std::cout << "func2 thread ID is : " << std::this_thread::get_id() << std::endl;
std::cout << "func2 g_k = " << g_k << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
int main()
{
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
在func1对g_k循环加1操作,在func2每间隔1000毫秒输出一次g_k的值:
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
func2 thread ID is : 15312
func2 g_k = 0
......
可以看出func2中的g_k始终保持不变。
以上是关于C++11多线程的主要内容,如果未能解决你的问题,请参考以下文章