いけむランド

はてダからやってきました

puts の挙動

実はputsは、背後で、引数の出力と改行の出力の2つに処理を分割して実行します。このため、この2つの処理の間に別のスレッドがスケジュールされ、別のスレッドの出力が挟まれしまう可能性があります。(プログラミングRuby 第2版 言語編 p.119)

ちょっと気になったので、C とか Java でも調べてみた。

C

Manpage of PUTS を見てみると、

stdio ライブラリに含まれる出力関数と、同じ出力ストリームに結びつけられたファイルディスクリプタに対する write(2) の低レベル呼び出しを混在して使用することは賢明ではない。その結果は定義されておらず、望む結果が得られない可能性が高い。

とあることから、やっぱりスレッドとか間に入ったら、平気でぶった切られる仕様らしい。

念のために newlib/libc/stdio/puts.c で実装はどうなってるか確認してみた。

int
_DEFUN(_puts_r, (ptr, s),
       struct _reent *ptr _AND
       _CONST char * s)
{
  size_t c = strlen (s);
  struct __suio uio;
  struct __siov iov[2];

  iov[0].iov_base = s;
  iov[0].iov_len = c;
  iov[1].iov_base = "\n";
  iov[1].iov_len = 1;
  uio.uio_resid = c + 1;
  uio.uio_iov = &iov[0];
  uio.uio_iovcnt = 2;

  _REENT_SMALL_CHECK_INIT (ptr);
  return (__sfvwrite_r (ptr, _stdout_r (ptr), &uio) ? EOF : '\n');
}
#define GETIOV(extra_work) \
  while (len == 0) \
    { \
      extra_work; \
      p = iov->iov_base; \
      len = iov->iov_len; \
      iov++; \
    }

int
_DEFUN(__sfvwrite_r, (ptr, fp, uio),
       struct _reent *ptr _AND
       register FILE *fp _AND
       register struct __suio *uio)
{
  register size_t len;
  register _CONST char *p = NULL;
  register struct __siov *iov;
  register int w, s;
  char *nl;
  int nlknown, nldist;

  if ((len = uio->uio_resid) == 0)
    return 0;

  /* make sure we can write */
  if (cantwrite (ptr, fp))
    {
      fp->_flags |= __SERR;
      ptr->_errno = EBADF;
      return EOF;
    }

  iov = uio->uio_iov;
  len = 0;

#ifdef __SCLE
  if (fp->_flags & __SCLE) /* text mode */
    {
      do
        {
          GETIOV (;);
          while (len > 0)
            {
              if (putc (*p, fp) == EOF)
                return EOF;
              p++;
              len--;
              uio->uio_resid--;
            }
        }
      while (uio->uio_resid > 0);
      return 0;
    }
#endif
  :
  :

排他制御っぽいことはしていないように見える。それ以上はよくわからないので、保留。

Java

puts に相当するのは多分 java.io.PrintWriter#println() だと思うので、とりあえず classpathjava/io/PrintWriter.java で確認してみた。

  private static final char[] line_separator
    = System.getProperty("line.separator", "\n").toCharArray();

  public void println()
  {
    synchronized (lock)
      {
	try
	  {
	    write(line_separator, 0, line_separator.length);
	    if (autoflush)
	      out.flush();
	  }
	catch (IOException ex)
	  {
	    error = true;
	  }
      }
  }

  public void println(boolean bool)
  {
    synchronized (lock)
      {
	print(bool);
	println();
      }
  }

なるほど、ロックをかけて、改行が連続して出力されるようにしている。これだと文字列と改行の間にスレッドが割り込むことはないようである。