C++11中封装了一套跨平台的线程库以及配套的并发控制库,这里一起记录一下
一、Thread库
C++11中封装了一套线程库,位于thread
文件中,具有基本的线程需要的各类函数。
如下显示了一个基本的线程操作流程:
#include <thread>
#include <iostream>>
using namespace std;
void ThreadFunc(int nThreadNum)
{
cout << "INFO: Num is " << nThreadNum << endl;
}
int main()
{
thread TestThread(ThreadFunc, 1);
cout << TestThread.get_id() << endl;
TestThread.join();
return 0;
}
线程初始化时传入的第一个参数为线程执行的函数,后续可以传入可变参数用于线程函数的参数。主线程可以用get_id获取对应的线程id,需要注意的是,此id并不是系统分配的那个id,获取系统id需要各个操作系统原生的接口。除了一般的函数,也可以传入类对象的成员函数,如
ThreadFuncObj TestObj;
thread TestThread(&ThreadFuncObj::ThreadFunc, &TestObj, 1);
注意上面代码中,由于ThreadFuncObj::ThreadFunc
是非静态,因此必须加&来创建一个该类型的函数指针,如果是静态成员函数则不用加,此时编辑器会默认是一个函数指针
二、互斥量mutex
c++11也提供了一套mutex库,用于多线程对共享变量的访问控制。所有相关类都在<mutex>
头文件中。一共有4个互斥类
序号 | 名称 | 用途 |
---|---|---|
1 | std::mutex | 最基本也是最常用的互斥类 |
2 | std::recursive_mutex | 同一线程内可递归(重入)的互斥类 |
3 | std::timed_mutex | 除具备mutex功能外,还提供了带时限请求锁定的能力 |
4 | std::recursive_timed_mutex | 同一线程内可递归(重入)的timed_mutex |
基本操作是lock
、try_lock
、unlock
三种。下面是个简单示例
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
void AddCount(mutex &mutexTest, int &nCount)
{
for (int i = 0; i < 1000; ++i)
{
mutexTest.lock();
++nCount;
mutexTest.unlock();
}
}
int main()
{
thread arrThreads[5];
mutex mutexTest;
int nCount = 0;
for (auto& threadIte : arrThreads)
{
threadIte = thread(AddCount, ref(mutexTest), ref(nCount));
}
for (thread& threadIte : arrThreads)
{
threadIte.join();
}
cout << nCount << endl;
return 0;
}
在上面例子中,如果把lock改为try_lock,则可能导致最终输出小于5000,此外还需注意两点:
- C++11新增的
for(a : b)
这种操作类似于python里面的for a in b
,注意,如果要在遍历时操作b里面的a,如改变a的值等,则需要加引用符号&。不加也能操作,但对象就是一个拷贝的对象,而不是b里面的a。如上述例子,threadIte
没有加引用,则会报错,因为thread不支持拷贝构造。 - 对于线程的传参,需要用ref()来标识引用参数,不然会报错
三、MutexLockGuard
C++的RAII特性被应用在很多地方,mutex库也提供了一套lock_guard类,使得程序可以实现在构造函数中锁定mutex,在析构中解锁。
最基础的是lock_guard模板
lock_guard<mutex> _guard(mutexTest);
lock_guard模板提供了最基本的RAII特性来操作mutex。此外还可以用unique_lock,unique_lock提供了更多的接口,比如构造之后还可以进行解锁,而不用非得等到析构的时候才解锁
unique_lock<mutex> _guard(mutexTest);
_guard.unlock();
相比lock_guard,unique_lock要更加灵活,但占用空间与效率也因此要慢一些
四、条件变量wait与notify
C++11同时也提供了条件变量来配合mutex,从而可以实现信号量的控制机制。基本操作就是wait
+notify_one/notify_all
void AddCount(mutex& mutexTest, condition_variable &cv, int& nCount)
{
for (int i = 0;i < 100; ++i)
{
unique_lock<mutex> _guard(mutexTest);
nCount += 1;
cv.notify_one();
}
}
void Subcount(mutex& mutexTest, condition_variable& cv, int& nCount)
{
for (int i = 0; i < 100; ++i)
{
unique_lock<mutex> _guard(mutexTest);
if (nCount <= 0)
cv.wait(_guard);
nCount -= 0;
}
}
当调用wait
时,线程会把传入的互斥量解锁,并等待对应的信号量,当收到其他线程的notify
信号后,会重新尝试锁定互斥量,并执行wait
之后的程序。
如果要用条件变量condition_variable
类,则必须要使用unique_lock<mutex>
提供的互斥量类,如果想使用其他类型,则可以用condition_variable_any
。