【问题标题】:Atomic syscall. Input/Output operations原子系统调用。输入/输出操作
【发布时间】:2016-08-27 17:40:53
【问题描述】:

我想使用无锁队列编写多线程安全记录器。记录线程将消息推送到队列,记录器将弹出它们并发送到输出。我考虑如何解决这个问题——发送到输出。 我想尽可能避免使用互斥锁/锁。 因此,假设我将使用 C++ 流写入文件/控制台。我们可以假设目标系统是 Linux。

好的,写入流必须只是 Unix write 提供的系统调用的包装器(也许是高级包装器)。据我所知,系统调用是原子的(只有一个进程可以同时执行系统调用)。因此,不使用锁来安全地写入文件是很诱人的。 但是write 是一个系统调用,但它不能保证写入“整个输出”。它返回成功写入文件的字节数。

基本上,我的问题是: 如何解决?是否可以避免互斥锁? (我认为这是不可能的)。并请注明我的考虑,我错了吗?

【问题讨论】:

  • 我不确定我是否完全理解问题的本质。你认为你在和谁竞争? “相互”排斥的前提是至少存在两方试图共享资源——你关心的第二方是谁?
  • 有两个线程竞争直播。
  • 好吧,你有这个精心设置的队列,明确的目的是只有一个线程实际执行 I/O。那你为什么要通过引入更多线程写入同一个文件来破坏你自己的设计呢?拥有那个队列有什么意义,如果你继续往文件中写入是免费的?
  • 但是为什么呢?这似乎完全违背了练习的重点。让一个线程完成对文件的所有写入:问题已解决。
  • 让我直说。您从一个问题开始:多个线程需要写入单个日志文件。为了解决这个问题,你引入了一个队列——所有线程都将它们的消息放入其中,然后一些东西会弹出它们并将它们写出来。然后你说 - 我想要多个线程来做这个弹出和写入。于是你又回到了你开始的地方——你有多个线程想要写入同一个文件。你离你的目标还差一步。如果有某种神奇的方式来协调这组新线程,您可以使用相同的方式来协调原始线程。

标签: c++ multithreading unix logging lock-free


【解决方案1】:

Igor 是对的:只需一个线程完成所有日志写入。请记住,内核必须进行锁定以同步对打开文件描述符(跟踪文件位置)的访问,因此通过从多个内核进行写入会导致内核内部争用。更糟糕的是,您正在从多个内核进行系统调用,这意味着内核的代码/数据访问会弄脏您在多个内核上的缓存。

有关在系统调用完成后进行系统调用对用户空间代码性能的影响的更多信息,请参阅this paper。 (以及关于不频繁系统调用的内核内部的数据/指令缓存未命中)。让一个线程执行所有系统调用(至少是所有写入系统调用)绝对是有意义的,以使您的进程占用空间的这一部分与一个内核隔离。以及内核内部的锁定争用。

那篇 FlexSC 论文是关于批处理系统调用以减少用户->内核->用户转换的想法,但它们也测量了正常同步系统调用方法的开销。更重要的是讨论系统调用造成的缓存污染。


或者,如果您可以让多个线程写入您的日志文件,您可以这样做而不使用队列。

不能保证大型写入会不间断地完成,但中小型写入应该(几乎?)在大多数操作系统上始终复制其整个缓冲区。特别是如果您正在写入文件,而不是管道。 IDK Linux write() 被抢占时的行为方式,但我希望它通常会恢复完成写入,而不是在未写入所有请求字节的情况下返回。当被信号中断时,部分写入可能更有可能发生。

保证来自两个write() 系统调用的字节不会混在一起;一个字节的所有字节都将在另一个字节之前或之后。不过,您说得对,部分写入是一个潜在的问题。我忘记了 glibc 系统调用包装器是否会在 EINTR 上为您恢复呼叫。虽然在这种情况下,这意味着实际上没有写入任何字节,或者它会返回成功的字节数。

您应该针对部分写入和性能进行测试。内核空间锁定可能比无锁队列的开销更便宜,但是从生成日志消息的每个线程进行系统调用可能会降低性能。 (当你测试这个时,确保你在你的用户空间进程中发生了一些实际的工作,而不仅仅是一个 only 调用 write 的循环。)

【讨论】:

  • 谢谢 :) “当你测试这个时,确保你在你的用户空间进程中发生了一些真正的工作,而不仅仅是一个只调用 write 的循环。)”是的,我'我遇到过 :)- 锁争用 :)
猜你喜欢
  • 2015-01-12
  • 2014-10-07
  • 1970-01-01
  • 2014-11-21
  • 2014-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多