本文转载自微信公众号《程序喵大师》,作者程序喵大师。转载本文请联系程序大师喵公众号。在平时的开发过程中,大家可能都会接触到多线程开发。其实多线程还是有很多方法的。1使用标准库中的并行算法:C++标准库中有大量的算法。C++17之后,超过60种算法支持并行执行,可以设置ExecutionPolicy策略。尽量使用这些并行算法,没必要自己写多线程相关的算法。2你可以使用C++11的std::thread而不是pthread。推荐std::thread没有其他性能原因,只是因为用起来很方便。std::thread配合lambda表达式创建线程运行,非常方便!直接join或者detach线程对象就很方便了!thread和mutex的std::unique_lock、std::lock_guard一起使用非常方便!使用条件变量非常方便!使用std::this_thread::sleep_for(xxx)休眠一定时间非常方便!3使用std::thread时,确保std::thread对象在生命周期结束前不可组合,即保证对象调用join()或detach()。不然程序会崩溃,为什么会这样?源码下,没什么秘密,这是std::thread的析构函数,一看就知道:~thread(){if(joinable())std::terminate();}C++20的std::jthread解决了这个问题,jthread在析构函数中会自动join()。其实不使用C++20,我们也可以自己封装一个std::threadwrapper来解决这个问题。4使用sleep(xxx)永远解决不了任何与时序相关的bug,必须使用条件变量来保证时序。5不要迷信多线程。我们需要清楚地知道为什么我们需要使用多线程。是为了更高的性能吗?还是为了不阻塞当前线程?还是有其他的考虑?考虑利弊,最好综合评估后再做决定。6最好的同步是不同步:尽可能以更合理的方式设计线程,让所有线程在使用共享数据时只能读不能写,或者只写其他线程不会读的部分,或者保证数据integrityOwnership是单线程模式,一次只有一个线程在访问这块数据,所以多线程编码会简单很多,不会出现数据竞争,也不会有死锁等问题。7、先考虑原子类型再考虑锁:通过原子类型或原子操作来编写没有数据竞争和死锁的代码会更方便,因为它们可以自动处理同步问题。如果您不能使用原子类型或原子操作,请考虑使用互斥体来保护临界区。(我看到有些大佬不推荐原子操作,但是没说为什么,有什么顾虑吗,可以留言聊聊。)8确保同步问题解决后再考虑优化:典型就是普通的互斥锁和读写锁的问题。很多人使用读写锁是为了追求更高的性能。除非读操作比写操作频繁得多,否则读写锁不会提高性能。我见过很多因为使用读写锁而出现同步问题的Case。所以,当你开始写代码的时候,就应该停止使用普通的锁,真正需要优化的时候再考虑使用其他的方法。9使用RAII锁对象:使用lock_guard、unique_lock、shared_lock或scoped_lock等RAII类来管理锁,这样可以保证锁会被释放。为了降低死锁的风险,但是我们也需要明白,如果出现了死锁,我们如何定位呢?再问一个问题:我们都知道加锁的顺序可能会导致死锁。如果释放锁,顺序不一致会不会导致死锁?10尽快释放锁:当需要通过锁来保护共享数据时,必须尽快释放锁,尽可能降低锁控制的粒度,明确哪些数据需要加锁,哪些不需要加锁根本不需要。脑锁。因为当一个线程持有锁时,会导致其他线程阻塞等待锁,这可能会降低程序的性能。11使用线程池:动态频繁的创建和销毁大量线程会导致性能下降。在这种情况下,最好使用线程池来重用现有的线程。之前写过一篇关于如何使用线程池的文章。你可以检查一下。12如果确实需要共享数据,尽量使用通信而不是共享内存。可以使用队列,通信队列必要时可以考虑使用阻塞队列代替while(!queue.empty()){xxx}。13做好日志记录:使用多线程程序容易出现各种问题,问题不稳定又重现,而且重现的时间往往不同。一定要做好日志记录,以保证在出现问题时有据可查,能够快速分析问题。
