控制台输出是阻塞操作吗?

8
当Java程序调用System.out.println()或Scala程序调用println()时,线程会被阻塞吗?
我正在编写一个有大量子任务的Scala程序。每个子任务都在一个Future中执行。建议在actor和future中的代码不要阻塞,这样后续任务也不必等待。但我非常想在控制台上打印输出。
如果打印操作是阻塞的,那么我应该怎么做来优化性能呢?
  • 我应该为控制台输出使用专用线程,这样只有该线程会被阻塞吗?
  • 还有其他建议吗?
当然,我可以尝试减少输出量或将一些输出收集到StringBuilder中,并批量输出,以减少输出操作的次数。
2个回答

14
当Java程序调用System.out.println()或Scala程序调用println()时,线程会被阻塞吗? 是的和不是的。 System.out是一个同步类PrintStream。因此,多个线程向System.out写入大量内容肯定会相互阻塞。然而,一旦线程获取锁定,无论IO是否会阻塞线程取决于架构。如果你写入大量超出底层硬件容量的IO,则写入将会阻塞。此外,进行许多小的写入(而不是缓冲的写入)也会减慢线程。
我应该为控制台输出使用专用线程,这样线程就是唯一阻塞的吗? 非常好的想法,是的。那么这个线程可以通过单个BufferedWriter或某种log4j或其他日志记录软件包进行写入,这将与System.out相比更具有性能优势。您需要使用类似于BlockingQueue的东西来排队消息,它是同步的,但IO永远不会阻止此队列,除非您正在生成消息比IO通道可以保存它们的速度更快。
当然,我可以尝试减少输出量或将一些输出收集到StringBuilder中,并批量打印它们,以减少输出操作次数。 BufferedWriter将为您处理此操作。
还有其他建议吗? - 如上所述,使用更好的日志记录包或单线程编写器。 - 将日志写入具有更多IO带宽的不同物理磁盘。 - 切换到内存文件系统或硬件以增加IO带宽。 SSD ++。 - 通过网络将其发送到另一个框进行实际持久化解决。 - 使用GzipOutputStream即时压缩它。

将日志写入不同的文件系统。您是指不同的物理驱动器吗?或者,如果您的意思是一个文件系统比另一个更适合这些任务(例如ext*与btrfs),您能透露名称吗? - om-nom-nom
不,我真的是指IO链,而不是不同的文件系统类型@om-nom-nom。顺便说一句,你的用户名很棒。 :-) - Gray
我从未听说过System.out是“非阻塞”的。它不是基于java.nio实现的,是吗?如果将stdout重定向到慢设备上的文件并运行,我相当确定对System.out.println()的任何调用都会被阻塞,并且在写入完成之前不会返回。此外,我认为这与PrintWriter是同步类没有任何关系。您可以轻松创建一个执行阻塞IO的Java方法,而无需标记类或方法为“同步”。在最低级别上,有一个C或内核调用write(),它不会返回直到完成,阻塞线程直到它返回。 - faffaffaff
1
这取决于架构 @faffaffaff。所有 IO 操作在某种程度上都是阻塞的。我想要解释的是,在你填满要写入的设备的 IO 流之前(至少在 Unix 下),写操作不会阻塞线程。它只会被复制到内核缓冲区。我已经修改了我的答案。 - Gray
好的,但通常“非阻塞IO”有一个非常具体的含义(并且是作为一种特定模式/选项实现的),即要么调用立即执行,要么返回“写入失败,将会被阻止”,因此使用“在某种程度上阻塞”的表达有点奇怪。我认为,要么调用总是有阻塞的风险,要么保证永远不会阻塞(并可能因为不等待而失败)? - faffaffaff
1
从软件角度来看,这是正确的,但关于线程是否停止并等待IO完成的实际细节更加复杂。@faffaffaff。 OP正在询问多个线程记录性能的问题。这更多地涉及操作系统处理写操作的实际情况,而不是“阻塞”定义。 - Gray

5

这要看情况。在Windows操作系统中,它是一项阻塞性操作,需要涉及许多内核内容才能将某些内容打印到控制台。而在类Unix的操作系统中,该操作是缓冲的,所以不会被感知为慢。

我建议您采用缓冲区方法,并且单独开辟线程也是个好主意。或者,如果您的输出并不是很重要,您可以将其写入文件,这比写入控制台快得多。


还要记住,System.out不一定会打印到控制台(它可能已被重定向到带有“command > out.txt”的文件中)。 - faffaffaff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接