実現したいこと
外部のexeファイルと文字列の入出力のやり取りをするpythonのコードにconcurrent.futuresを使えるようにして関数をマルチスレッド化したい。スレッドの最大数を制限し、終了したスレッドが発生次第スレッドをまた動かすことを繰り返したいです。
前提
subprocess.Popenを用いて、外部のexeファイルと文字列の入出力のやり取りをするプログラムを書いています。外部のexeは詰将棋を解答するプログラム(脊尾詰)で、こちらのURLでダウンロードしたものをそのまま使っています。
http://panashogi.web.fc2.com/seotsume.html
私が書いているexeファイルは、usiプロトコルに従って文字列の入出力を行います。
http://shogidokoro.starfree.jp/usi.html#ProblemExample
参考URLの要点だけを説明すると、私のプログラムで行うexeへの入出力とその説明は以下の通りです。
usi\n:詰将棋エンジン起動。これをexeへ入力すると、usiokを含む文字列がexeから出力される
setoption name USI_Hash value 512\n:詰将棋エンジンに関する設定を変更
setoption name Do_YoTsume_Search value false\n:詰将棋エンジンに関する設定を変更
isready\n:詰将棋解答開始前の準備。これをexeへ入力すると、readyokを含む文字列がexeから出力される
usinewgame\n:これをexeへ入力することで、exeは詰将棋盤面の文字列の入力待ち状態に移行する。
position sfen…\n:詰将棋の盤面をexeへ入力。
go mate infinite\n:これをexeへ入力することで、exeは入力された詰将棋を解答し始める。結果が出ると'checkmate'を含む文字列を出力する。終了すると盤面の入力待ち状態に戻り、再び"position sfen..."コマンドを待つ状態になる。
私のコードでは、1つ目の関数(funcX)でusinewgame\nまでexeに入力してexeを盤面の入力待ち状態に移行させた後、2つ目の関数(funcY)で詰将棋の盤面をexeに入力して解答結果を得る、という構成になっていて、1回だけfuncXを実行してその後はfuncYを繰り返し実行しています。スレッドの最大数を制限しつつ、funcXとfuncYをマルチスレッド化(orマルチプロセス化)し、複数の詰将棋を複数の詰将棋解答exeに入力して、解答が終了したものから解答結果を得ていくようにしたいです。あるスレッドで関数Yが終了したら、スレッドをまた動かして別の盤面を指定したfuncYを実行して…ということをやりたいです。
Python
1import subprocess 2import keyboard 3import concurrent.futures 4 5def main(): 6 #list1は、詰将棋の盤面を表現した文字列を集めてリストにしたもの。非本質なので気にしないでください 7 list1=['position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b R2G2Nr2b2g4s3l15p 1\n', 8 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RGS2NPr2b3g3s3l14p 1\n', 9 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RSNLr2b4g3sn2l15p 1\n', 10 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RSNPr2b4g3sn3l14p 1\n', 11 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RSN2Pr2b4g3sn3l13p 1\n', 12 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RBSNrb4g3sn3l15p 1\n', 13 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RBSNLrb4g3sn2l15p 1\n', 14 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RBGNrb3g4sn3l15p 1\n', 15 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b R2BNr4g4sn3l15p 1\n', 16 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b B2GS2rb2g3s2n3l15p 1\n', 17 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b BG2SL2rb3g2s2n2l15p 1\n', 18 'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b 2B3S2L2r4gs2nl15p 1\n', 19 'position sfen 2n3p2/5+r1p1/7+pk/3p5/3+p5/9/9/6L2/9 b R4GNP2b4s2n3l12p 1\n'] 20 21 file_to_path1="SeoTsume_1.exe"#exeファイルのパス 22 file_to_path2="SeoTsume_2.exe"#exeファイルのパス2 23 proc1=subprocess.Popen(file_to_path1,stdin=subprocess.PIPE,stdout=subprocess.PIPE)#サブプロセスを起動しておく。exeが起動される 24 proc2=subprocess.Popen(file_to_path2,stdin=subprocess.PIPE,stdout=subprocess.PIPE)#マルチスレッド用にexeをコピーした別のexeを用意して起動。 25 26 funcX(proc1)#funcXは最初に一度だけやっておかなければならない入出力のやり取りの関数 27 funcX(proc2) 28 num=0 29 while(num<len(list1)-1): 30 with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:#マルチスレッド化したい 31 executor.submit(funcY,proc1,list1[num]) #funcYの引数として、funcXを介して盤面の入力待ち状態になったPopenオブジェクトと、盤面の文字列list1[num]を指定する 32 executor.submit(funcY,proc2,list1[num+1]) 33 num+=2 34 35def funcX(proc):#exeファイルを盤面の入力待ち状態に移行させるために最初に一度だけ実行しておく関数 36 proc.stdin.write(('usi\n').encode())#pyからexeファイルへ文字列"usi\n"を入力 37 proc.stdin.flush()#flushは必須 38 39 while True: 40 line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ受け取る 41 if line == 'usiok':#exeからpyへ"usiok"という文字列が出力された場合、出力の受け取りを中断する。 42 print(line) 43 break 44 45 proc.stdin.write(('setoption name USI_Hash value 512\n').encode())#pyからexeファイルへ文字列を入力。 46 proc.stdin.flush() 47 48 proc.stdin.write(('setoption name Do_YoTsume_Search value false\n').encode())#pyからexeファイルへ文字列を入力。 49 proc.stdin.flush() 50 51 proc.stdin.write(('isready\n').encode())#pyからexeファイルへ文字列を入力 52 proc.stdin.flush() 53 54 while True: 55 line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ受け取る 56 if line == 'readyok':#exeからpyへ"readyok"という文字列が出力された場合、出力の受け取りを中断する 57 print(line) 58 break 59 60 proc.stdin.write(('usinewgame\n').encode())#pyからexeファイルへ文字列を入力。ここで、exeファイルが詰将棋の盤面入力待ち状態に移行する。 61 proc.stdin.flush() 62 63 64def funcY(proc,input_cmd):#funcXによって盤面の入力待ち状態に移行したexeファイルに対して、実際に盤面を入力し、exeから解答結果を得る 65 print(input_cmd) 66 proc.stdin.write(input_cmd.encode())#pyからexeファイルへ文字列を入力。input_cmdは詰将棋の盤面。これをexeに投げる。 67 proc.stdin.flush() 68 69 proc.stdin.write(('go mate infinite\n').encode())#pyからexeファイルへ文字列を入力。これによりexeが詰将棋を解き始める。 70 proc.stdin.flush() 71 72 while True:#exeが詰将棋解答中。 73 line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ取る 74 print(line) 75 if line[:9]=='checkmate':#exeからpyへ"checkmate"を含む文字列が出力された=詰将棋の解答結果が出たという意味。出力の受け取りを中断し、解答結果を得る 76 result=line 77 # print(line) 78 break 79 if keyboard.is_pressed("escape"):#キーボード操作でいつでも中断できる 80 break 81 82 return result#詰将棋を解答した結果を返す 83 84if __name__ == "__main__": 85 main()
発生している問題・エラーメッセージ
上に示すコードはfuncXは普通に実行し、funcYだけマルチスレッド化したコードです。funcYの内部にprint文を入れて出力を観察しましたが、funcXが終わった後はfuncY内部のprint文は一切反応せず、少し間があいてプログラムが終了します。
concurrent.futuresを使用しない普通のシングルスレッドのコードなら普通に動作することから、executor.submit()が動作してなさそうです。submit()の引数としてPopenオブジェクトを指定できないのではないかと考えています。
usiok readyok usiok readyok #本来はここからfuncYでの実行結果が表示されるはずだが…
あり得る疑問点
A.funcXとfuncYを分離させている意味、funcXを1回だけ実行してfuncYを繰り返す処理を行う意味
Q.
funcXは、exeをただの起動状態から盤面の入力待ち状態にする関数。funcYは、盤面の入力待機状態のexeに盤面を入力して解答させ、exeからの結果を待つ関数。
なのでfuncYはfuncXの後でないと動かない。また、上記した通り、funcYを1度実行した後はexeは盤面の入力待機状態に戻るので、funcYを実行した後はfuncXを実行し直さなくてよい。
無視できない程度に時間がかかり、実行回数は最低限にしたいfuncXとfuncYを纏めて繰り返すことはしたくないから、funcXとfuncYを分離させ、funcYだけ繰り返すようにした。
A.funcYの引数として、proc=Popenオブジェクトを指定する理由
Q.
funcXを介した後のPopenオブジェクトの状態(exeの盤面入力待ち状態)を維持したままfuncYに移りたいので、Popenオブジェクトを指定している。
例えば、funcYの引数としてexeのファイルパスを渡し、funcY内でsubprocess.Popen(<path to exe>,stdin=subprocess.PIPE,stdout=subprocess.PIPE)としてexeを起動する方法もあるが、それだとfuncY内で起動したexeをただの起動状態から入力待ち状態に移行させる必要性が出てくる。
それはfuncXとfuncYを纏めて繰り返す処理と同じだが、前述の理由でそれはしたくないです。
補足情報(FW/ツールのバージョンなど)
Windows11環境です。

回答1件
あなたの回答
tips
プレビュー