muduo库学习之C++多线程系统编程精要04——多线程与IO

东阳的学习笔记

在进行多线程网络编程的时候,几个自然的问题是:

  1. 如何处理IO?(一个文件只由一个进程中的一个线程来读写,这种做法显然是正确的)
  2. 能否多个线程同时读写同一个 socket 文件描述符?(最好不要
  3. 我们知道用多线程同时处理多个 socket 通常可以提高效率,那么处理同一个 socket 呢?

系统调用时线程安全的,但是使用不是线程安全的

  • 首先,**操作文件描述符的系统调用本身是线程安全的,**我们不用担心多个线程同时操作文件描述符会造成进程崩溃或内核崩溃

  • 但是,

    多个线程同时操作同一个socket文件描述符确实很麻烦,我认为是得不偿失的。需要考虑的情况如下:

    • 如果一个线程正在阻塞地read某个socket,而另一个线程close了此socket
    • 如果一个线程正在阻塞地accept某个listening socket,而另一个 线程close了此socket
    • 更糟糕的是,一个线程正准备read某个socket,而另一个线程 close了此socket;第三个线程又恰好open了另一个文件描述符,其 fd号码正好与前面的socket相同。这样程序的逻辑就混乱了(见下面的“用RAII包装文件描述符”)。
  • 我认为以上这几种情况都反映了程序逻辑设计上有问题

  • 现在假设不考虑关闭文件描述符,只考虑读和写,情况也不见得多好。

    因为socket读写的特点是不保证完整性,读100字节有可能只返回20字节,写操作也是一样的:

    • **如果两个线程同时read同一个TCP socket,**两个线程几乎同时各自收到一部分数据,如何把数据拼成完整的消息?如何知道哪部分数据先 到达?
    • **如果两个线程同时write同一个TCP socket,**每个线程都只发出去半条消息,那接收方收到数据如何处理?
    • **如果给每个TCP socket配一把锁,**让同时只能有一个线程读或写此socket,似乎可以“解决”问题,但这样还不如直接始终让同一个线程来操作此socket来得简单
    • **对于非阻塞IO,情况是一样的,**而且收发消息的完整性与原子性 几乎不可能用锁来保证,因为这样会阻塞其他IO线程
  • 如此看来,理论上只有read和write可以分到两个线程去,因为TCP socket是双向IO。问题是真的值得把read和write拆开成两个线程吗?

多线程不会加快磁盘IO

  • 以上讨论的都是网络IO,那么多线程可以加速磁盘IO吗?
    • 首先要避免lseek/ read的race condition(参阅前面的“C/C++系统库的安全性”)。做到这一点之后,据我看, 用多个线程read或write同一个文件也不会提速
    • 不仅如此,多个线程分别read或write同一个磁盘上的多个文件也不见得能提速。因为每块磁盘都有一个操作队列,多个线程的读写请求到了内核是排队执行的。只有在内核缓存了大部分数据的情况下,多线程读这些热数据才可能比单线程快
  • 多线程磁盘IO的一个思路是:每个磁盘配一个线程,把所有针对此磁盘的IO都挪到同一个线程,这样或许能避免或减少内核中的锁争用
  • 我认为应该用“显然是正确”的方式来编写程序,一个文件只由一个进程中的一个线程来读写,这种做法显然是正确的

多线程应遵守的原则

  • 为了简单起见,我认为多线程程序应该遵循的原则是:
    • 每个文件描述符只由一个线程操作,从而轻松解决消息收发的顺序性问题,也避免了关闭文件描述符的各种race condition
    • 一个线程可以操作多个文件描述符,但一个线程不能操作别的线程拥有的文件描述符
    • 这些不难做到,muduo网络库已经把这些细节封装了
  • epoll也遵循相同的原则:
    • Linux文档并没有说明:当一个线程正阻塞在epoll_ wait()上时,另一个线程往此epoll fd添加一个新的监视fd会发生什么。新fd上的事件会不会在此次epoll_wait()调用中返回?
    • 为了稳妥起见,我们应该把对同一个epoll fd的操作(添加、删除、修改、等待) **都放到同一个线程中执行,**这正是我们需要muduo::EventLoop::wakeup() 的原因
  • 这条规则有两个例外:
    • 对于磁盘文件,在必要的时候多个线程可以同时调用pread/pwrite来读写同一个文件(pread/pwrite参阅:https://blog.csdn.net/qq_41453285/article/details/88936714)
    • 对于UDP,由于协议本身保证消息的原子性,在适当的条件下(比如消息之间彼此独立)可以多个线程同时读写同一个UDP文件描述符

猜你喜欢

转载自blog.csdn.net/qq_22473333/article/details/113528787