Muduo学习

一、线程安全的对象生命期管理

  • 对象构造要做到线程安全,唯一的要求是在构造期间不要暴露this指针。如果this指针被泄漏给其他对象,那么别的线程有可能访问这个半成品对象,这会造成难以预料的后果。
  • 成员函数用来保护临界区的互斥器本身就是必须有效的,但析构函数会把互斥器销毁掉。
  • share_ptr的使用:传递参数的时候以const reference方式传递,值传递的时候会增减计数,开销大。可以通过模板参数指定析构对象执行的函数。
  • RAII:资源获取即初始化。每一个明确的资源配置动作都应该在单一语句中执行,并在该语句中立即将配置获得的资源交给handle对象。通常的做法是owner持有指向child的share_ptr,child持有指向owner的weak_ptr。
  • 弱回调:share_ptr可能会意外延长对象的生命期,所以可以考虑weak_ptr,可用share_ptr指向该weak_ptr,通过提升称为share_ptr判断weak_ptr是否为空,提升成功说明还建在。

二、线程同步精要

  • mute分为递归和非递归两种。
  • 虚假唤醒:即使没有signal或者broadcast,阻塞的条件变量也有可能唤醒。
  • 可以根据share_ptr实现读写锁。读端可在临界区定义新的指针指向该指针,然后怼新指针进行操作;写端如果该指针的unique()为false,说明别的线程正在读取,不能原地修改,而是在临界区中复制一份,退出临界区之后在副本上修改,改完了替换。(reset函数)。

三、多线程服务器使用场合和编程模型

  • 事件驱动模型的缺点,要求时间回调函数必须是非阻塞的。
  • one loop per thread:此种模型下,程序的每个IO线程有一个event loop, 用于处理读写和定时事件。eventloop 代表线程的主循环,需要让哪个线程干活,就把timer或者IO channel注册到那个线程的loop里;对实时性有要求的connection可以独占一个线程;对于数据量的可以独占一个线程,并把数据处理任务分到另几个计算线程中(用线程池)
  • 必须使用单线程的场合:程序可能会fork;限制程序的CPU使用率。
  • 适合多线程的场合:多个CPU可用;共享数据;相应有优先级差异。
  • 多线程服务器的分类:IO线程,主循环是IO复用;计算线程,主循环是阻塞队列(事件);第三方库线程,如日志。
  • linux能同时启用300个线程:3G/10M
  • 线程池适用:相应一次请求需要比较多的计算。如果主要时间在等待IO,需要换Proactor模型。
  • 阻抗匹配原则:任务密集计算的时间比例为P,CPU数量为C,线程池大小T = C/P。
  • 多进程和多线程的取舍:工作集较大使用多线程,避免cache换入换出,影响性能;否则就多进程。线程不能减少CPU时间。

四、C++多线程系统编程精要

  • 编写线程安全程序的一个难点在于线程安全是不可组合的。
  • pthread_self用于返回当前进程的描述符。pthread_t不一定是一个数据类型,也有可能是一个结构体。
    -强行终止线程的话,没有机会清理资源,也没机会释放已经持有的锁。
  • 如果确实要强行终止一个计算任务,可以考虑把那一部分代码 fork为新的进程。会比直接通过kill杀进程内的所有线程安全的多。
  • 如果确实需要主动结束线程,可以考虑exit(2)系统调用而不是exit,他不会试图析构全局对象,也不会执行其他清理工作。
  • __thread变量是每个线程有一个独立实体,各个线程的变量值互不干扰。它还可以修饰那些值可能会变,带有全局性,但是又不值得用全局锁保护的变量。
  • 多线程可以加速磁盘IO吗:只有内核缓存了大部分数据的情况下,才可以加速。因为每块磁盘都有一个操作队列,多线程请求是排队执行的。一种思路是每个磁盘配一个线程,把所有针对此磁盘的IO都挪到一个线程。
  • 多线程应遵循的:每个文件描述符只由一个线程操作。一个线程可以操作多个文件描述符,但一个线程不能操作别的线程拥有的文件描述符。
  • 新连接的文件描述符有可能等于之前意外断开的连接 ,会造成串话。muduo使用share_ptr管理生命期。
  • fork一般不能在多线程程序中调用,因为fork只克隆当前线程的thread of control,不克隆其他线程。fork之后,除了当前线程之外,其他线程都消失了。消失的线程可能会含有锁,位于某个临界区之内。唯一安全的做法是fork之后exec。
  • signal中只能调用可重入函数。

六、muduo网络库简介

  • tcp的发送完毕指将数据写入操作系统的缓存区,将由TCP协议栈负责数据的发送与重传,不代表对方已经收到数据。
  • 发送接收缓存的意义:发送或接收不完整时使用缓存。
  • 比较好的网络模型:单线程Reactor;主Reactor负责accept,把连接挂在某个辅Reactor中;主副Reactor加线程池处理计算任务。
  • 如果程序本身没有多少计算量,而主要瓶颈在网络带宽,可以只用一个event loop;如果IO带宽较小,计算量大,而且对延迟不敏感,可以将计算放到thread pool中,也可以只用一个event loop。
  • 如果有优先级之分,单个event loop可能不适合,把高优先级的连接用单独的event loop处理。

七、muduo编程实例

  • 大文件传输时,可考虑分块发送文件以减小内存的使用,采用share_ptr管理FILE*。对于一个文件多次连接,可以整个程序共享一个文件描述符,每个连接记住自己当前的偏移量。
  • muduo使用了半关闭,通过引用计数进行析构。
  • TCP分包四种方法:消息长度固定;特殊字符边界、头部加长度;利用消息本身格式分割。
  • 连接数过大,文件描述符不够的解决:准备一个空闲文件描述符,连接时关闭该描述符,然后利用该描述符指向新连接之后关闭;设置软件的文件描述符限制,超过了关闭即可。
  • 用time wheel踢掉空闲连接:多个桶组成循环队列,第n个桶存放n秒之后超时的连接。每个连接收到数据放到最后一个桶,定时把第一个桶的连接断开,空桶移到队尾。

八、muduo设计与实现

  • epoll使用水平触发(LT),所以read调用只需一次,而不用循环读知道返回eagain
  • 利用linux中的timerfd,可以采用处理IO方式来处理定时。
  • 线程安全性的问题,muduo的解决方法不是加锁,而是将操作都转移到IO线程执行,如果用户在当前线程中调用函数,回调会同步进行;如果是在其他线程,就加入IO队列,由IO线程唤醒。
  • 为了让IO线程尽快回调,传统的方法是使用pipe,需要唤醒时往pipe写一个字节,IO线程从阻塞中返回。
  • 读写带应用层缓冲。缓冲使用了发散集合IO,一部分缓冲取自stack,这样输入缓冲区足够大。
  • 客户端断开导致服务进程退出的解决:在程序开始时忽略SIGPIPE。
  • 发送接收缓冲溢出解决:提供高水位回调和低水位回调。
  • 连接重试的间隔应该逐渐增长,且具有随机性。
  • 自连接:没有显示调用的情况下,源ip、端口和目的有可能相同,需要断开连接再重试。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值