基于C++从0到1手写Linux高性能网络编程框架(无密分享)

54 阅读4分钟

/xia栽ke:52xueit.com/582/

一个好的程序员是那种过单行线马路都要往两边看的人。

===

线程同步的本质是防止临界区(公共资源)并发操作,即多个线程禁止同时操作临界区。为此,在程序中以某种手段,将多个线程按照先后顺序访问临界区。

临界区的操作一直要保持谨慎。多线程访问临界区,同时读还好;假如一个写的同时,另一个读,那这个读的值可能不是确定的,有可能是写之前的也有可能是写之后的,这可能会引发重大bug,且难以排查。

本文主要介绍如何使用c++11中条件变量以及期望来设计并发操作。

「注」 本文示例代码过长只贴一部分,可在公众号输入标题获取本文源码。 条件变量 std::condition_variable 在多线程任务中,线程通常使用条件变量阻塞自身,直至条件发生。即A线程阻塞等待某个条件变量,B线程通知条件变量变化解除A线程阻塞。为了防止竞争,条件变量的使用总是和互斥锁结合在一起。 C++标准库对条件变量有两套实现: std::condition_variable 和 std::condition_variable_any。其中前者仅限于与std:mutex配合使用,后者可以和任何满足最低标准的互斥量一起使用。 从体积、性能以及系统资源的使用方面,推荐使用 std::condition_variable。仅当对灵活性有硬性要求的情况下,才会选择 std::condition_variable_any。 std::condition_variable 实例: arduino复制代码static std::mutex mut; static bool cond_value = false; static std::condition_variable cond; // 阻塞线程 void wait_thread() { std::unique_lockstd::mutex lk(mut); //a cond.wait( lk, []{ return cond_value; }); //lk.unlock(); // b //... //c } // 通知线程 void notify() { std::lock_guardstd::mutex lk(mut); cond_value = true; cond.notify_one(); }

流程: step 1. wait_thread先获取锁,然后查询cond_value为false时,先解锁,然后阻塞线程,等待其他线程通知。 step2. notify()获取锁,更改cond_value为true,并通过notify_one()通知阻塞的线程,并解锁。 step3. wait_thread接收到通知,重新获取锁,检查cond_value为true,从wait()返回(解除阻塞)。返回时仍处于持有锁状态,直至互斥锁被析构或者手动解锁。 在多线程中持有锁时间过长是一件糟糕的事情,当处理完与互斥锁相关的共享数据时,就应该立刻解锁。故上述c处还有其他业务时,b处有必要解锁。 另外,还存在多个线程等待同一事件。此种场景,可通过notify_all()通知所有阻塞的线程检查条件。 「注」:wait()会去检查这些条件(通过调用所提供的lambda函数), 当条件满足(lambda函数返回true)时返回。如果条件不满足(lambda函数返回false), wait()函数将解锁互斥量, 并且将这个线程(上段提到的处理数据的线程)置于阻塞或等待状态。当准备数据的线程调用notify_one()通知条件变量时, 处理数据的线程从睡眠状态中苏醒, 重新获取互斥锁, 并且对条件再次检查,在条件满足的情况下, 从wait()返回并继续持有锁。当条件不满足时, 线程将对互斥量解锁,并且重新开始等待。 期望 std::future 是指某个线程只等待一个特定的一次性事件。C++标准库将这种一次性事件称为“期望” (future)。 当一个线程需要等待一个特定的一次性事件时,在某种程度上来说它就需要知道这个事件在未来的表现形式。之后,这个线程会周期性(较短的周期)的等待或检查,事件是否触发(检查信息板);在检查期间也会执行其他任务。另外,在等待任务期间它可以先执行另外一些任务,直到对应的任务触发,而后等待期望的状态会变为“就绪”(ready)。 在C++标准库中, 有两种“期望”, 使用两种类型模板实现, 声明在头文件中: 唯一期望(uniquefutures)( std::future<> )和共享期望(shared futures)( std::shared_future<> )。这是仿照 std::unique_ptr 和 std::shared_ptr 。std::future 的实例只能与一个指定事件相关联,而 std::shared_future 的实例就能关联多个事件。后者的实现中, 所有实例会在同时变为就绪状态, 并且他们可以访问与事件相关的任何数据。 「注」: 以上两段描述,摘抄于《C++并发编程实战》   std::future并非单独使用,在C++标准库std::async、std::packaged_task和std::promise关联了std::future。即std::async、std::packaged_task和std::promise会返回std::future类型,线程通过std::future获取任务执行的结果。 了解这些,我们可以使用std::future程序上实现业务与任务的分离。即业务线程只负责处理逻辑,任务线程负责任务执行,业务线程又能获取到任务执行的结果或其他的设计。