質問編集履歴

2

URLが異常だったので修正しました。

2023/02/20 09:53

投稿

417s
417s

スコア11

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でダウンロードしたものをそのまま使っています。[http://panashogi.web.fc2.com/seotsume.html](url)
5
+ subprocess.Popenを用いて、外部のexeファイルと文字列の入出力のやり取りをするプログラムを書いています。外部のexeは詰将棋を解答するプログラム(脊尾詰)で、こちらのURLでダウンロードしたものをそのまま使っています。
6
+ http://panashogi.web.fc2.com/seotsume.html
7
+ 私が書いているexeファイルは、usiプロトコルに従って文字列の入出力を行います。
6
- 私が書いているexeファイルは、usiプロトコルに従って文字列の入出力を行います。[http://shogidokoro.starfree.jp/usi.html#ProblemExample](url)
8
+ http://shogidokoro.starfree.jp/usi.html#ProblemExample
7
9
 
8
10
  参考URLの要点だけを説明すると、私のプログラムで行うexeへの入出力とその説明は以下の通りです。
9
11
  usi\n:詰将棋エンジン起動。これをexeへ入力すると、usiokを含む文字列がexeから出力される

1

質問の内容を大きく書き換えました。

2023/02/20 09:50

投稿

417s
417s

スコア11

test CHANGED
@@ -1 +1 @@
1
- Pythonのthreadingを用いて、スレッド数の上限の範囲内で関数を並行処理し、戻り値を順々に得ていく
1
+ Pythonスレッド数の上限の範囲内で関数を並行処理する
test CHANGED
@@ -1,81 +1,139 @@
1
1
  ### 実現したいこと
2
- (1)Pythonのthreadingモジュールを使、並行処理の最大数を設定つつ関数の実行終了時に戻値を得る
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
- Pythonでスレッド数の上限を制限しつつ数の並行処理を行い、終了したものが発生次第、再び実行するプログラム書いています。
11
+ setoption name Do_YoTsume_Search value false\n:詰将棋エンジンに関する設定変更
10
- 並行処理したい関数funclist1から順々に取りした引数をnumとして、最終的にやりたいことは下のようなイメージです。
12
+ isready\n:詰将棋解答開始前の準備。これexeへ入力するとreadyokを含む文字列がexeから出力される
11
- (簡略化したイメージであり、実際のコードでは数値型のnum及びproc=subprocess.Popen("external.exe",stdin=subprocess.PIPE,stdout=subprocess.PIPE)が関数引数で、関数内でprocを使って外部プログラムとやり取りをう奇異な構成になっています。)
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
- thread3:func(num=10)実行開始
15
+ position sfen…\n:詰将棋の盤面exeへ入力。
31
-
32
- thread2:func(num=2)終了戻り値と終了したスレッの番号2
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 queue
25
+ import subprocess
52
- import random
26
+ import keyboard
53
- import time
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
- sem = threading.Semaphore(3)
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
- q1 = queue.Queue()
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
- list1=[1,2,3,5,8,10,12,15,16,19,20]
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
- for num in list1:
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
- th = threading.Thread(target=usigame, args=(sem,num,q1))
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
- th.start()
63
+ while True:
71
- result=q1.get()
64
+ line = proc.stdout.readline().rstrip().decode('utf-8')#exeからpyへ文字列出力を1行ずつ受け取る
65
+ if line == 'usiok':#exeからpyへ"usiok"という文字列が出力された場合、出力の受け取りを中断する。
72
- print(result)
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
- windows11環境です。
139
+ Windows11環境です。
81
-