c++11/14特性整理---汇总(线程)
主要新特性有类的初始化与函数、新增关键字、std库新特性、lambda表达式、智能指针、线程等
由于内容太多拆分分几个章节讲解
c++11/14新特性—关键字
c++11/14新特性—函数语法
c++11/14新特性—智能指针
c++11/14新特性—线程
文章目录
多线程
构造函数
// 不执行任何处理动作
thread() noexcept;
// 将 other 的线程所有权转移给新的 thread 对象 之后 other 不再表示执行线程
thread( thread&& other ) noexcept;
// 创建线程对象,并在该线程中执行函数 f 中的业务逻辑,args 是要传递给函数 f 的参数
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
// 不允许线程对象之间的拷贝
thread( const thread& ) = delete;
获取线程id
std::thread::id get_id() const noexcept;
回收线程
void join();
分离线程
void detach();
判断主线程和子线程是否处理关联(连接)状态:
- 在创建的子线程对象的时候,如果没有指定任务函数,那么子线程不会启动,主线程和这个子线程也不会进行连接
- 在创建的子线程对象的时候,如果指定了任务函数,子线程启动并执行任务,主线程和这个子线程自动连接成功
- 子线程调用了detach()函数之后,父子线程分离,同时二者的连接断开,调用joinable()返回false
- 在子线程调用了join()函数,子线程中的任务函数继续执行,直到任务处理完毕,这时join()会清理(回收)当前子线程的相关资源,所以这个子线程和主线程的连接也就断开了,因此,调用join()之后再调用joinable()会返回false。
bool joinable() const noexcept;
赋值只能move资源,不能拷贝
// move (0k)
thread& operator= (thread&& other) noexcept;
// copy (x)
thread& operator= (const other&) = delete;
获取当前计算机的 CPU 核心数,根据这个结果在程序中创建出数量相等的线程,每个线程独自占有一个CPU核心,这些线程就不用分时复用CPU时间片,此时程序的并发效率是最高的。
static unsigned hardware_concurrency() noexcept;
使用例子:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void download1()
{
// 模拟下载, 总共耗时500ms,阻塞线程500ms
this_thread::sleep_for(chrono::milliseconds(500));
cout << "download1: " << this_thread::get_id() << endl;
}
void download2()
{
// 模拟下载, 总共耗时300ms,阻塞线程300ms
this_thread::sleep_for(chrono::milliseconds(300));
cout << "download2: " << this_thread::get_id() << endl;
}
void doSomething()
{
cout << "doSomething" << endl;
}
int main()
{
thread t1(download1);
thread t2(download2);
// 阻塞主线程,等待所有子线程任务执行完毕再继续向下执行
t1.join();
t2.join();
doSomething();
return 0;
}
结果:
download2: 4656
download1: 2780
doSomething
detach之后如果不模拟延时,线程应该来不及执行就结束了。
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void download1()
{
// 模拟下载, 总共耗时500ms,阻塞线程500ms
this_thread::sleep_for(chrono::milliseconds(500));
cout << "download1: " << this_thread::get_id() << endl;
}
void download2()
{
// 模拟下载, 总共耗时300ms,阻塞线程300ms
this_thread::sleep_for(chrono::milliseconds(300));
cout << "download2: " << this_thread::get_id() << endl;
}
void doSomething()
{
cout << "doSomething" << endl;
}
int main()
{
thread t1(download1);
thread t2(download2);
// 阻塞主线程,等待所有子线程任务执行完毕再继续向下执行
t1.detach();
t2.detach();
doSomething();
// 让主线程休眠, 等待子线程执行完毕
this_thread::sleep_for(chrono::seconds(5));
return 0;
}
结果:
doSomething
download2: 16728
download1: 6676
this_thread命名空间
当前线程的线程 ID,基本上在线程函数中使用。
thread::id get_id() noexcept;
线程从运行态变成阻塞态并在这种状态下休眠一定的时长,因为阻塞态的线程已经让出了 CPU 资源,代码也不会被执行,所以线程休眠过程中对 CPU 来说没有任何负担。
程序休眠完成之后,会从阻塞态重新变成就绪态,就绪态的线程需要再次争抢 CPU 时间片,抢到之后才会变成运行态,这时候程序才会继续向下运行。
template <class Rep, class Period>
void sleep_for (const chrono::duration<Rep,Period>& rel_time);
线程阻塞到某一个指定的时间点之后解除阻塞
template <class Clock, class Duration>
void sleep_until (const chrono::time_point<Clock,Duration>& abs_time);
运行态的线程主动让出自己已经抢到的 CPU 时间片,最终变为就绪态,进入下一轮 CPU 时间片抢夺。
避免一个线程长时间占用CPU资源,从而导致多线程处理性能下降。
void yield() noexcept;
互斥锁
互斥锁的个数和共享资源的个数相等,也就是说每一个共享资源都应该对应一个互斥锁对象。
互斥锁对象的个数和线程的个数没有关系。
std::mutex
最基本的互斥锁
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
using namespace std;
int g_num = 0; // 为 g_num_mutex 所保护
mutex g_mutex;
void slow_increment(int id)
{
for (int i = 0; i < 3; ++i)
{
g_mutex.lock();
++g_num;
cout << id << " => " << g_num << endl;
g_mutex.unlock();
this_thread::sleep_for(chrono::seconds(1));
}
}
int main()
{
thread t1(slow_increment, 0);
thread t2(slow_increment, 1);
t1.join();
t2.join();
return 0;
}
std::recursive_mutex
递归锁,允许同一线程多次获得互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
不建议使用,容易导致复杂的bug。
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx;
void recursive_function(int depth)
{
rmtx.lock();
std::cout << "Depth: " << depth << std::endl;
if (depth > 0)
{
recursive_function(depth - 1);
}
rmtx.unlock();
}
int main()
{
std::thread t(recursive_function, 5);
t.join();
return 0;
}
std::timed_mutex
带超时机制的互斥锁,在获取互斥锁资源时增加了超时等待功能,因为不知道获取锁资源需要等待多长时间,为了保证不一直等待下去,设置了一个超时时长,超时后线程就可以解除阻塞去做其他事情了。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex mtx;
void try_lock_function()
{
if (mtx.try_lock_for(std::chrono::seconds(1)))
{
std::cout << "Lock acquired!\n";
// 执行受保护的操作
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
mtx.unlock(); // 显式解锁
}
else
{
std::cout << "Failed to acquire lock within timeout.\n";
}
}
int main()
{
std::thread t1(try_lock_function);
std::thread t2(try_lock_function);
t1.join();
t2.join();
return 0;
}
std::recursive_timed_mutex
带超时机制的递归互斥锁,支持递归锁定和超时机制。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::recursive_timed_mutex mtx;
void recursive_lock_function()
{
if (mtx.try_lock_for(std::chrono::seconds(1)))
{
std::cout << "Lock acquired!\n";
// 递归锁定
if (mtx.try_lock_for(std::chrono::seconds(1)))
{
std::cout << "Recursive lock acquired!\n";
mtx.unlock(); // 释放递归锁
}
else
{
std::cout << "Failed to acquire recursive lock within timeout.\n";
}
// ... 执行受保护的操作
mtx.unlock(); // 释放原始锁
}
else
{
std::cout << "Failed to acquire lock within timeout.\n";
}
}
int main()
{
std::thread t1(recursive_lock_function);
std::thread t2(recursive_lock_function);
t1.join();
t2.join();
return 0;
}
std::shared_mutex
C++14引入的,读写锁,许多个线程同时读取,但只有一个线程可以写入。这在读多写少的场景下非常有用。
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex shmtx;
void read_shared(int id)
{
std::shared_lock<std::shared_mutex> lock(shmtx); // 共享锁
std::cout << "Thread " << id << " is reading" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
void write_shared(int id)
{
std::unique_lock<std::shared_mutex> lock(shmtx); // 独占锁
std::cout << "Thread " << id << " is writing" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
int main()
{
std::thread readers[5], writer(write_shared, 1);
for (int i = 0; i < 5; ++i) {
readers[i] = std::thread(read_shared, i + 2);
}
writer.join();
for (auto& reader : readers) {
reader.join();
}
return 0;
}
std::shared_timed_mutex
带超时机制的共享互斥锁。
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>
std::shared_timed_mutex shtmmtx;
void try_read_shared(int id)
{
if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100)))
{
std::cout << "Thread " << id << " is reading" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
shtmmtx.unlock_shared();
}
else
{
std::cout << "Thread " << id << " could not read" << std::endl;
}
}
void try_write_shared(int id)
{
if (shtmmtx.try_lock_for(std::chrono::milliseconds(100)))
{
std::cout << "Thread " << id << " is writing" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
shtmmtx.unlock();
}
else
{
std::cout << "Thread " << id << " could not write" << std::endl;
}
}
int main()
{
std::thread readers[5], writer(try_write_shared, 1);
for (int i = 0; i < 5; ++i)
{
readers[i] = std::thread(try_read_shared, i + 2);
}
writer.join();
for (auto& reader : readers)
{
reader.join();
}
return 0;
}
锁定策略
std::lock_guard
使用卫兵来上锁和解锁
// 类的定义,定义于头文件 <mutex>
template< class Mutex >
class lock_guard;
// 常用构造函数
explicit lock_guard( mutex_type& m );
通过修改发现代码被精简了,而且不用担心因为忘记解锁而造成程序的死锁,但是这种方式也有弊端,在上面的示例程序中整个for循环的体都被当做了临界区,多个线程是线性的执行临界区代码的,因此临界区越大程序效率越低,还是需要根据实际情况选择最优的解决方案。
void slow_increment(int id)
{
for (int i = 0; i < 3; ++i)
{
lock_guard<mutex> lock(g_mutex);
++g_num;
cout << id << " => " << g_num << endl;
this_thread::sleep_for(chrono::seconds(1));
}
}
锁定策略:
defer_lock— 不获取互斥锁的所有权adopt_lock— 假设调用线程已经拥有互斥锁的所有权
std::mutex m;
// 使用 defer_lock 策略
void using_defer_lock()
{
std::lock_guard<std::mutex> guard(m, std::defer_lock);
m.lock(); // 显式锁定
// 互斥区...
m.unlock(); // 显式解锁
}
// 使用 adopt_lock 策略
void using_adopt_lock()
{
m.lock(); // 在进入互斥区之前锁定
std::lock_guard<std::mutex> guard(m, std::adopt_lock);
// 互斥区...
}
std::unique_lock
由于lock_guard使用简单,所以不够灵活。
unique_lock允许手动锁定和解锁互斥量,以及与条件变量一起使用。当对象离开作用域时,也会自动解锁互斥量。
策略类型:
adopt_lockdefer_locktry_to_lock— 尝试在不阻塞的情况下获取互斥锁的所有权
// 使用 try_to_lock 策略
void using_try_to_lock()
{
std::unique_lock<std::mutex> guard(m, std::try_to_lock);
if (guard.owns_lock())
{
// 已经获得了锁
// 互斥区...
}
else
{
// 锁已被占用,执行其他操作或重试
}
}
std::shared_lock
C++14引入的锁,这是一个用于共享互斥量(如 std::shared_mutex 或 std::shared_timed_mutex)的锁,允许多个线程同时读取共享数据,但在写入数据时仍然保证互斥。
std::scoped_lock
C++17 引入的一个新锁,用于同时锁定多个互斥量,以避免死锁。
条件变量
另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来使用。
condition_variable 配合 unique_lock 使用更灵活一些,可以在任何时候自由地释放互斥锁,而 condition_variable_any 如果和 lock_guard 一起使用必须要等到其生命周期结束才能将互斥锁释放。但是,condition_variable_any 可以和多种互斥锁配合使用,应用场景也更广,而 condition_variable 只能和独占的非递归互斥锁(mutex)配合使用,有一定的局限性。
std::condition_variable
#include <condition_variable>
// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
// 在阻塞线程时候,如果线程已经对互斥锁 mutex 上锁,那么会将这把锁打开,这样做是为了避免死锁
// 当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个 mutex 互斥锁锁上,继续向下访问临界区
// 独占的互斥锁对象不能直接传递,需要通过模板类 unique_lock 进行二次处理
void wait (unique_lock<mutex>& lck);
// 第二个参数是一个判断条件,是一个返回值为布尔类型的函数
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
// 假设阻塞的线程没有被其他线程唤醒,当阻塞时长用完之后,线程就会自动解除阻塞,继续向下执行。
template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time);
template <class Rep, class Period, class Predicate>
bool wait_for(unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);
// 假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行。
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time);
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
// 唤醒一个被当前条件变量阻塞的线程
void notify_one() noexcept;
// 唤醒全部被当前条件变量阻塞的线程
void notify_all() noexcept;
生产者消费者例子
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <functional>
#include <condition_variable>
using namespace std;
class SyncQueue
{
public:
SyncQueue(int maxSize) : m_maxSize(maxSize) {}
void put(const int& x)
{
unique_lock<mutex> locker(m_mutex);
// 根据条件阻塞线程
m_notFull.wait(locker, [this]() {
return m_queue.size() != m_maxSize;
});
// 将任务放入到任务队列中
m_queue.push_back(x);
cout << x << " 被生产" << endl;
// 通知消费者去消费
m_notEmpty.notify_one();
}
int take()
{
unique_lock<mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this]() {
return !m_queue.empty();
});
// 从任务队列中取出任务(消费)
int x = m_queue.front();
m_queue.pop_front();
// 通知生产者去生产
m_notFull.notify_one();
cout << x << " 被消费" << endl;
return x;
}
bool empty()
{
lock_guard<mutex> locker(m_mutex);
return m_queue.empty();
}
bool full()
{
lock_guard<mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
int size()
{
lock_guard<mutex> locker(m_mutex);
return m_queue.size();
}
private:
list<int> m_queue; // 存储队列数据
mutex m_mutex; // 互斥锁
condition_variable m_notEmpty; // 不为空的条件变量
condition_variable m_notFull; // 没有满的条件变量
int m_maxSize; // 任务队列的最大任务个数
};
int main()
{
SyncQueue taskQ(50);
auto produce = bind(&SyncQueue::put, &taskQ, placeholders::_1);
auto consume = bind(&SyncQueue::take, &taskQ);
thread t1[3];
thread t2[3];
for (int i = 0; i < 3; ++i)
{
t1[i] = thread(produce, i + 100);
t2[i] = thread(consume);
}
for (int i = 0; i < 3; ++i)
{
t1[i].join();
t2[i].join();
}
return 0;
}
std::condition_variable_any
可以和任意带有 lock()、unlock() 语义的 mutex 搭配使用,也就是说上面介绍的四种都可以。
template <class Lock>
void wait (Lock& lck);
template <class Lock, class Predicate>
void wait (Lock& lck, Predicate pred);
template <class Lock, class Rep, class Period>
cv_status wait_for (Lock& lck, const chrono::duration<Rep,Period>& rel_time);
template <class Lock, class Rep, class Period, class Predicate>
bool wait_for (Lock& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);
template <class Lock, class Clock, class Duration>
cv_status wait_until (Lock& lck, const chrono::time_point<Clock,Duration>& abs_time);
template <class Lock, class Clock, class Duration, class Predicate>
bool wait_until (Lock& lck, const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
void notify_one() noexcept;
void notify_all() noexcept;
生产者消费者例子
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <functional>
#include <condition_variable>
using namespace std;
class SyncQueue
{
public:
SyncQueue(int maxSize) : m_maxSize(maxSize) {}
void put(const int& x)
{
lock_guard<mutex> locker(m_mutex);
// 根据条件阻塞线程
m_notFull.wait(m_mutex, [this]() {
return m_queue.size() != m_maxSize;
});
// 将任务放入到任务队列中
m_queue.push_back(x);
cout << x << " 被生产" << endl;
// 通知消费者去消费
m_notEmpty.notify_one();
}
int take()
{
lock_guard<mutex> locker(m_mutex);
m_notEmpty.wait(m_mutex, [this]() {
return !m_queue.empty();
});
// 从任务队列中取出任务(消费)
int x = m_queue.front();
m_queue.pop_front();
// 通知生产者去生产
m_notFull.notify_one();
cout << x << " 被消费" << endl;
return x;
}
bool empty()
{
lock_guard<mutex> locker(m_mutex);
return m_queue.empty();
}
bool full()
{
lock_guard<mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
int size()
{
lock_guard<mutex> locker(m_mutex);
return m_queue.size();
}
private:
list<int> m_queue; // 存储队列数据
mutex m_mutex; // 互斥锁
condition_variable_any m_notEmpty; // 不为空的条件变量
condition_variable_any m_notFull; // 没有满的条件变量
int m_maxSize; // 任务队列的最大任务个数
};
int main()
{
SyncQueue taskQ(50);
auto produce = bind(&SyncQueue::put, &taskQ, placeholders::_1);
auto consume = bind(&SyncQueue::take, &taskQ);
thread t1[3];
thread t2[3];
for (int i = 0; i < 3; ++i)
{
t1[i] = thread(produce, i + 100);
t2[i] = thread(consume);
}
for (int i = 0; i < 3; ++i)
{
t1[i].join();
t2[i].join();
}
return 0;
}
原子操作
原子操作是不可打断的最低粒度操作,是线程安全的。
对原子变量的操作可以分为三种:
store将一个值存到原子变量中。load读取原子变量中的值。read-modify-write(RMW) 原子地执行读取,修改和写入。如自增操作fetch_add,交换操作exchange(返回变量当前的值并写入指定值) 等。
Happens-before 是一个非常重要的概念,如果操作 a “happens-before” 操作 b, 则操作 a 的结果对于操作 b 可见。happens-before 的关系可以建立在用一个线程的两个操作之间,也可以建立在不同的线程的两个操作之间。
内存序:
- memory_order_relaxed — 宽松模型,不对执行顺序做保证
- memory_order_consume — 当前线程中,满足
happens-before原则,当前线程中该原子的所有后续操作,必须在本条操作完成之后执行 - memory_order_acquire — 当前线程中,读操作满足
happens-before原则,所有后续的读操作必须在本操作完成后执行 - memory_order_release — 当前线程中,写操作满足
happens-before原则,所有后续的写操作必须在本操作完成后执行 - memory_order_acq_rel — 当前线程中,同时满足memory_order_acquire和memory_order_release
- memory_order_seq_cst — 最强约束,全部读写都按顺序执行
原子类有:
- std::atomic_flag
- std::atomic
- std::atomic整数指针特化
实现一个锁:
struct LFLock
{
std::atomic_flag flag;
LFLock() : flag{false}
{
}
void lock()
{
while (flag.test_and_set(std::memory_order_acquire)) ;
}
bool try_lock()
{
return !flag.test_and_set(std::memory_order_acquire);
}
void unlock()
{
flag.clear(std::memory_order_release);
}
};
std::future
提供了一种机制,允许我们在多线程环境中安全地获取异步操作的结果,无需关注线程同步和通信的具体实现细节。
使用std::async关联异步任务,创建并运行一个异步任务,并返回一个与该任务结果关联的std::future对象。
std::future::get()函数只能被调用一次。
#include <iostream>
#include <future>
#include <chrono>
int long_running_task()
{
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
}
int main()
{
std::future<int> result_future = std::async(std::launch::async, long_running_task);
// 在此处执行其他操作
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
return 0;
}
使用std::promise与std::future配合,std::promise对象来显式地设置任务的结果,而std::future对象则用于获取这个结果。
#include <iostream>
#include <future>
#include <thread>
void long_running_task(std::promise<int> result_promise)
{
// 执行长时间运行的任务
int result = 42;
// 将结果设置到promise对象中
result_promise.set_value(result);
}
int main()
{
std::promise<int> result_promise;
std::future<int> result_future = result_promise.get_future();
// 创建一个新线程,执行长时间运行的任务
std::thread task_thread(long_running_task, std::move(result_promise));
// 在此处执行其他操作
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
task_thread.join();
return 0;
}
为了避免阻塞,我们可以使用std::future::wait_for()或std::future::wait_until()函数等待一段时间或直到某个时刻。这些函数会返回一个std::future_status枚举值,表示异步操作的状态
- std::future_status::ready
- std::future_status::timeout
- std::future_status::deferred
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
void long_running_task(std::promise<int> result_promise)
{
// 执行长时间运行的任务
std::this_thread::sleep_for(std::chrono::seconds(3));
int result = 42;
// 将结果设置到promise对象中
result_promise.set_value(result);
}
int main()
{
std::promise<int> result_promise;
std::future<int> result_future = result_promise.get_future();
// 创建一个新线程,执行长时间运行的任务
std::thread task_thread(long_running_task, std::move(result_promise));
while (true)
{
// 在此处执行其他操作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::chrono::milliseconds timeout(1);
std::future_status status = result_future.wait_for(timeout);
if (status == std::future_status::ready)
{
int result = result_future.get();
std::cout << "Result: " << result << std::endl;
break;
}
else
{
std::cerr << "Timeout: the task is still running" << std::endl;
}
}
task_thread.join();
return 0;
}
更多推荐


所有评论(0)