linux多线程服务端编程第四章-C++多线程系统编程精要 读后感
? linux ? ? c++ ? ? 多线程 ?   发布于 2018-12-08   605人围观  0条评论
? linux ? ? c++ ? ? 多线程 ?   发表于 2018-12-08   605人围观  0条评论

前言

最近在读陈硕的moduo网络库的书,记录总结一些东西。

这章内容比较散,主要介绍多线程系统编程需要理解的一些知识点。看完的整个感受就是可以直接看最后的总结就行了,中间有些内容不是非常能理解的透彻。

基本线程原语

基本线程原语只需要三样东西:thread、mutex、condition。

  1. thread的创建与销毁
  2. mutex的创建、销毁、加锁、解锁
  3. condition_variable的创建、销毁、通知、广播、等待

有了这三样的东西,可以完成任何多线程编程任务。通常,不会直接使用thread和condition_variable,取而代之的是,高级的编程构建,比如线程池,比如之前提到过的CountDownLatch用于主线程和其他线程之间的同步。

C++系统库的线程安全性

这一节中的内容有些东西我还没能够完全理解,现下只记录下一些可理解的内容。以后回过头来再看看。

C++的标准库容器和std::string都不是线程安全的,只有std::allocator保证是线程安全的。一方面的原因是为了避免不必要的性能开销,另一方面的原因是单个成员函数的线程安全并不具备可组合性。

C++标准库中的绝大多数泛型算法是线程安全的,因为这些都是无状态纯函数。只要输入区间是线程安全的,那么泛型算法就是线程安全的。

 

Linux上的线程标识

linux上的线程标识不适合使用pthread_t,原因很多,详情见书。取而代之,使用的是gettid(2)系统调用,其优势书中也写的很清楚。

为了避免效率问题,使用__thread关键字做了缓存,避免每次获取线程id时都需要执行一次系统调用。

线程的创建与销毁的守则

线程的创建

线程的创建需要遵循以下几个原则:

  1. 程序库不应该在未提前告知的情况下创建自己的背景线程
  2. 尽量用相同的方式创建线程
  3. 在进入main()函数之前不应该启动线程
  4. 程序中线程的创建最好能在初始化阶段全部完成,不要为了每个计算任务或者每个网络连接去实时创建线程

以上四点都比较直观,也很容器理解。

针对第一点,如果程序库需要使用背景线程,那么最好让使用者在初始化库时传入线程池或者event loop对象,这样做是为了方便统筹线程的数目和用途,避免低优先级程序的任务独占某个线程。如果程序库在未告知的情况下使用了额外线程,那么会使得我们在规划线程资源的时候漏算一部分,甚至可能使得关键任务的计算资源无法达到性能指标。

针对第二点,统一的方式创建线程,可以方便的监控活动线程的数目。

针对第三点,main函数之前会完成全局对象的构造,各个编译单元之间的对象构造顺序是不确定的,有可能线程创建的时候,所使用全局对象还未构造,导致难以debug的错误

针对第四点,如果为了每个计算任务或者每个网络连接去实时创建线程,那么在负载急剧增加的的时候,可能会使得机器失去正常响应,导致我们无法探查到出了什么问题。

线程的销毁

线程正常退出的唯一方式就是自然死亡,任何从外部强行终止线程的做法和想法都是不合适的。强行终止线程,那么线程就么有机会清理资源,也没有机会释放已经持有的锁。

如果一定要强行终止计算任务,那么可以在计算期间性的检查某个全局标识。如果一定要强行终止某个耗时很长的任务,那么考虑把该任务独立出来作为一个进程,杀死一个进程比杀死一个进程中的线程要安全的多。

如果可以做到线程的创建在初始化阶段全部完成,那么线程不必要销毁,伴随进程一直运行,避免资源释放、线程对象生命期管理等各种问题。

__thread关键字

__thread变量存取效率可以去全局变量相比,__thread变量是每个线程有一份独立实体,各个线程的变量值互不干扰。

书中有句话,我现在还没办法特别能理解,先留着,之后再回顾

除了这个主要用途,它还可以修饰那些“值可能会变,带有全局性,但是又不值得用全局锁保护”的变量。

多线程与IO

多个线程同时操作同一个文件描述符(读写)会带来很多问题,所以通常我们遵循的原则是,每个文件描述符只由一个线程操作,以解决消息收发的顺序性问题。

用RAII包装文件描述符

Linux文件描述符,刚刚启动时,0是标准输入,1是标准输出,2是标准错误。之后如果新开一个文件,其描述符就都会是3,因为POSIX标准要求每次新开文件都必须使用当前最小可用的文件描述符号码。这导致稍不注意就可能造成串话,因为可能前后时刻出出现同一个文件描述符。

主要解决方法就是RAII。比如说,用Socket对象包装文件描述符即可,所有读写通过该对象来操作,析构函数关闭文件描述符。即Socket对象村花,那么文件描述符则存在。

多线程与fork()

多线程程序一般不允许调用fork(),因为Linux的fork()只会克隆当前线程。所以...很危险

总结

  1. 线程数目的规划和CPU数目有关,和负载无关。
  2. 一个程序最好在一开始创建好所有需要的线程,并且反复使用,避免反复创建、销毁。
  3. 线程规划上,每个线程应该明确各自的职责,管IO的管IO,管计算的搞个线程池
  4. 线程之间的交互尽量简洁,最好只用消息传递(如BlockingQueue)。如果必须用锁,那么最好避免一个线程同时持有多把锁,以彻底防止死锁
  5. 预先考虑清楚被暴露给其他线程的对象的读写状况

上一篇: linux多线程服务端编程第五章-高效的多线程日志 读后感

下一篇: linux多线程服务端编程第三章-多线程服务器的适用场合与常用编程模型 读后感

立即登录,发表评论
没有帐号?立即注册
0 条评论