Slide 1

Slide 1 text

セマフォで(並列)タスクの同時実行数制限 Hank Ehly (ハンク・イーリー) ENECHANGE株式会社 @hankehly (GitHub) qiita.com/hankehly

Slide 2

Slide 2 text

問題 ● 並列タスクの同時実行数を制御したい場合がある ○ スクレイピング ○ レート制限がかかっている API ○ CPU/メモリーの負荷を抑えたい ● ThreadPoolExecutor / ProcessPoolExecutor ● 同時実行数を制御するオプションがない ○ “一度に最大 N タスクまで実行したい ”

Slide 3

Slide 3 text

どんな工夫があるか 1. time.sleep while True: run_job() time.sleep(3) current_jobs = 0 max_jobs = 3 while True: if current_jobs < max_jobs: run_job() current_jobs -= 1 while True: if acquire_lock(): run_job() release_lock() 2. グローバルなカウンター変数 3. ロックオブジェクト ○ redis ○ multiprocessing.Lock ○ 空っぽのファイル

Slide 4

Slide 4 text

セマフォとは(Wiki) セマフォとは、並列プログラミング環境での複数の実行単位が共有する資源にアクセスするのを制御する 際の、便利な抽象化を提供する変数または抽象データ型である。 つまり… ● セマフォは、限られた容量のリソースへのアクセスを制御するために使う ● “リソース”は「獲得」した後に「解放」しないといけないもの ● セマフォは「獲得/解放の回数」を監視する “Semaphore” (セマフォ) を知っていただきたい

Slide 5

Slide 5 text

Semaphore と BoundedSemaphore - Semaphore - BoundedSemaphore sem = threading.Semaphore(1) print(sem._value) # 1 sem.acquire() # Trueを返す print(sem._value) # 0 sem.acquire(timeout=5) # 5秒経過後にFalseを返す sem.release() print(sem._value) # 1 sem.release() print(sem._value) # 2 セマフォが「獲得回数と解放回数」を見てくれる

Slide 6

Slide 6 text

Semaphore と BoundedSemaphore - Semaphore - BoundedSemaphore - 有限セマフォとも sem = threading.BoundedSemaphore(1) print(sem._value) # 1 sem.acquire() # Trueを返す print(sem._value) # 0 sem.acquire(timeout=5) # 5秒経過後にFalseを返す sem.release() print(sem._value) # 1 sem.release() Traceback (most recent call last): File "", line 1, in File "/ (省略) /threading.py", line 504, in release raise ValueError("Semaphore released too many times") ValueError: Semaphore released too many times 下限値に加えて上限値もある。 “解放”し過ぎないようにしたい時に使う

Slide 7

Slide 7 text

【実例】スクレイピング def scrape_url(url): res = requests.get(url) s3_bucket.put(res.content, key=”...”) urls = [“http://netkeiba.com”, ...] sem = BoundedSemaphore(3) with ThreadPoolExecutor() as pool: while urls: if sem.acquire(): try: urls = url.pop() task = pool.submit(scrape_url, url) task.add_done_callback(lambda _: sem.release()) except: sem.release() else: logging.debug("他のタスク待ち..") コード例:qiita.com/hankehly

Slide 8

Slide 8 text

【実例】スクレイピング def scrape_url(url): res = requests.get(url) s3_bucket.put(res.content, key=”...”) urls = [“http://netkeiba.com”, ...] sem = BoundedSemaphore(3) with ThreadPoolExecutor() as pool: while urls: if sem.acquire(): try: urls = url.pop() task = pool.submit(scrape_url, url) task.add_done_callback(lambda _: sem.release()) except: sem.release() else: logging.debug("他のタスク待ち..") コード例:qiita.com/hankehly

Slide 9

Slide 9 text

【実例】スクレイピング def scrape_url(url): res = requests.get(url) s3_bucket.put(res.content, key=”...”) urls = [“http://netkeiba.com”, ...] sem = BoundedSemaphore(3) with ThreadPoolExecutor() as pool: while urls: if sem.acquire(): try: urls = url.pop() task = pool.submit(scrape_url, url) task.add_done_callback(lambda _: sem.release()) except: sem.release() else: logging.debug("他のタスク待ち..") コード例:qiita.com/hankehly

Slide 10

Slide 10 text

終わりに 並列プログラミングで とあるタスクを “同時に N 個まで” 制限する必要があった場合 セマフォでエレガントに解決できるかも!