在C++11之前,开发多线程的程序,一般都是使用pthread_create
来创建线程,繁琐且不易读,可以看一下它的函数原型:
int pthread_create(pthread_t* restrict tidp,const pthread_attr_t* restrict_attr,void* (*start_rtn)(void*),void *restrict arg);
输入参数:
可以看到pthread_create
参数有点复杂,不利于开发。
因此,c++11引入了
std::thread
来创建线程,支持对线程join
或者detach
,不过在调用这两个函数之前,先用joinable()
函数判断。
示例:
#include
#include
using namespace std;
int main()
{
auto func1 = []()
{
cout << "is func1 running" << endl;
};
std::thread t(func1);
if (t.joinable()) // 检查线程可否被join
{
t.detach();
}
auto func2 = [](int value)
{
cout << "is func2 running, "
<< "value is " << value << endl;
};
std::thread tt(func2, 20);
if (tt.joinable())
{
tt.join();
}
return 0;
}
打印输出:
is func1 running
is func2 running, value is 20
上述代码中,函数func1
和func2
运行在线程对象t和tt中,从刚创建对象开始就会新建一个线程用于执行函数。
调用join函数将会阻塞主线程,直到线程函数执行结束,线程函数的返回值将会被忽略。
如果不希望线程被阻塞执行,可以调用线程对象的detach函数,表示将线程和线程对象分离。
如果没有调用join或者detach函数,假如线程函数执行时间较长,此时线程对象的生命周期结束调用析构函数清理资源,这时可能会发生错误。
这里有两种解决办法,
调用join(),保证线程函数的生命周期和线程对象的生命周期相同,
调用detach(),将线程和线程对象分离,
这里需要注意,如果线程已经和对象分离,那就再也无法控制线程什么时候结束了,不能再通过
join
来等待线程执行完。
这里可以对thread
进行封装(后边的lock也是用了这种封装方式),避免没有调用join或者detach
可导致程序出错的情况出现:
#include
#include
using namespace std;
class ThreadGuard
{
public:
enum class DesAction
{
join,
detach
};
ThreadGuard(std::thread &&t, DesAction a) : m_thread(std::move(t)), m_action(a){};
~ThreadGuard()
{
if (m_thread.joinable())
{
if (m_action == DesAction::join)
{
m_thread.join();
}
else
{
m_thread.detach();
}
}
}
ThreadGuard(ThreadGuard &&) = default;
ThreadGuard &operator=(ThreadGuard &&) = default;
std::thread &get() { return m_thread; }
private:
std::thread m_thread;
DesAction m_action;
};
int main()
{
ThreadGuard t(std::thread([]()
{ std::cout << "thread guard " << std::endl; }),
ThreadGuard::DesAction::join);
return 0;
}
c++11还提供了获取线程id,或者系统cpu个数,获取thread native_handle
,使得线程休眠等功能
std::thread t(func);
cout << "当前线程ID " << t.get_id() << endl;
cout << "当前cpu个数 " << std::thread::hardware_concurrency() << endl;
auto handle = t.native_handle();// handle可用于pthread相关操作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::mutex
是一种线程同步的手段,用于保存多线程同时操作的共享数据。相比于以往的pthread_mutex_t
方便了许多。
pthread_mutex_t
示例:
pthread_mutex_t m_mutex;
pthread_mutex_lock(&m_mutex);//加锁
do_something()
pthread_mutex_unlock(&m_mutex);//释放锁
mutex
分为四种:
std::mutex
:独占的互斥量,不能递归使用,不带超时功能std::recursive_mutex
:递归互斥量,可重入,不带超时功能std::timed_mutex
:带超时的互斥量,不能递归std::recursive_timed_mutex
:带超时的互斥量,可以递归使用
std::recursive_mutex
不同点在于,当前线程可以重复对互斥量进行上锁,即递归上锁,但是这个操作有一个前提就是,调用std::recursive_mutex
的lock
和unlock
次数得相同。当前线程持有std::recursive_mutex
时,可以重复调用std::recursive_mutex
不会阻塞。如果其他线程尝试持有std::recursive_mutex
的所有权,则会阻塞或者受到false
的返回值。
下面拿std::mutex
和std::timed_mutex
举例,别的都是类似的使用方式:
std::mutex
mutex
调用lock
时,调用的线程将锁住该互斥量。这时候存在有三种情况:
mutex
被其他线程锁住了,则当前线程则会阻塞在这里。mutex
没有被锁住的情况,则将当前mutex
进行锁住,直到调用unlock为止。mutex
调用try_lock
时,表示当前线程尝试锁住该mutex
,如果mutex
被其他线程持有,则当前线程不会阻塞在这里,这时候也会出现三种情况:
false
,并且不会阻塞。mutex
调用un_lock
时,解锁,当前线程释放对mutex的持有。示例代码:
#include
#include
#include
using namespace std;
std::mutex mutex_;
int main()
{
auto func1 = [](int k)
{
mutex_.lock();
for (int i = 0; i < k; ++i)
{
cout << i << " ";
}
cout << endl;
mutex_.unlock();
};
std::thread threads[5];
for (int i = 0; i < 5; ++i)
{
threads[i] = std::thread(func1, 200);
}
for (auto &th : threads)
{
th.join();
}
return 0;
}
std::timed_mutex
std::timed_mutex
设置了等待超时的机制,之前的互斥量如果无法等待进入机会,会一直阻塞线程,使用std::timed_mutex
可以为锁的等待设置一个超时值,一旦超时可以做其他事情。
通过阅读源码发现,std::time_mutex
比std::mutex
多了两个操作:
try_lock_for()
:尝试锁定互斥,若互斥在指定的时限时期中不可用则返回try_lock_until()
: 尝试锁定互斥,直至抵达指定时间点互斥不可用则返回示例1:
// test try_lock_for()
#include
#include
#include
#include
std::timed_mutex test_mutex;
void func()
{
auto now = std::chrono::steady_clock::now();
if (test_mutex.try_lock_for(std::chrono::seconds(1)))
{
std::cout << "success!\n";
test_mutex.unlock();
}
else
{
std::cout << "failed!\n";
}
std::cout << "hello world\n";
}
int main()
{
std::lock_guard<std::timed_mutex> l(test_mutex);
std::thread t(func);
t.join();
}
打印输出:
failed!
hello world
示例2:
// test try_lock_until()
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include
using namespace std;
std::timed_mutex test_mutex;
void func()
{
std::cout << "sub thread start\n";
auto now = std::chrono::steady_clock::now();
test_mutex.try_lock_until(now + std::chrono::seconds(5));
std::cout << "sub thread end\n";
}
int main()
{
std::cout << "main start\n";
std::lock_guard<std::timed_mutex> l(test_mutex);
std::thread t(func);
t.join();
std::cout << "main end\n";
}
打印输出:
main start
sub thread start
sub thread end
main end
在示例1和示例2中,用到了
std::lock_guard
主要是为了测试子线程一直未获取锁的情况,相当于锁被主线程拿了,等到main函数结束才释放。
c++11主要有std::lock_guard
和std::unique_lock
两种锁封装的方式,可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁。
std::lock_guard
在介绍std::timed_mutex
时其实已经展示了使用示例。std::unique_lock
的使用方式类似。
示例:
#include
#include
#include
#include
using namespace std;
std::mutex mutex_;
int main() {
auto func1 = [](int k) {
// std::lock_guard lock(mutex_);
std::unique_lock<std::mutex> lock(mutex_);
for (int i = 0; i < k; ++i) {
cout << i << " ";
}
cout << endl;
};
std::thread threads[5];
for (int i = 0; i < 5; ++i) {
threads[i] = std::thread(func1, 200);
}
for (auto& th : threads) {
th.join();
}
return 0;
}
std::lock_gurad
相比于std::unique_lock
更加轻量级,少了一些成员函数,std::unique_lock
类有unlock
函数,可以手动释放锁,所以条件变量都配合std::unique_lock
使用,而不是std::lock_guard
,因为条件变量在wait
时需要有手动释放锁的能力。