首先需要注意的是:C++11中的线程需要VS2012以上版本才可以,否则程序不识别头文件#include<thread>,所有的线程工具均在这个头文件中。
一、C++11并发编程:thread简单的认识:
1、创建线程实例时,必须提供该线程将要执行的函数,方法之一就是传递一个函数指针。测试代码如下:
#include<thread>
#include<iostream>
using namespace std;
//using std::cout;
//using std::endl;
//using std::thread;
voidhello()
{
cout<<"Hello from thread!"<<endl;
}
int main()
{
threadt1(hello);
t1.join();//直到t1结束,main才返回,若不加该句,则可能t1未结束main就返回,该句的作用是等待被join的线程结束main才返回
return 0;
}
运行结果为:
Hello from thread!
2、区分线程:
每个线程都有唯一的ID,使用std:thread类的get_id()便可获得线程对应的唯一的ID。
示例代码如下:
#include<thread>
#include<iostream>
#include<vector>
using namespace std;
voidhello()
{
cout<<"Hello fromthread:"<<this_thread::get_id()<<endl;
}
intmain()
{
vector<thread>threads;
for(inti=0;i<5;i++)
{
threads.push_back(thread(hello));
}
//C++11包含的一种新的for循环,for循环遍历threads赋值给th
for(auto& th :threads) //让编译器通过初始值来推算变量类型
{
th.join();
}
return 0;
}
3、使用Lambda 启动线程。
当线程所要执行的代码非常短小时,没有必要专门为之创建一个函数,可使用Lambda代替。
#include<thread>
#include<iostream>
#include<vector>
using namespace std;
intmain()
{
vector<thread>threads;
for(int i = 0; i < 5;++i)
{
threads.push_back(std::thread(
[](){
cout << "Hellofrom thread " << this_thread::get_id() << endl;
}));
}
//C++11包含的一种新的for循环,for循环遍历threads赋值给th
for(auto& th :threads)
{
th.join();
}
return 0;
}
二、C++11并发编程:保护共享资源
1、同步问题:
使用简单的计数器作为示例,该计数器为结构体,它拥有计数变量以及增加或减少计数函数,例如:
#include<thread>
#include <iostream>
#include <vector>
using namespace std;
structCounter {
int m_nValue;
Counter(intn = 0) : m_nValue(n){}
voidincrement(){ ++m_nValue; }
};
intmain(){
Counter counter;
vector<thread>threads;
for(int i = 0; i < 5; ++i){
threads.push_back(thread([&counter](){
for(int i = 0; i < 999999;++i){
counter.increment();
}
}));
}
for(auto&th : threads){
th.join();
}
cout<< counter.m_nValue << endl;
return 0;
}
每次运行结果不一样,因为计数器的increment()并非原子操作,而是由三个独立的操作完成的:
(1)读取m_nValue;
(2)将m_nValue的值+1;
(3)将+1后的值返回Value变量。
当单线程运行上述代码,每次运行的结果是一样的,上述三个步骤会按顺序进行。但是在多进程情况下,可能存在如下执行顺序:
- 线程a:读取 m_nValue的当前值,得到值为 0。加1。得到1,但还没来得及写回内存
- 线程b:读取 m_nValue的当前值,得到值为 0。加1。得到1,但还没来得及写回内存。
- 线程a:将 1 写回 m_nValue 内存并返回 1。
- 线程b:将 1 写回 m_nValue内存并返回 1。
这种情况源于线程间的interleaving(交叉运行)。
解决交叉运行的方法之一就是互斥量。在同一时刻只有一个线程能够得到该对象上的锁。借助互斥量这种简而有力的性质,我们便可以解决线程同步问题。
为了计数器具有线程的安全性,需要添加std::mutex 成员,并在成员函数中对互斥量进行 lock()和unlock()调用。
struct Counter {
int m_nValue;
mutex mtx;
Counter(): m_nValue(0){}
voidincrement(){
mtx.lock();
++m_nValue;
mtx.unlock();
}
};
需添加头文件 #include<mutex>//C++11中存放互斥量的头文件。