d学了这么久我才刚刚知道C++11居然对多线程有如此丰富的支持,一方面感慨自己学的还是太少了,一方面真的很开心可以告别自己封装mutex,线程,currentthread这些东西时代了。最重要的一点,在windows上写的代码和在linux上也可以编译运行,这意味着可以使用宇宙第一IDE来调试多线程程序。我会用这个系列记录自己学习C++11多线程的过程。
C++11中引入了thread这个头文件,终于把线程当成对象来操作了,以前还要自己封装,还要自己定义每个线程的私有空间,现在C++11把一切都帮我们做了。
除了thread还有atomic,可以让我们让我们非常轻松的申请一个原子数,其中还包含一个atomic_flag,我们可以用它来制作一个自旋锁。
首先来看看atomic_flag,它内含一个标志位,它支持两个操作test_and_set()和clear(),在使用之前用宏ATOMIC_FLAG_INIT初始化,初始化意义是吧其中的标志位置位0,test_and_set检测其中的标志位,如果是0就置位1,返回0,如果是1就不变,返回1,这些操作都是原子性的,clear用于把标志位置位0。
下面的程序可以用来熟悉上述的几个操作。大致的意思是创建10个线程,在确保所有线程都创建好了之后,开始计数,第一个计数完毕的输出自己的id。其中的全局原子数ready起到了类似发令枪的作用,yield用于防止忙等待,如果此时ready还没有准备好,就把时间片交出去。
#include<iostream>
#include<atomic>
#include<thread>
#include<vector>
using namespace std;
atomic<bool> ready(false);
atomic_flag winner=ATOMIC_FLAG_INIT;
void count1m(int id)
{
while(!ready)
this_thread::yield();
for(int i=0;i<1000000;i++);
if(!winner.test_and_set())
cout<<id<<endl;
}
int main()
{
vector<thread>threads;
for(int i=0;i<10;i++)
threads.push_back(thread(count1m,i));
ready=true;
for(int i=0;i<10;i++)
threads[i].join();
}
#include<iostream>
#include<atomic>
#include<thread>
#include<vector>
using namespace std;
atomic_flag flag=ATOMIC_FLAG_INIT;
void f(int id)
{
while(flag.test_and_set())
;
cout<<id<<endl;
flag.clear();
}
int main ()
{
vector<thread>threads;
for(int i=0;i<10;i++)
threads.push_back(thread(f,i));
for(int i=0;i<threads.size();i++)
threads[i].join();
}
上面这段代码便已经是一个简单的自选锁了,while构成加锁动作,clear构成解锁动作,如果此时锁已经被获取,就在while处忙等待,直到获取锁为止。
class Mylock
{
private:
atomic_flag _lock;
public:
Mylock()_lock(ATOMIC_FLAG_INIT)
{ }
void lock()
{
while(_lock.test_and_set())
;
}
void unlock()
{
_lock.clear();
}
};
Mylock splock;
void f(int id)
{
splock.lock();
cout<<id<<endl;
splock.unlock();
}
int main ()
{
vector<thread>threads;
for(int i=0;i<10;i++)
threads.push_back(thread(f,i));
for(int i=0;i<threads.size();i++)
threads[i].join();
}
但我发现网上大多数的写法都是这样的
class Mylock
{
private:
atomic_flag _lock;
public:
Mylock()_lock(ATOMIC_FLAG_INIT)
{ }
void lock()
{
while(_lock.test_and_set(memory_order_require))
;
}
void unlock()
{
_lock.clear(memory_order_release);
}
};
其中涉及到test_and_set和clear的参数
他们的参数是内存顺序,是一个枚举类型
typedef enum memory_order
{
memory_order_relaxed, //不对执行顺序做任何保证
memory_order_consume, //本线程中所有有关本原子类型的操作,必须等到本条原子操作完成之后进行
memory_order_acquire, //本线程中,后续的读操作必须在本条原子操作完成后进行
memory_order_release, // 本线程中,之前的写操作完成后才执行本条原子操作
memory_order_acq_rel, //memory_order_acquire和memory_order_release 效果的合并
memory_order_seq_cst //顺序一致
} memory_order;
大致的目的应该是防止编译器对语句执行顺序做优化。