Python3のsleepは寝ていない

すごい、飲みすぎいて頭イタなっているときに妙なtweetがながれてきて、忘れていたことを思い出し…

それは、 Pythonのsleepを呼び出すと処理が停止してしまう という現象だったのですが…

問題の処理内容

HaskellのAsyncライブラリを弄っていたときに遭遇した現象で以下のような処理をサンプルで作成しました

  1. HaskellのcreateProcessで子プロセスのPythonコードをexecしPipeでStdin,Stdoutをつなげる
  2. Python側でテキストファイルを読み込み1行ずつStdoutへ出力
  3. Haskell側はStdinからPythonが書き込んだデータを読み込み出力する

という簡単なものです、以下にコードを貼っておきます。

Haskellのコード

createProcessで子プロセスを作成しPipeをつないで、readHandle関数をmapConcurrentlyで並列に動かすという単純なサンプルです

{-# LANGUAGE ScopedTypeVariables #-}

import System.IO
import System.Process
import Control.Concurrent.Async
import Control.Exception

main :: IO ()
main = do
  (_, Just hout, Just herr, hproc) <-
    createProcess
      (shell "./extern_command.py")
      { std_out = CreatePipe
      , std_err = CreatePipe
      }
  res <- mapConcurrently (readHandle 0) [hout, herr]
  putStrLn "=== res"
  mapM_ (putStrLn . show) res

stdOut :: Handle -> IO Int
stdOut = readHandle 0

stdErr :: Handle -> IO Int
stdErr = readHandle 0

readHandle :: Int -> Handle -> IO Int
readHandle c h = do
  end <- hIsEOF h
  case end of
    False -> do
      readInfo <- hGetLine h
      putStrLn readInfo
      readHandle (c+1) h
    _ -> return c

Pythonのコード

単純にファイルをOpenし1行づつ返すものです

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
import sys
import os

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)

def main():
    path = "test.txt"
    with open(path) as f:
        for s in f:
            time.sleep(2)
            sys.stdout.write(s)
        
if __name__ == '__main__':
    main()

test.txtファイル

ああああいいいいうううう
ええええおおおおかかかか
ききききくくくくけけけけ

実行

2秒ごとに1行づつ送り返してきます

~/Async $ runghc Async1.hs 
ああああいいいいうううう
ええええおおおおかかかか
ききききくくくくけけけけ
=== res
3
0
~//Async $ 

結果として、どこも悪くなく、Python3の ディフォルトバッファリングモードがブロックバッファリング ということで、 バッファリングするデータが多いと フラッシュまでに時間がかかり止まったように見えたということでした。

sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)

これを入れることによって、行バッファリングになるそうです、sleepつかってるのでそこでフリーズしたのかと勘違いしました。

ちなみに以下のPHPのコードは

#!/usr/bin/php
<?php

$f = fopen("test.txt", "r");
if($f) {
    fputs(STDERR, ">>>=== STDERR 1 put!\n");
    while($line = fgets($f)) {
        sleep(2);
        fputs(STDOUT,$line);
    }
    fputs(STDERR, ">>>=== STDERR 2 put!\n");
}
?>

これは行でフラッシュしてきます。

個人的には行の方がイメージ的に使いやすいのではないかと思いますが、この辺の動作はPython2から変わっているのかな、思い出して 「あぁー」 ってなったのですがすっきりしてよかったです。

Posted on 2021-05-26 07:18:26

はじめまして

静岡の極東の浜辺で何かをやっている人間です、基本的に季節を問わずBBQとオフロードをバイクで走るのがが大好きです、暇があれば何かを焼いているか走っています。どこかの浜で焼いている姿をみたら、gentooの話もで飲みながらしましょう...

Posted

Amazon

tags