0
0

More than 1 year has passed since last update.

【Python】並行処理でwrite()してたら馬鹿みたいな文字化けをした

Last updated at Posted at 2022-12-29

まとめ

並行処理でwriteするときはthreading.Lockを使おう!(どんな並行処理ライブラリを使っていてもthreadsをimportすればOK!)

経緯

Webスクレイピングで7つぐらいのスレッドから一つのファイルにスクレイピング結果を書き込んでいたとき、完成したスクレイピング結果ファイルをみると文字化けだらけだった!
5.png

ここでは、並行処理にfuturesを使っていました。

やっていたコード(悪い例)

悪い例
from concurrent import futures

def scrape(アクセス先):
    global f    

    #(スクレイピング部分)

    f.write(result)

f = open("ファイル", "w", encoding="utf-8")

future_list = []
with futures.ThreadPoolExecutor(max_workers=drivers_n) as executor:
    #処理を登録
    for link in アクセス先リスト:
        future = executor.submit(scrape, url=link)
        future_list.append(future)

    #全ての処理が完了するまで待ち
    _ = futures.as_completed(fs=future_list)

f.close()

そしてさっきの写真のような文字化けをおこしました。
複数のスレッドのwriteが同時発生して競合したせいでしょうね。

解決

一つのスレッドがwriteしている間は他のスレッドを待たせる必要があります。排他処理ってやつですね。

その「一つのスレッドがwriteしている間は他のスレッドを待たせる」ロジックを使うにはthreading.Lockを使えばいいんです。
これはどの並行処理ライブラリ(futuresでもなんでも)にも有効です。

改善したコード(良い例)

良い例
```Python:悪い例
from concurrent import futures
from threading import Lock

def scrape(アクセス先):
    #追加!
    global f, write_lock    

    #(スクレイピング部分)

    #追加!
    with write_lock:
        f.write(result)

f = open("ファイル", "w", encoding="utf-8")

future_list = []
#追加!
write_lock = Lock()
with futures.ThreadPoolExecutor(max_workers=drivers_n) as executor:
    #処理を登録
    for link in アクセス先リスト:
        future = executor.submit(scrape, url=link)
        future_list.append(future)

    #全ての処理が完了するまで待ち
    _ = futures.as_completed(fs=future_list)

f.close()

グローバル変数としてLockを一つ用意すれば、排他処理にしたいロジックを
with Lock:
のブロックに入れることで、「一つのスレッドがwriteしている間は他のスレッドを待たせる」処理ができます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0