質問編集履歴
2
URLが異常だったので修正しました。
test
CHANGED
File without changes
|
test
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
外部のexeファイルと文字列の入出力のやり取りをするpythonのコードにconcurrent.futuresを使えるようにして関数をマルチスレッド化したい。スレッドの最大数を制限し、終了したスレッドが発生次第スレッドをまた動かすことを繰り返したいです。
|
3
3
|
|
4
4
|
### 前提
|
5
|
-
subprocess.Popenを用いて、外部のexeファイルと文字列の入出力のやり取りをするプログラムを書いています。外部のexeは詰将棋を解答するプログラム(脊尾詰)で、こちらのURLでダウンロードしたものをそのまま使っています。
|
5
|
+
subprocess.Popenを用いて、外部のexeファイルと文字列の入出力のやり取りをするプログラムを書いています。外部のexeは詰将棋を解答するプログラム(脊尾詰)で、こちらのURLでダウンロードしたものをそのまま使っています。
|
6
|
+
http://panashogi.web.fc2.com/seotsume.html
|
7
|
+
私が書いているexeファイルは、usiプロトコルに従って文字列の入出力を行います。
|
6
|
-
|
8
|
+
http://shogidokoro.starfree.jp/usi.html#ProblemExample
|
7
9
|
|
8
10
|
参考URLの要点だけを説明すると、私のプログラムで行うexeへの入出力とその説明は以下の通りです。
|
9
11
|
usi\n:詰将棋エンジン起動。これをexeへ入力すると、usiokを含む文字列がexeから出力される
|
1
質問の内容を大きく書き換えました。
test
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Python
|
1
|
+
Pythonでスレッド数の上限の範囲内で関数を並行処理する
|
test
CHANGED
@@ -1,81 +1,139 @@
|
|
1
1
|
### 実現したいこと
|
2
|
-
|
2
|
+
外部のexeファイルと文字列の入出力のやり取りをするpythonのコードにconcurrent.futuresを使えるようにして関数をマルチスレッド化したい。スレッドの最大数を制限し、終了したスレッドが発生次第スレッドをまた動かすことを繰り返したいです。
|
3
|
-
(2)スレッドを何度も立て直すことなく、スレッド待機し続けながら(1)を実現する。
|
4
|
-
|
5
|
-
(1)だけでも方法を教えていただきたいです。
|
6
3
|
|
7
4
|
### 前提
|
5
|
+
subprocess.Popenを用いて、外部のexeファイルと文字列の入出力のやり取りをするプログラムを書いています。外部のexeは詰将棋を解答するプログラム(脊尾詰)で、こちらのURLでダウンロードしたものをそのまま使っています。[http://panashogi.web.fc2.com/seotsume.html](url)
|
6
|
+
私が書いているexeファイルは、usiプロトコルに従って文字列の入出力を行います。[http://shogidokoro.starfree.jp/usi.html#ProblemExample](url)
|
8
7
|
|
8
|
+
参考URLの要点だけを説明すると、私のプログラムで行うexeへの入出力とその説明は以下の通りです。
|
9
|
+
usi\n:詰将棋エンジン起動。これをexeへ入力すると、usiokを含む文字列がexeから出力される
|
10
|
+
setoption name USI_Hash value 512\n:詰将棋エンジンに関する設定を変更
|
9
|
-
|
11
|
+
setoption name Do_YoTsume_Search value false\n:詰将棋エンジンに関する設定を変更
|
10
|
-
|
12
|
+
isready\n:詰将棋解答開始前の準備。これをexeへ入力すると、readyokを含む文字列がexeから出力される
|
11
|
-
|
13
|
+
usinewgame\n:これをexeへ入力することで、exeは詰将棋盤面の文字列の入力待ち状態に移行する。
|
12
14
|
|
13
|
-
スレッド数上限=3
|
14
|
-
list1=[1,2,3,5,8,10,12,15,16,19,20]
|
15
|
-
|
16
|
-
thread1:func(num=1)を実行開始
|
17
|
-
thread2:func(num=2)を実行開始
|
18
|
-
thread3:func(num=3)を実行開始(スレッド数上限3に到達)
|
19
|
-
↓
|
20
|
-
thread3:func(num=3)終了、戻り値と終了したスレッドの番号3を得る
|
21
|
-
↓
|
22
|
-
thread3:func(num=5)を実行開始
|
23
|
-
↓
|
24
|
-
thread1:func(num=1)終了、戻り値と終了したスレッドの番号1を得る
|
25
|
-
↓
|
26
|
-
thread1:func(num=8)を実行開始
|
27
|
-
↓
|
28
|
-
thread3:func(num=5)終了、戻り値と終了したスレッドの番号3を得る
|
29
|
-
↓
|
30
|
-
t
|
15
|
+
position sfen…\n:詰将棋の盤面をexeへ入力。
|
31
|
-
↓
|
32
|
-
t
|
16
|
+
go mate infinite\n:これをexeへ入力することで、exeは入力された詰将棋を解答し始める。結果が出ると'checkmate'を含む文字列を出力する。終了すると盤面の入力待ち状態に戻り、再び"position sfen..."コマンドを待つ状態になる。
|
33
|
-
↓
|
34
|
-
thread2:func(num=12)を実行開始
|
35
|
-
…
|
36
17
|
|
37
18
|
|
38
|
-
|
39
|
-
似たような事をやっている例を発見したので、https://teratail.com/questions/49684
|
40
|
-
https://superfastpython.com/thread-semaphore/
|
41
|
-
などを参考にしてコードを書いたのですが、関数の実行が終了したものから順番に戻り値と終了したスレッドの番号を得る方法が分かりません。書いたコードは一応戻り値を得ることができていますが、コードの見た目通り並行処理になっていません。
|
42
|
-
また、スレッドを何度も立て直すのも非効率だと思うので、スレッドを待機したまま上記のことを実現することはできないのでしょうか?
|
19
|
+
私のコードでは、1つ目の関数(funcX)でusinewgame\nまでexeに入力してexeを盤面の入力待ち状態に移行させた後、2つ目の関数(funcY)で詰将棋の盤面をexeに入力して解答結果を得る、という構成になっていて、1回だけfuncXを実行してその後はfuncYを繰り返し実行しています。スレッドの最大数を制限しつつ、funcXとfuncYをマルチスレッド化(orマルチプロセス化)し、複数の詰将棋を複数の詰将棋解答exeに入力して、解答が終了したものから解答結果を得ていくようにしたいです。あるスレッドで関数Yが終了したら、スレッドをまた動かして別の盤面を指定したfuncYを実行して…ということをやりたいです。
|
43
20
|
|
44
|
-
threadingの公式ドキュメントはこちらです
|
45
|
-
https://docs.python.org/ja/3/library/threading.html
|
46
21
|
|
47
|
-
###
|
22
|
+
###
|
48
23
|
|
49
24
|
```Python
|
50
|
-
import threading
|
51
|
-
import
|
25
|
+
import subprocess
|
52
|
-
import r
|
26
|
+
import keyboard
|
53
|
-
import t
|
27
|
+
import concurrent.futures
|
54
|
-
|
55
|
-
def usigame(sem,num,q):
|
56
|
-
with sem:
|
57
|
-
print(f'{num}開始')
|
58
|
-
time.sleep(random.uniform(0,3))
|
59
|
-
print(f'{num}終了')
|
60
|
-
q.put(num**2)
|
61
28
|
|
62
29
|
def main():
|
63
|
-
s
|
30
|
+
#list1は、詰将棋の盤面を表現した文字列を集めてリストにしたもの。非本質なので気にしないでください
|
31
|
+
list1=['position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b R2G2Nr2b2g4s3l15p 1\n',
|
32
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RGS2NPr2b3g3s3l14p 1\n',
|
33
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RSNLr2b4g3sn2l15p 1\n',
|
34
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RSNPr2b4g3sn3l14p 1\n',
|
35
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RSN2Pr2b4g3sn3l13p 1\n',
|
36
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RBSNrb4g3sn3l15p 1\n',
|
37
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RBSNLrb4g3sn2l15p 1\n',
|
38
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b RBGNrb3g4sn3l15p 1\n',
|
64
|
-
|
39
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b R2BNr4g4sn3l15p 1\n',
|
40
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b B2GS2rb2g3s2n3l15p 1\n',
|
41
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b BG2SL2rb3g2s2n2l15p 1\n',
|
42
|
+
'position sfen 7nl/7k1/9/5Np1p/9/5+P3/9/9/9 b 2B3S2L2r4gs2nl15p 1\n',
|
43
|
+
'position sfen 2n3p2/5+r1p1/7+pk/3p5/3+p5/9/9/6L2/9 b R4GNP2b4s2n3l12p 1\n']
|
65
44
|
|
66
|
-
l
|
45
|
+
file_to_path1="SeoTsume_1.exe"#exeファイルのパス
|
46
|
+
file_to_path2="SeoTsume_2.exe"#exeファイルのパス2
|
47
|
+
proc1=subprocess.Popen(file_to_path1,stdin=subprocess.PIPE,stdout=subprocess.PIPE)#サブプロセスを起動しておく。exeが起動される
|
48
|
+
proc2=subprocess.Popen(file_to_path2,stdin=subprocess.PIPE,stdout=subprocess.PIPE)#マルチスレッド用にexeをコピーした別のexeを用意して起動。
|
67
49
|
|
50
|
+
funcX(proc1)#funcXは最初に一度だけやっておかなければならない入出力のやり取りの関数
|
51
|
+
funcX(proc2)
|
52
|
+
num=0
|
68
|
-
|
53
|
+
while(num<len(list1)-1):
|
54
|
+
with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:#マルチスレッド化したい
|
55
|
+
executor.submit(funcY,proc1,list1[num]) #funcYの引数として、funcXを介して盤面の入力待ち状態になったPopenオブジェクトと、盤面の文字列list1[num]を指定する
|
69
|
-
|
56
|
+
executor.submit(funcY,proc2,list1[num+1])
|
57
|
+
num+=2
|
58
|
+
|
59
|
+
def funcX(proc):#exeファイルを盤面の入力待ち状態に移行させるために最初に一度だけ実行しておく関数
|
60
|
+
proc.stdin.write(('usi\n').encode())#pyからexeファイルへ文字列"usi\n"を入力
|
61
|
+
proc.stdin.flush()#flushは必須
|
62
|
+
|
70
|
-
|
63
|
+
while True:
|
71
|
-
r
|
64
|
+
line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ受け取る
|
65
|
+
if line == 'usiok':#exeからpyへ"usiok"という文字列が出力された場合、出力の受け取りを中断する。
|
72
|
-
print(
|
66
|
+
print(line)
|
67
|
+
break
|
68
|
+
|
69
|
+
proc.stdin.write(('setoption name USI_Hash value 512\n').encode())#pyからexeファイルへ文字列を入力。
|
70
|
+
proc.stdin.flush()
|
71
|
+
|
72
|
+
proc.stdin.write(('setoption name Do_YoTsume_Search value false\n').encode())#pyからexeファイルへ文字列を入力。
|
73
|
+
proc.stdin.flush()
|
74
|
+
|
75
|
+
proc.stdin.write(('isready\n').encode())#pyからexeファイルへ文字列を入力
|
76
|
+
proc.stdin.flush()
|
77
|
+
|
78
|
+
while True:
|
79
|
+
line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ受け取る
|
80
|
+
if line == 'readyok':#exeからpyへ"readyok"という文字列が出力された場合、出力の受け取りを中断する
|
81
|
+
print(line)
|
82
|
+
break
|
83
|
+
|
84
|
+
proc.stdin.write(('usinewgame\n').encode())#pyからexeファイルへ文字列を入力。ここで、exeファイルが詰将棋の盤面入力待ち状態に移行する。
|
85
|
+
proc.stdin.flush()
|
86
|
+
|
87
|
+
|
88
|
+
def funcY(proc,input_cmd):#funcXによって盤面の入力待ち状態に移行したexeファイルに対して、実際に盤面を入力し、exeから解答結果を得る
|
89
|
+
print(input_cmd)
|
90
|
+
proc.stdin.write(input_cmd.encode())#pyからexeファイルへ文字列を入力。input_cmdは詰将棋の盤面。これをexeに投げる。
|
91
|
+
proc.stdin.flush()
|
92
|
+
|
93
|
+
proc.stdin.write(('go mate infinite\n').encode())#pyからexeファイルへ文字列を入力。これによりexeが詰将棋を解き始める。
|
94
|
+
proc.stdin.flush()
|
95
|
+
|
96
|
+
while True:#exeが詰将棋解答中。
|
97
|
+
line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ取る
|
98
|
+
print(line)
|
99
|
+
if line[:9]=='checkmate':#exeからpyへ"checkmate"を含む文字列が出力された=詰将棋の解答結果が出たという意味。出力の受け取りを中断し、解答結果を得る
|
100
|
+
result=line
|
101
|
+
# print(line)
|
102
|
+
break
|
103
|
+
if keyboard.is_pressed("escape"):#キーボード操作でいつでも中断できる
|
104
|
+
break
|
105
|
+
|
106
|
+
return result#詰将棋を解答した結果を返す
|
73
107
|
|
74
108
|
if __name__ == "__main__":
|
75
109
|
main()
|
76
110
|
```
|
77
111
|
|
112
|
+
|
113
|
+
### 発生している問題・エラーメッセージ
|
114
|
+
上に示すコードはfuncXは普通に実行し、funcYだけマルチスレッド化したコードです。funcYの内部にprint文を入れて出力を観察しましたが、funcXが終わった後はfuncY内部のprint文は一切反応せず、少し間があいてプログラムが終了します。
|
115
|
+
concurrent.futuresを使用しない普通のシングルスレッドのコードなら普通に動作することから、executor.submit()が動作してなさそうです。submit()の引数としてPopenオブジェクトを指定できないのではないかと考えています。
|
116
|
+
|
117
|
+
```実行結果
|
118
|
+
usiok
|
119
|
+
readyok
|
120
|
+
usiok
|
121
|
+
readyok
|
122
|
+
#本来はここからfuncYでの実行結果が表示されるはずだが…
|
123
|
+
```
|
124
|
+
|
125
|
+
### あり得る疑問点
|
126
|
+
A.funcXとfuncYを分離させている意味、funcXを1回だけ実行してfuncYを繰り返す処理を行う意味
|
127
|
+
Q.
|
128
|
+
funcXは、exeをただの起動状態から盤面の入力待ち状態にする関数。funcYは、盤面の入力待機状態のexeに盤面を入力して解答させ、exeからの結果を待つ関数。
|
129
|
+
なのでfuncYはfuncXの後でないと動かない。また、上記した通り、funcYを1度実行した後はexeは盤面の入力待機状態に戻るので、funcYを実行した後はfuncXを実行し直さなくてよい。
|
130
|
+
無視できない程度に時間がかかり、実行回数は最低限にしたいfuncXとfuncYを纏めて繰り返すことはしたくないから、funcXとfuncYを分離させ、funcYだけ繰り返すようにした。
|
131
|
+
|
132
|
+
A.funcYの引数として、proc=Popenオブジェクトを指定する理由
|
133
|
+
Q.
|
134
|
+
funcXを介した後のPopenオブジェクトの状態(exeの盤面入力待ち状態)を維持したままfuncYに移りたいので、Popenオブジェクトを指定している。
|
135
|
+
例えば、funcYの引数としてexeのファイルパスを渡し、funcY内でsubprocess.Popen(<path to exe>,stdin=subprocess.PIPE,stdout=subprocess.PIPE)としてexeを起動する方法もあるが、それだとfuncY内で起動したexeをただの起動状態から入力待ち状態に移行させる必要性が出てくる。
|
136
|
+
それはfuncXとfuncYを纏めて繰り返す処理と同じだが、前述の理由でそれはしたくないです。
|
137
|
+
|
78
138
|
### 補足情報(FW/ツールのバージョンなど)
|
79
|
-
|
80
|
-
|
139
|
+
Windows11環境です。
|
81
|
-
|