每个 C++ 程序都含有至少一个线程,即运行main()的线程,它由 C++ 运行时(C++ runtime)系统启动。
发起线程
class Func
{
public:
void operator() () const
{
do_something();
}
};
Func f;
std::thread myThread(f);
// std::thread myThread(Func()); // 会被解释成函数声明(接收的参数是函数指针,所指向的函数没有参数传入,返回Func对象)
std::thread myThread((Func()));
std::thread myThread{Func()};
std::thread myThread([]{
do_something();
});
等待线程完成
在出现异常的情况下等待
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_) : t(t_) {}
~thread_guard()
{
if(t.joinable())
t.join();
}
thread_guard(thread_guard const&) = delete;
thread_guard& operator=(thread_guard const&) = delete;
};
struct func // 函数对象
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for(unsigned j=0; j<1000000; ++j)
{
do_something(i);
}
}
}
void f()
{
int some_local_state = 0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
}
在后台运行线程
向线程函数传递参数
void f(int i, std::string const& s); // 注意这里是 const
void not_oops(int some_param)
{
char buffer[1024];
sprintf(buffer, "%i", some_param);
std::thread t(f, 3, std::string(buffer)); // 使用 std:string 避免悬空指针
t.detach();
}
void update_data_for_widget(widget_id w, widget_data& data);
void oops_again(widget_id w)
{
widget_data data;
// std::thread t(update_data_for_widget, w, data); 编译失败
std::thread t(update_data_for_widget, w, std::ref(data));
t.join();
}
class X
{
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work, &my_x);
// 向线程转移动态对象的归属权:
void process_big_object(std::unique_ptr);
std::unique_ptr p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object, std::move(p));
移交线程归属权
void some_function();
void some_other_function();
std::thread t1(some_function); // 启动新线程,并使之关联t1
std::thread t2 = std::move(t1);
t1 = std::thread(some_other_function); // 下面说明①
std::thread t3;
t3 = std::move(t2);
t1 = std::move(t3); // 该赋值操作会终止整个程序,下面说明②
class joining_thread
{
std::thread t;
public:
joining_thread() noexcept = default;
template
explicit joining_thread(Callable&& func, Args&& ... args)
: t(std::forward(func), std::forward(args)...)
{}
explicit joining_thread(std::thread t_) noexcept
: t(std::move(t_))
{}
joining_thread(joining_thread&& other) noexcept
: t(std::move(other.t))
{}
joining_thread& operator=(joining_thread&& other) noexcept
{
if(joinable())
join();
t = std::move(other.t);
return *this;
}
joining_thread& operator=(std::thread other) noexcept
{
if(joinable())
join();
t = std::move(other);
return *this;
}
~joining_thread() noexcept
{
if(joinable())
join();
}
void swap(joining_thread& other) noexcept
{
t.swap(other.t);
}
std::thread::id get_id() const noexcept
{
return t.get_id();
}
bool joinable() const noexcept
{
return t.joinable();
}
void join()
{
t.join();
}
void detach()
{
t.detach();
}
std::thread& as_thread() noexcept
{
return t;
}
const std::thread& as_thread() const noexcept
{
return t;
}
};
void do_work(unsigned id);
void f()
{
std::vector threads;
for(unsigned i=0; i<20; ++i)
{
threads.emplace_back(do_work, i);
}
for(auto& entry : threads)
entry.join(); // 依次在各线程上调用join()函数
}
C++ 标准库的 std::thread::hardware_concurrency() 函数,它的返回值是一个指标,表示程序在各次运行中可真正并发的线程数量。这仅仅是一个指标,若信息无法获取,该函数则可能返回0。
识别线程
用互斥保护共享数据
线程安全的栈容器类
#include
#include
#include
#include
struct empty_stack : std::exception
{
const char* what() const throw();
}
template
class threadsafe_stack
{
private:
std::stack data;
mutable std::mutex m;
public:
threadsafe_stack() {}
threadsafe_stack(const threadsafe_stack& other)
{
std::lock_guard lock(other.m);
data = other.data; // 在构造函数的函数体内进行复制操作
}
threadsafe_stack& operator=(const threadsafe_stack&) = delete;
void push(T new_value)
{
std::lock_guard lock(m);
data.push(std::move(new_value));
}
std::shared_ptr pop()
{
std::lock_guard lock(m);
if(data.empty()) throw empty_stack();
std::shared_ptr const res(std::make_shared(data.top()));
data.pop();
return res;
}
void pop(T& value)
{
std::lock_guard lock(m);
if(data.empty()) throw empty_stack();
value = data.top();
data.pop();
}
bool empty() const
{
std::lock_guard lock(m);
return data.empty();
}
}
防范死锁的建议通常是,始终按相同顺序对两个互斥加锁。
运用 std::lock() 函数和 std::lock_guard<> 类模板,进行内部数据的互换操作
class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
class X
{
private:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd) : some_detail(sd) {}
friend void swap(X& lhs, X& rhs)
{
if(&lhs == &rhs)
return;
std::lock(lhs.m, rhs.m);
std::lock_guard lock_a(lhs.m, std::adopt_lock);
std::lock_guard lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
}
// C++17 的写法:
void swap(X& lhs, X& rhs)
{
if(&lhs == &rhs)
return;
std::scoped_lock guard(lhs.m, rhs.m); // 类模板参数推断
swap(lhs.some_detail, rhs.some_detail);
}
防范死锁的补充准则
hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
hierarchical_mutex other_mutex(6000);
int do_low_level_stuff();
int low_level_func()
{
std::lock_guard lk(low_level_mutex);
return do_low_level_stuff();
}
void high_level_stuff(int some_param);
int high_level_func()
{
// 先对 high_level_mutex 加锁,随后调用 low_level_func(),这两步符合加锁规则,因为互斥 high_level_mutex 所在的层级 10000 高于 low_level_mutex 所在的层级 5000。
std::lock_guard lk(high_level_mutex);
return high_level_stuff(low_level_func());
}
void thread_a()
{
high_level_func();
}
void do_other_stuff();
void other_stuff()
{
high_level_func();
do_other_stuff();
}
void thread_b() // 线程 b 在运行期出错。
{
std::lock_guard lk(other_mutex);
other_stuff();
}
class hierarchical_mutex
{
std:mutex internal_mutex;
unsigned long const hierarchy_value;
unsigned long previous_hierarchy_value;
static thread_local unsigned long this_thread_hierarchy_value;
void check_for_hierarchy_violation()
{
if(this_thread_hierarchy_value <= hierarchy_value)
{
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value()
{
previous_hierarchy_value = this_thread_hierarchy_value;
this_thread_hierarchy_value = hierarchy_value;
}
public:
explicit hierarchical_mutex(unsigned long value)
: hierarchy_value(value)
, previous_hierarchy_value(0)
{}
void lock()
{
check_for_hierarchy_violation();
internal_mutex.lock();
update_hierarchy_value();
}
void unlock()
{
if(this_thread_hierarchy_value != hierarchy_value)
throw std::logic_error("mutex hierarchy violated");
this_thread_hierarchy_value = previous_hierarchy_value; // 线程的层级按保存的值复原。
internal_mutex.unlock();
}
bool try_lock()
{
check_for_hierarchy_violation();
if(!internal_mutex.try_lock())
return false;
update_hierarchy_value();
return true;
}
};
thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);
运用 std::unique_lock<> 灵活加锁
void swap(X& lhs, X& rhs)
{
if(&lhs == &rhs)
return;
// 实例 std::defer_lock 将互斥保留为无锁状态
std::unique_lock lock_a(lhs.m, std::defer_lock);
std::unique_lock lock_b(rhs.m, std::defer_lock);
std::lock(lock_a, lock_b); // 到这里才对互斥加锁
swap(lhs.some_detail, rhs.some_detail);
}
在不同作用域之间转移互斥归属权
std::unique_lock get_lock()
{
extern std::mutex some_mutex;
std::unique_lock lk(some_mutex);
prepare_data();
return lk;
}
void process_data()
{
std::unique_lock lk(get_lock());
do_something();
}
按适合的粒度加锁
在初始化过程中保护共享数据
令所有线程共同调用 std::call_once() 函数,从而确保在该调用返回时,指针初始化由其中某线程安全且唯一地完成(通过适合的同步机制)。必要的同步数据则由 std::once_flag 实例存储,每个 std::once_flag 实例对应一次不同的初始化。相比显式使用互斥,std::call_once() 函数的额外开销往往更低,特别是在初始化已经完成的情况下,所以如果功能符合需求就应优先使用。
// 用互斥实现线程安全的延迟初始化:
std::shared_ptr resource_ptr;
std::mutex resource_mutex;
void foo()
{
std::unique_lock lk(resource_mutex);
if(!resource_ptr)
{
resource_ptr.reset(new some_resource);
}
lk.unlock();
resource_ptr->do_something();
}
// std::once_flag 类和 std::call_once() 函数的方式:
std::shared_ptr resource_ptr;
std::once_flag resource_flag;
void init_resource()
{
resource_ptr.reset(new some_resource);
}
void foo()
{
std::call_once(resource_flag, init_resource); // 初始化函数准确地被唯一一次调用
resource_ptr->do_something();
}
利用 std::call_once() 函数对类 X 的数据成员实施线程安全的延迟初始化
class X
{
private:
connection_info connection_details;
connection_handle connection;
std::once_flag connection_init_flag;
void open_connection()
{
connection = connection_manager.open(connection_details);
}
public:
X(connection_info const& connection_details_)
: connection_details(connection_details_)
{}
void send_data(data_packet const& data)
{
std::call_once(connection_init_flag, &X::open_connection, this);
}
data_packet receive_data()
{
std::call_once(connection_init_flag, &X::open_connection, this);
return connection.receive_data();
}
}
如果把局部变量声明成静态数据,那样便有可能让初始化过程出现条件竞争。根据 C++ 标准规定,只要控制流程第一次遇到静态数据的声明语句,变量即进行初始化。若多个线程同时调用同一函数,而它含有静态数据,则任意线程均可能首先到达其声明处,这就形成了条件竞争的隐患。C++11 解决了这个问题,规定初始化只会在某一线程上单独发生,在初始化完成之前,其他线程不会越过静态数据的声明而继续运行。
某些类的代码只需用到唯一一个全局实例,这种情形可用以下方法代替 std::call_once():
class my_class;
my_class& get_my_class_instance() // 线程安全的初始化,C++11 标准保证其正确性
{
static my_class instance;
return instance;
}
保护甚少更新的数据结构
递归加锁
线程间的同步操作很常见,C++ 标准库专门为之提供了处理工具:条件变量和 future。另外,并发技术规约还提供了新式的同步工具:线程闩(latch)和线程卡(barrier)。
让线程休眠100毫秒:
std::this_thread::sleep_for(std::chrono::milliseconds(100));
凭借条件变量等待条件成立
std::mutex mut;
std::queue data_queue;
std::condition_variable data_cond;
void data_preparation_thread() // 由线程乙运行
{
while(more_data_to_prepare())
{
data_chunk const data = prepare_data();
{
std::lock_guard lk(mut);
data_queue.push(data);
}
data_cond.notify_one();
}
}
void data_processing_thread() // 由线程甲运行
{
while(true)
{
std::unique_lock lk(mut);
data_cond.wait(lk, []{ return !data_queue.empty(); });
data_chunk data = data_queue.front();
dara_queue.pop();
lk.unlock();
process(data);
if(is_last_chunk(data))
break;
}
}
利用条件变量构建线程安全的队列
#include
#include
#include
#include
template
class threadsafe_queue
{
private:
mutable std::mutex mut; // 互斥必须用 mutable 修饰(针对 const 对象,准许其数据成员发生变动)
std::queue data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue()
{}
threadsafe_queue(threadsafe_queue const& other)
{
std::lock_guard lk(other.mut);
data_queue = other.data_queue;
}
void push(T new_value)
{
std::lock_guard lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}
void wait_and_pop(T& value)
{
std::unique_lock lk(mut);
data_cond.wait(lk, [this]{ return !data_queue.empty(); });
value = data_queue.front();
data_queue.pop();
}
std::shared_ptr wait_and_pop()
{
std::unique_lock lk(mut);
data_cond.wait(lk, [this]{ return !data_queue.empty(); });
std::shared_ptr res(std::make_shared(data_queue.front());
data_queue.pop();
return res;
}
bool try_pop(T& value)
{
std::lock_guard lk(mut);
if(data_queue.empty())
return false;
value = data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr try_pop()
{
std::lock_guard lk(mut);
if(data_queue.empty())
return std::shared_ptr();
std::shared_ptr res(std::make_shared(data_queue.front());
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard lk(mut);
return data_queue.empty();
}
}
使用 future 等待一次性事件发生
从后台任务返回值
#include
#include
int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
std::future the_answer = std::async(find_the_answer_to_ltuae);
do_other_stuff();
std::cout << "The answer is " << the_answer.get() << std::endl;
}
#include
#include
struct X
{
void foo(int, std::string const&);
std::string bar(std::string const&);
};
X x;
auto f1 = std::async(&X::foo, &x, 42, "hello"); // ①
auto f2 = std::async(&X::bar, x, "goodbye"); // ②
struct Y
{
double operator() (double);
}
Y y;
auto f3 = std::async(Y(), 3.141); // ③
auto f4 = std::async(std::ref(y), 2.718); // 调用 y(2.718)
X baz(X&);
std::async(baz, std::ref(x)); // 调用 baz(x)
class move_only
{
public:
move_only();
move_only(move_only&&);
move_only(move_only const&) = delete;
move_only& operator=(move_only&&);
move_only& operator=(move_only const&) = delete;
void operator() ();
};
auto f5 = std::async(move_only()); // ④
auto f6 = std::async(std::launch::async, Y(), 1.2); // 运行新线程
ayto f7 = std::async(std::launch::deferred, baz, std::ref(x)); // 在 wait() 或 get() 内部运行任务函数
auto f8 = std::async(std::launch::deferred | std::launch::async, baz, std::ref(x)); // 交由实现自行选择运行方式
auto f9 = std::async(baz, std::ref(x)); // 交由实现自行选择运行方式
f7.wait(); // 任务函数调用被延后到这里才运行
关联 future 实例和任务
// 定义特化的 sstd::packaged_task<> 类模板(定义不完全,只列出部分代码)
template<>
class packaged_task*, int)>
{
public:
template
explicit packaged_task(Callable&& f);
std::future get_future();
void operator() (std::vector*, int);
}
#include
#include
#include
#include
#include
std::mutex m;
std::deque> tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread()
{
while(!gui_shutdown_message_received())
{
get_and_process_gui_message();
std::packaged_task task;
{
std::lock_guard lk(m);
if(tasks.empty())
continue;
task = std::move(tasks.front());
tasks.pop_front();
}
task();
}
}
std::thread gui_bg_thread(gui_thread);
template
std::future post_task_for_gui_thread(Func f)
{
std::packaged_task task(f);
std::future res = task.get_future();
std::lock_guard lk(m);
tasks.push_back(std::move(task));
return res;
}
创建 std::promise
#include
void process_connections(connection_set& connections)
{
while(!done(connections))
{
for(connection_iterator connection = connections.begin(), end = connections.end(); connection != end; ++connection)
{
if(connection->has_incoming_data())
{
data_packet data = connection->incoming();
std::promise& p = connection->get_promise(data.id);
p.set_value(data.payload);
}
if(connection->has_outgoing_data())
{
outgoing_packet data = connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true);
}
}
}
}
将异常保存到 future 中
extern std::promise some_promise;
try
{
some_promise.set_value(calculate_value());
}
catch(...)
{
some_promise.set_exception(std::current_exception());
}
some_promise.set_exception(std::make_exception_ptr(std::logic_error("foo ")));
多个线程一起等待
std::promise p;
std::future f(p.get_future());
assert(f.valid()); // future 对象 f 有效
std::shared_future sf(std::move(f));
assert(!f.valid()); // 对象 f 不再有效
assert(sf.valid()); // 对象 sf 开始生效
std::promise p;
td::shared_future sf(p.get_future()); // 隐式转移归属权
std::promise< std::map::iterator> p;
auto sf = p.get_future().share();
限时等待
时钟类
时长类
using namespace std::chrono_literals;
auto one_day = 24h;
auto half_an_hour = 30min;
auto max_time_between_messages = 30ms;
std::chrono::milliseconds ms(54802);
std::chrono::seconds s = std::chrono::duration_cast(ms);
std::future f = std::async(some_task);
if(f.wait_for(std::chrono::milliseconds(35)) == std::future_status::ready)
do_something_with(f.get());
时间点类
auto start = std::chrono::high_resolution_clock::now();
do_something();
auto stop = std::chrono::high_resolution_clock::now();
std::cout << "do_something() took "
<< std::chrono::duration(stop-start).count()
<< " seconds" << std::endl;
#include
#include
#include
std::condition_variable cv;
bool done;
std::mutex m;
bool wait_stop()
{
auto const timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
std::unique_lock lk(m);
while(!done)
{
if(cv.wait_until(lk, timeout) == std::cv_status::timeout)
break;
}
return done;
}
接收超时时限的函数
C++ 标准库中接受超时时限的函数:
类/名字空间 | 函数 | 返回值 |
---|---|---|
std::this_thread 名字空间 | sleep_for(duration) sleep_until(time_point) |
无 |
std::condition_variable 或 std::condition_variable_any wait_for(lock,duration) wait_until(lock,time_point) |
std::cv_status::timeout 或 std::cv_status::no_timeout wait_for(lock,duration,predicate) wait_until(lock,time_point,predicate) |
Bool——被唤醒时断言的返回值 |
std::timed_mutex, std::recursive_timed_mutex 或 std::shared_timed_mutex | try_lock_for(duration) try_lock_until(time_point) |
Bool——若获取了锁,则返回 true,否则返回 false |
std::shared_timed_mutex | try_lock_shared_for(duration) try_lock_shared_until(time_point) |
Bool——若获取了锁,则返回 true,否则返回 false |
std::unique_lock unique_lock(lockable,duration) unique_lock(lockable,time_point) |
try_lock_for(duration) try_lock_until(time_point) |
无——如果在新构建的对象上获取了锁,那么 owns_lock() 返回 true,否则返回 false Bool——若获取了锁,则返回 true,否则返回 false |
std::shared_lock shared_lock(lockable,duration) shared_lock(lockable,time_point) |
try_lock_for(duration) try_lock_until(time_point) |
无——如果在新构建的对象上获取了锁,那么 owns_lock() 返回 true,否则返回 false Bool——若获取了锁,则返回 true,否则返回 false |
std::future或std::shared_future | wait_for(duration) wait_until(time_point) |
如果等待超时则返回 std::future_status::timeout 如果 future 已就绪则返回 std::future_status::ready 如果 future 上的函数按推迟方式执行,且尚未开始执行,则返回 std::future_status::deferred |
一种简化代码的途径是:在并发实战中使用非常贴近函数式编程的风格。线程间不会直接共享数据,而是由各任务分别预先备妥自己所需的数据,并借助 future 将结果发送到其他有需要的线程。
利用 future 进行函数式编程
术语“函数式编程”是指一种编程风格,函数调用的结果完全取决于参数,而不依赖任何外部状态。这源自数学概念中的函数,它的意义是,若我们以相同的参数调用一个函数两次,结果会完全一致。
future 对象可在线程间传递,所以一个计算任务可以依赖另一个任务的结果,却不必显式地访问共享数据。
函数式编程风格的快速排序:
// 快速排序的串行实现
template
std::list sequential_quick_sort(std::list input)
{
if(input.empty())
{
return input;
}
std::list result;
result.splice(result.begin(), input, input.begin());
T const& pivot = *result.begin();
auto divide_point = std::partition(input.begin(), input.end(),
[&](T const& t) { return t lower_part;
lower_part.splice(lower_part.end(), input, input.begin(), divide_point);
auto new_lower(sequential_quick_sort(std::move(lower_part)));
auto new_higher(sequential_quick_sort(std::move(input)));
result.splice(result.end(), new_higher);
result.splice(result.begin(), new_lower);
return result;
}
函数式编程风格的并行快速排序
// 运用 future 实现并行快速排序
template
std::list parallel_quick_sort(std::list input)
{
if(input.empty())
{
return input;
}
std::list result;
result.splice(result.begin(), input, input.begin());
T const& pivot = *result.begin();
auto divide_point = std::partition(input.begin(), input.end(),
[&](T const& t) { return t lower_part;
lower_part.splice(lower_part.end(), input, input.begin(), divide_point);
std::future> new_lower(std::async(¶llel_quick_sort, std::move(lower_part)));
auto new_higher(parallel_quick_sort(std::move(input)));
result.splice(result.end(), new_higher);
result.splice(result.begin(), new_lower.get());
return result;
}
使用消息传递进行同步
以下内容:略
对象和内存区域
struct my_data
{
int i;
double d;
unsigned bf1:10;
int bf2:25;
int bf3:0;
int bf4:9;
int i2;
char c1, c2;
std::string s;
};
改动序列
原子操作是不可分割的操作。
标准原子类型
操作 std::atomic_flag
// 采用 std::atomic_flag 实现自旋锁互斥
class spinlock_mutex
{
std::atomic_flag flag;
public:
spinlock_mutex()
: flag(ATOMIC_FLAG_INIT)
{}
void lock()
{
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock()
{
flag.clear(std::memory_order_release);
}
}
操作 std::atomic
std:atomic b;
bool x = b.load(std::memory_order_acquire);
b.store(true);
x = b.exchange(false, std::memory_order_acq_rel);
bool expected = false;
extern atomic b; // 由其他源文件的代码设定变量的值
while(!b.compare_exchange_weak(expected, true) && !expected);
操作 std::atomic
操作标准整数原子类型
泛化的 std::atomic<> 类模板
原子操作的非成员函数
std::shared_ptr p;
void process_global_data()
{
std::shared_ptr local = std::atomic_load(&p);
process_data();
}
void update_global_data()
{
std::shared_ptr local(new my_data);
std::atomic_store(&p, local);
}
同步操作和强制次序
// 利用原子操作强制其他非原子操作遵从预定次序
#include
#include
#include
std::vector data;
std::atomic data_ready(false);
void reader_thread()
{
while(!data_read.load()) // ①
{
std::this_thread::sleep(std::chrono::milliseconds(1));
}
std::cout << "The answer = " << data[0] << "\n"; // ②
}
void writer_thread()
{
data.push_back(42); // ③
data_ready = true; // ④
}
#include
void foo(int a, int b)
{
std::cout << a << ", " << b << std::endl;
}
int get_num()
{
static int i = 0;
return ++i;
}
int main()
{
foo(get_num(), get_num()); // get_num() 发生两次调用,但没有明确的先后次序
}
原子操作的内存次序——概述
原子操作的内存次序——先后一致次序
原子操作的内存次序——非先后一致次序
原子操作的内存次序——宽松次序
原子操作的内存次序——理解宽松次序
原子操作的内存次序——获取-释放次序
原子操作的内存次序——通过获取-释放次序传递同步
std::atomic data[5];
std::atomic sync1(false), sync2(false);
void thread_1()
{
data[0].store(42, std::memory_order_relaxed);
data[1].store(97, std::memory_order_relaxed);
data[2].store(17, std::memory_order_relaxed);
data[3].store(-141, std::memory_order_relaxed);
data[4].store(2003, std::memory_order_relaxed);
sync1.store(true, std::memory_order_release); // ① 设置 sync1 成立
}
void thread_2()
{
while(!sync1.load(std::memory_order_acquire)); // ② 一直循环,到 sync1 成立为止
sync2.store(true, std::memory_order_release); // ③ 设置 sync2 成立
}
void thread_3()
{
while(!sync2.load(std::memory_order_acquire)); // ④ 一直循环,到 sync2 成立为止
assert(data[0].load(std::memory_order_relaxed) == 42);
assert(data[1].load(std::memory_order_relaxed) == 97);
assert(data[2].load(std::memory_order_relaxed) == 17);
assert(data[3].load(std::memory_order_relaxed) == -141);
assert(data[4].load(std::memory_order_relaxed) == 2003);
}
原子操作的内存次序——获取-释放次序和 memory_order_consume 次序造成的数据依赖
释放序列和同步关系
栅栏
#include
#include
#include
std::atomic x,y;
std::atomic z;
void write_x_then_y()
{
x.store(true, std::memory_order_relaxed); // ①
std::atomic_thread_fence(std::memory_order_release); // ②
y.store(true, std::memory_order_relaxed); // ③
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed)); // ④
std::atomic_thread_fence(std::memory_order_acquire); // ⑤
if(x.load(std::memory_order_relaxed)) // ⑥
++z;
}
int main()
{
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load() != 0); // ⑦
}
强制非原子操作服从内存次序
略。
略。
略。
线程池最简单的实现形式是,采用数目固定的工作线程(往往与 std::thread::hardware_concurrency() 的返回值相等)。
由于 std::packaged_task<> 的实例仅能移动而不可复制,但 std::funtion<> 要求本身所含的函数对象能进行拷贝构造,因此任务队列的元素不能用 std::function<> 充当。我们必须定制自己的类作为代替,以包装函数,并处理只移型别。这个类其实就是一个包装可调用对象的简单类,对外可消除该对象的型别。这个类还具备函数调用操作符。
class function_wrapper
{
struct impl_base
{
virtual void call() = 0;
virtual ~impl_base() {}
};
std::unique_ptr impl;
template
struct impl_type : impl_base
{
F f;
impl_type(F&& f_)
: f(std::move(f_))
{}
void call()
{
f();
}
};
public:
template
function_wrapper(F&& f)
: impl(new impl_type(std::move(f)))
{}
void operator() ()
{
impl->call();
}
function_wrapper() = default;
function_wrapper(function_wrapper&& other)
: impl(std::move(other.impl))
{}
function_wrapper& operator=(function_wrapper&& other)
{
impl = std::move(other.impl);
return *this;
}
function_wrapper(const function_wrapper&) = delete;
function_wrapper(function_wrapper&) = delete;
function_wrapper& operator=(const function_wrapper&) = delete;
};
基于锁的队列,它支持任务窃取
class work_stealing_queue
{
private:
typedef function_wrapper data_type;
std::queue the_queue;
mutable std::mutex the_mutex;
public:
work_stealing_queue()
{}
work_stealing_queue(const work_stealing_queue& other) = delete;
work_stealing_queue& operator=(const work_stealing_queue& other) = delete;
void push(data_type data)
{
std::lock_guard lock(the_mutex);
the_queue.push_front(std::move(data));
}
bool empty() const
{
std::lock_guard lock(the_mutex);
return the_queue.empty();
}
bool try_pop(data_type& res)
{
std::lock_guard lock(the_mutex);
if(the_queue.empty())
{
return false;
}
res = std::move(the_queue.front());
the_queue.pop_front();
return true;
}
bool try_steal(data_type& res)
{
std::lock_guard lock(the_mutex);
if(the_queue.empty())
{
return false;
}
res = std::move(the_queue.back());
the_queue.pop_back();
return true;
}
};
利用任务窃取的线程池
class thread_pool
{
typedef function_wrapper task_type;
std::atomic_bool done;
threadsafe_queue pool_work_queue;
std::vector > queues;
std::vector threads;
join_threads joiner;
static thread_local work_stealing_queue* local_work_queue;
static thread_local unsigned my_index;
void worker_thread(unsigned my_index_)
{
my_index = my_index_;
local_work_queue = queues[my_index].get();
while(!done)
{
run_pending_task();
}
}
bool pop_task_from_local_queue(task_type& task)
{
return local_work_queue && local_work_queue->try_pop(task);
}
bool pop_task_from_pool_queue(task_type& task)
{
return pool_work_queue.try_pop(task);
}
bool pop_task_from_other_thread_queue(task_type& task)
{
for(unsigned i=0; itry_steal(task))
{
return true;
}
}
return false;
}
public:
thread_pool()
: done(false)
, joiner(threads)
{
unsigned const thread_count = std::thread::hardware_concurrency();
try
{
for(unsigned i=0; i(new work_stealing_queue));
}
for(unsigned i=0; i
std::future::type> submit(FunctionType f)
{
typedef typename std::result_of::type> result_type;
std::packaged_task task(f);
std::future res(task.get_future());
if(local_work_queue)
{
local_work_queue->push(std::move(task));
}
else
{
pool_work_queue.push(std::move(task));
}
return res;
}
void run_pending_task()
{
task_type task;
if(pop_task_from_local_queue(task)
|| pop_task_from_pool_queue(task)
|| pop_task_from_other_thread_queue(task))
{
task();
}
else
{
std::this_thread::yield();
}
}
};
中断线程
C++20 标准已正式引入了 std::jthread,其下管控的线程可通过这个类接受中断,还能自动汇合。
发起一个线程,以及把它中断
class interrupt_flag
{
public:
void set();
bool is_set() const;
};
thread_local interrupt_flag this_thread_interrupt_flag;
class interruptible_thread
{
std::thread internal_thread;
interrupt_flag* flag;
public:
template
interruptible_thread(FunctionType f)
{
std::promise p;
internal_thread = std::thread([f, &p]
{
p.set_value(&this_thread_interrupt_flag);
f();
});
flag = p.get_future().get();
}
void interrupt()
{
if(flag)
{
flag->set();
}
}
}
检测线程是否被中断
void interruption_point()
{
if(this_thread_interrupt_flag.is_set())
{
throw thread_interrupted();
}
}
void foo()
{
while(!done)
{
interruption_point();
process_next_item();
}
}
中断条件变量上的等待
class interrupt_flag
{
std::atomic flag;
std::condition_variable* thread_cond;
std::mutex set_clear_mutex;
public:
interrupt_flag()
: thread_cond(0)
{}
void set()
{
flag.store(true, std::memory_order_relaxed);
std::lock_guard lk(set_clear_mutex);
if(thread_cond)
{
thread_cond->notify_all();
}
}
bool is_set()
{
return flag.load(std::memory_order_relaxed);
}
void set_condition_variable(std::condition_variable& cv)
{
std::lock_guard lk(set_clear_mutex);
thread_cond = &cv;
}
void clear_condition_variable()
{
std::lock_guard lk(set_clear_mutex);
thread_cond = 0;
}
struct clear_cv_on_destruct
{
~clear_cv_on_destruct()
{
this_thread_interrupt_flag.clear_condition_variable();
}
};
};
void interruptible_wait(std::condition_variable& cv, std::unique_lock& lk)
{
interruption_point();
this_thread_interrupt_flag.set_condition_variable(cv);
interrupt_flag::clear_cv_on_destruct guard;
interruption_point();
cv.wait_for(lk, std::chrono::milliseconds(1));
interruption_point();
}
template
void interruptible_wait(std::condition_variable& cv, std::unique_lock& lk, Predicate pred)
{
interruption_point();
this_thread_interrupt_flag.set_condition_variable(cv);
interrupt_flag::clear_cv_on_destruct guard;
while(!this_thread_interrupt_flag.is_set() && !pred())
{
cv.wait_for(lk, std::chrono::milliseconds(1));
}
interruption_point();
}
中断条件变量 std::condition_variable_any 上的等待
class interrupt_flag
{
std::atomic flag;
std::condition_variable* thread_cond;
std::condition_variable_any* thread_cond_any;
std::mutex set_clear_mutex;
public:
interrupt_flag()
: thread_cond(0)
, thread_cond_any(0)
{}
void set()
{
flag.store(true, std::memory_order_relaxed);
std::lock_guard lk(set_clear_mutex);
if(thread_cond)
{
thread_cond->notify_all();
}
else if(thread_cond_any)
{
thread_cond_any->notify_all();
}
}
template
void wait(std::condition_variable_any& cv, Lockable& lk)
{
struct custom_lock
{
interrupt_flag* self;
Lockable& lk;
custom_lock(interrupt_flag* self_, std::condition_variable_any& cond, Lockable& lk_)
: self(self_)
, lk(lk_)
{
self->set_clear_mutex.lock();
self->thread_cond_any = &cond;
}
void unlock()
{
lk.unlock();
self->set_clear_mutex.unlock();
}
void lock()
{
std::lock(self->set_clear_mutex, lk);
}
~custom_lock()
{
self->thread_cond_any = 0;
self->set_clear_mutex.unlock();
}
};
custom_lock cl(this, cv, lk);
interruption_point();
cv.wait(cl);
interruption_point();
}
// 余下代码和上面 8.2 相同
};
template
void interruptible_wait(std::condition_variable_any& cv, Lockable& lk)
{
this_thread_interrupt_flag.wait(cv, lk);
}
中断其他阻塞型等待
// 现有一个 interruptible_wait() 函数的重载,它是 std::future<> 的成员函数
template
void interruptible_wait(std::future& uf)
{
while(!this_thread_interrupt_flag.is_set())
{
if(uf.wait_for(lk, std::chrono::milliseconds(1)) == std::future_status::ready)
break;
}
interruption_point();
}
处理中断
try{
do_something();
}
catch(thread_interrupted&){
handle_interruption();
}
internal_thread = std::thread([f, &p]
{
p.set_value(&this_thread_interrupt_flag);
try{
f();
}
catch(thread_interrupted const&){
}
});
在应用程序退出时中断后台任务
std::mutex config_mutex;
std::vector background_threads;
void background_thread(int disk_id)
{
while(true)
{
interruption_point();
fs_change fsc = get_fs_change(disk_id);
if(fsc.has_changes())
{
update_index(fsc);
}
}
}
void start_background_processing()
{
background_threads.push_back(interruptible_thread(background_thread, disk_1));
background_threads.push_back(interruptible_thread(background_thread, disk_2));
}
int main()
{
start_background_processing();
process_gui_until_exit();
std::unique_lock lk(config_mutex);
for(unsigned i=0; i
略。
略。
右值引用
int var = 42;
int& ref = var; // 创建名为 ref 的引用,指向的模目标是变量 var
int &i = 42; // 无法编译
int const& i = 42; // 我们一般都能将右值绑定到 const 左值引用上
int&& i = 42;
int j = 42;
int&& k = j; // 编译失败
移动语义
void process_copy(std::vector const& vec_)
{
std::vector vec(vec_);
vec.push_back(42);
}
void process_copy(std::vector && vec)
{
vec.push_back(42);
}
class X
{
private:
int* data;
public:
X()
: data(new int[1000000])
{}
~X()
{
delete [] data;
}
X(const X& other)
: data(new int[1000000])
{
std::copy(other.data, other.data+1000000, data);
}
X(X&& other)
: data(other.data)
{
other.data = nullptr;
}
}
X x1;
X x2 = std::move(x1);
X x3 = static_cast(x2);
void do_stuff(X&& x_)
{
X a(x_); // 复制构造
X b(std::move(x_)); // 移动构造
}
do_stuff(X()); // 正确,X() 生成一个匿名临时对象,作为右值与右值引用绑定
X x;
do_stuff(x); // 错误,具名对象 x 是左值,不能与右值引用绑定
右值引用和函数模板
template
void foo(T&& t)
{}
foo(42); // 调用 foo(42)
foo(3.14159); // 调用 foo(3.14159)
foo(std::string()); // 调用 foo(std::string())
int i = 42;
foo(i); // 调用 foo(i)
删除函数
默认函数
struct X
{
int a;
}
X x1; // x1.a 的值尚未确定
X x2 = X(); // x2.a == 0 必然成立
X::X() = default; // 默认初始化规则对成员 a 起作用
常量表达式函数
1、设定数组界限:
int bounds = 99;
int array[bounds]; // 错误,界限 bounds 不是常量表达式
const int bounds2 = 99;
int array2[bounds2]; // 正确,界限 bounds2 是常量表达式
2、设定非类型模板参数的值:
template
struct test
{};
test ia; // 错误,界限 bounds 不是常量表达式
test ia2; // 正确,界限 bounds2 是常量表达式
3、在定义某个类时,充当静态常量整型数据成员的初始化表达式:
class X
{
static const int the_answer = forty_two;
};
4、对于能够进行静态初始化的内建型别和聚合体,我们可以将常量表达式作为其初始化表达式:
struct my_aggregate
{
int a;
int b;
}
static my_aggregate ma1 = {forty_two, 123}; // 静态初始化
int dummy = 257;
static my_aggregate ma2 = {dummy, dummy}; // 动态初始化
5、只要采用本例示范的静态初始化方式,即可避免初始化的先后次序问题,从而防止条件竞争。
constexpr int square(int x)
{
return x*x;
}
int array[square(5)];
int dummy = 4;
int array[square(dummy)]; // 错误,dummy 不是常量表达式
constexpr 关键字和用户自定义型别
class CX
{
private:
int a;
int b;
public:
CX() = default;
constexpr CX(int a_, int b_)
: a(a_), b(b_)
{}
constexpr int get_a() const
{
return a;
}
constexpr int get_b()
{
return b;
}
constexpr int foo()
{
return a+b;
}
}
constexpr 对象
constexpr int i = 45; // 正确
constexpr std::string s("hello"); // 错误,std::string 不是字面值型别
int foo();
constexpr int j = foo(); // 错误,foo() 并未声明为 constexpr 函数
constexpr 函数要符合的条件
constexpr 与模板
lambda 函数
cond.wait(lk, []()->bool { return data_ready; });
std::funtion make_offseter(int offset)
{
return [=](int j) { return offset+j; };
}
auto f = [](auto x) { std::cout << "x=" << x << std::endl; };
f(42);
f("hello");
std::future spawn_async_task()
{
std::promise p;
auto f = p.get_future();
std::thread t([p = std::move(p)() { p.set_value(find_the_answer()); }]);
t.detach();
return f;
}
变参模板
template
class my_template
{};
template
class packaged_task;
template // ①
struct dummy3
{
std::tuple pointers; // ②
std::tuple ...> unique_pointers; // ③
}
template
thread::thread(CallableType&& func, Args&& ...args);
template
void bar(ArgsTypes&& ...args)
{
foo(std::forward(args)...);
}
自动推导变量的型别
线程局部变量
thread_local int x; // 线程局部变量,它以名字空间为作用域
class X
{
static thread_local std::string s; // 类的静态数据成员,该语句用于声明
}
static thread_local std::string X::s; // 该语句用于定义,类的静态数据成员应在外部另行定义
void foo()
{
thread_local std::vector v; // 普通的局部变量
}
类模板的参数推导
std::mutex m;
std::lock_guard guard(m); // 将推导出 std::lock_guard
std::mutex m1;
std::shard_mutex m2;
std::scoped_lock guard(m1, m2); // 将推导出 std::scoped_lock
C++11 增加的新特性包括静态断言(static assertion/static_assert)、强类型枚举(strongly typed enumeration/enum class)、委托构造(delegating constructor)函数、Unicode 编码支持、模板别名(template alias)和新式的统一初始化列表(uniform initialization sequence),以及许多相对细小的改变。