前言
最近在读陈硕的moduo网络库的书,记录总结一些东西。
读完本章,最大的收获是无论是CPU密集型还是IO密集型,多线程相对于多进程都没有绝对的性能优势。多线程相对于单线程的真正优势是降低平均响应时间。此外,进一步了解最常用的Reactor模型和多线程的一些细节上的东西。
多线程的主要适用场景是计算与IO叠加进行的场景,多进程的主要适用场景是各项任务较为独立的场景。没有银弹,选择多进程还是多线程,需要根据针对不同的场景做不同的设计。
进程与线程
进程与线程之间的区别是老生常谈的知识点,也是我们在设计一个系统时首先要考虑的点。今年阅读和编写了很多多进程和多线程的代码,也看了很多多线程和多进程编程的文章。个人对这两个东西的理解有了很大的提升。下面叙述最近醒悟的几个点:
① 之前我一直以为UI应用就应该使用多线程模型,其实大错特错。
的确,UI应用一般会有一个UI线程用来事件循环界面上的事件,但是其他脏活累活既可以扔给其他线程、也可以扔给其他进程,前者需要考虑线程之间的逻辑关系,而后者考虑的是进程之间的逻辑关系。
② 如果某个多任务应用需要适应单核环境,那么这时候应该使用状态机还是多线程?
单核环境下,状态机的好处是手动调度可以获取更好的性能,但是协调各个任务之间的关系略显蛋疼。相对来说,多线程更加直观,但是线程在单核上的上下文切换以及同步就比较糟蹋性能了。所以说,具体问题具体分析,特殊情况特殊对待,设计系统这种东西,要看场景,要讲经验。
③ 多核环境下呢?
多核环境下,状态机和多线程,当然首选多线程,这样可以更好的发挥多核处理器的性能。那么既然是多核了,直接上多进程来替代多线程岂不更好?问题来了,单核环境下多线程没有状态机高效,多核环境下多线程没有多进程高效,那么多线程的真正优势是什么?
③ 多线程的真正优势
多线程相对于单进程的真正优势是降低响应时间,提高响应速度。但是,多线程与多进程之间的选择往往视情况而定,这里引用书中原话“在其他条件相同的情况下,可以根据工作集合(work set)的大小来取舍,工作集指服务程序响应一次请求所访问的内存大小。如果工作集较大,那么就用多线程,避免CPU cache换入换出,影响性能;否则,就用单线程多进程,享受单线程编程的遍历。”
即,多线程和多进程的共享内存同步和通信都比较麻烦,但是如果工作集较大,则多线程避免cpu cached换入换出。但是如果用不了
前言
最近在读陈硕的moduo网络库的书,记录总结一些东西。在并发编程中,有两种基本模型,一种是message passing,另外一种是Shared memory。这一章主要讲述shared memory模型下线程同步需要注意的一些问题。作者陈硕推荐尽可能使用message passing,因为其更容易保证程序的正确性,并且可以移植到分布式系统中,扩容性更强。但是多线程编程仍然需要程序员了解shared memory情形,以备不时之需。
本章题名为“线程同步精要”,又在文章开头提到并发编程的两种基本模型,私以为,线程同步精要指的单单是shared memory,只有线程间共享的资源,才需要采取同步措施来保证资源的正确访问。本章作者分享的是他个人在线程同步方面的经验与心得。
线程同步的四项原则
文中按照重要性先后列出线程同步的四项原则
- 首要原则是尽量最低限度地共享对象,减少需要同步的场合。一个对象能不暴露给别的线程就不要暴露;如果要暴露,优先考虑immutable对象;实在不行才暴露可修改的对象,并用同步措施来充分的保护它。
- 其次是使用高级的并发编程构件,如TaskQueue、Producer-Consumer Queue、CountDownLatch等等。
- 最后不得已必须使用底层同步原语(primitives)时,只用非递归的互斥器和条件变量,慎用读写锁,不要用信号量。
- 除了使用atomic整数之外,不自己编写lock-free代码,也不要用“内核级”同步原语。不凭空猜测“哪种做法性能更好”,比如“spin lock vs.
原则第三条
互斥器
互斥器,很简单,就是RAII收发,常见的MutexLock和MutexLockGuard手法,MutexLock负责锁的创建和销毁以及判断是否被锁,MutexLockGuard负责加锁解锁(即临界区的进入和退出)。
作者推荐使用非递归锁,因为同一个线程中多次对非递
前言
在c++多线程编程中,线程间可能会使用跨线程对象,那么对象在什么时候销毁便成了一个让人头疼的问题,因为一个线程在使用这个对象时,另外一个线程可能正在析构这个对象。该章内容就是为我们解读,如何正确的处理跨线程对象的生命周期,以避免内存泄露、空悬指针或者使得程序处于不确定状态等问题。
线程安全
什么是线程安全?这里给出书中的定义:
一个线程安全的class应当满足以下三个条件:
- 多个线程同时访问时,能够表现出正确的行为
- 无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织
- 调用段代码无须额外的同步或者其他协调动作
依据这个定义,c++标准库中大多数class都不是线程安全的,包括std::string、std::vector、std::map等,因为这些class通常需要在外部加锁才能供多个线程同时访问
个人的理解就是,一个线程安全的类,即使不用在外部加锁,也可以在多个线程中同时访问,想想,实现这样的类,就很难,这也是为什么多线程编程是我一直想去积累一些东西的原因,这是座山,而我必须得翻过。
多线程中的析构与智能指针
由于在多线程环境中,一个对象可以同时被多个线程访问,这导致析构该对象的时机变得模糊不清。很有可能该对象在一个线程中被访问,而同时在另外一个线程中被析构,这些都会造成程序处于未知的状态。那么我们是否可以加锁来保证析构时,其他线程不会访问呢?遗憾的是,析构时锁作为对象的一部分也会被析构,这样就无法对临界区进行保护。
实际上,我们在多线程编程使用原始指针时,不得不思考一个问题,那就是什么时候对象的析构才是安全的?只有其他线程都访问不到该对象的时候才是安全的。自然而然,我们可以想到使用智能指针来引入一层间接性的引用计数。对于跨线程对象而言,最好使用标准库中的智能指针进行处理,智能指针的核心是引用计数。智能指针的思想符合用对象管理资源的思路,实际上,现代c++程序一般不会出现内存泄露的情况,除非特别情况,一般都会使用容器或者智能指针来管理资源。
用智能指针代替原始指针的一大好处是原始指针无法判断所指内存上的对象是否销毁,而只要有智能指针引用计数不为0,所指涉内存上的对象便不会被销毁。
观察者模式与智能指针
#include <algorithm> #include <vector> #include <stdio.h> cl