STL线程库的前身是boost::thread,在C++ 11标准化后便正式归纳入了stl库,通过它我们可以很容易实现跨平台的线程管理。
线程管理
在std::thread库中,一个线程用的是一个thread对象表示,当创建一个thread对象时即创建一个线程,一个简单的示例如下:
#include <iostream>
#include <thread>
using namespace std;
void thread_entry(const char* arg)
{
cout << "thread "<< this_thread::get_id() << " created: " << arg << endl;
}
int main()
{
thread thrd(thread_entry, "hello world");
thrd.join();
return 0;
}
这里我通过thread对象创建了一个线程,并调用join函数等待线程结束。thead对象的构造函数中是可以传入参数的,非常方便。
std::this_thread名字空间
在前面的例子中,还用到了一个this_thread::get_id()函数用以获取当前线程的tid,std::this_thread名字空间提供了如下函数以管理当前线程:
-
yield
-
get_id
-
sleep_for
-
sleep_until
基本上从名字里就可以猜出它们的功能了。以其中的sleep_for函数为例,它提供了一个跨平台的sleep功能,再也不用自己封装了:
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura);
PS:在gcc中使用这个函数时,需要再编译的时候加-D_GLIBCXX_USE_NANOSLEEP选项,否则报语法错误,具体原因可以参看这篇文章。
Linux平台的运行错误
上述代码在Windows平台运行良好,但在Linux上运行的时候发现如下错误:
tianfang > run
terminate called after throwing an instance of 'std::system_error'
what(): Operation not permitted
Aborted
tianfang >
然后在StackOverFlow上找到了答案:在链接的时候要加 –pthread 选项。这个应该算个bug了。
删除Thread对象
我最初以为当thread对象删除时会自动杀掉线程,运行时却发现:thread对象删除时,如果没有调用join或attach,就会报异常。为了演示这一过程,首先我们把main函数改成如下所示:
int main()
{
thread thrd(thread_entry, "hello world");
//thrd.join();
return 0;
}
运行该代码时,就会发现如下错误:
tianfang > run
terminate called without an active exception
Aborted
tianfang >
也就是说,thread对象并不会自动管理线程结束,需要手动控制。常见的控制手段有两个,join和detach。join已经介绍过,用于等待线程结束,而detach的功能是托管线程,线程仍然继续运行至结束,但不再受到thread对象控制。
对线程编程模型比较熟悉的人可能会发现:它并没有提供一个强制终止线程的选项。在boost的this_thread名字空间下其实是提供了终止线程的函数的,但是并没有纳入stl中,可见标准委员会也是不建议强制终止这种不健壮的做法。
互斥和同步
互斥体
stl中对mutex划分得很细,按是否递归、是否超时分为四个对象:
-
mutex
-
timed_mutex
-
recursive_mutex
-
recursive_timed_mutex
这四个对象的操作函数大体上都是如下几个:
-
lock 获取锁
-
trylock 尝试获取锁
-
unlock 释放锁
-
try_lock_for 在一定时间范围内尝试获取锁(有超时功能的mutex才能用)
-
try_lock_until 尝试获取锁到某个时间点位置(有超时功能的mutex才能用)
除了直接使用这几个类外,也可以使用lock_guar、unique_lock之类的的封装类,它的功能上类似C#的lock关键字(lock范围并不等价),在指定范围内自动获取锁,出了该范围外自动解锁,可有效防止加解锁不对称。
另外,stl也提供了try_lock和lock的泛型方法实现批量获取锁定,这里就不多介绍了。
条件变量
stl中对条件变量封装的是condition_variable类,它的基本使用方式和系统api差不多,如下是一个生产者/消费者的例子:
#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
#include <queue>
#include <chrono>
using namespace std;
int main()
{
queue<int> buffer;
mutex m;
condition_variable cond_var;
int num = 0;
thread producer([&]()
{
while (true)
{
this_thread::sleep_for(chrono::seconds(1));
unique_lock<std::mutex> lock(m);
num++;
std::cout << "producing " << num << '\n';
buffer.push(num);
cond_var.notify_one();
}
});
thread consumer([&]()
{
while (true)
{
unique_lock<std::mutex> lock(m);
if(buffer.empty())
cond_var.wait(lock);
std::cout << "consuming " << buffer.front() << '\n';
buffer.pop();
}
});
producer.join();
consumer.join();
}
条件变量还有其它几个封装,这里就不多介绍了。
其它
其它几个同步互斥的类,如栅栏、读写锁等并没有纳入STL,如果要使用它们,可以直接使用boost库。