KnowHow

技術的なメモを中心にまとめます。
検索にて調べることができます。

並列処理プログラミングのメモ#4 (Reader / Writer)

登録日 :2025/05/03 09:35
カテゴリ :Python基礎

同期を設計するもう一つのよく知られた方法。リーダー/ライター問題

読み取りは、複数のプロセス(スレッド)から可能な状態とする一方で、書込みを行う場合は、排他的にする必要がある(書込みの場合は、他からの読み込み・書込み禁止。逆に読み込みの中は、書込みは禁止)
ファイルサーバ上のファイルの読み書きをイメージするとわかりやすい。

  • 任意の数のリーダーが共有データの読み取りを同時に行うことができる。
  • 共有データに書込みを行うことができるライターは一度に一つだけ。
  • ライターが共有データに書込みを行っている間、リーダーが共有データを読み取ることはできない。

このようにして、読み取り/書き込みエラーや、書き込み/書き込みエラーによる競合状態や不適切なインタリーブを防ぐ。

ライブラリやプログラミング言語には、こうした問題解決のために「読み取り/書き込みロック」(RWLock)が含まれていることがよくある。PythonにはそのようなLockがないので、RWLockを実装してから実現するサンプルコード。

rwlock.py

"""
Chapter 9 /reader_writer/rwlock.py
"""
from threading import Lock


class RWLock(object):
    def __init__(self) -> None:
        self.readers = 0
        self.read_lock = Lock()
        self.write_lock = Lock()

    def acquire_read(self) -> None:
        self.read_lock.acquire()
        self.readers += 1
        if self.readers == 1:
            self.write_lock.acquire()
        self.read_lock.release()

    def release_read(self) -> None:
        assert self.readers >= 1
        self.read_lock.acquire()
        self.readers -= 1
        if self.readers == 0:
            self.write_lock.release()
        self.read_lock.release()

    def acquire_write(self) -> None:
        self.write_lock.acquire()

    def release_write(self) -> None:
        self.write_lock.release()

RWLockを用いて実行する、reader_writet.py

"""
Chapter 9/reader_writer/reader_writer.py
"""
import time
import random
from threading import Thread
from rwlock import RWLock


# share resource
counter = 0

# mutex
lock = RWLock()


class User(Thread):
    def __init__(self, idx: int):
        super(User, self).__init__()
        self.idx = idx

    def run(self) -> None:
        while True:
            lock.acquire_read()
            print(f"User {self.idx} reading: {counter}")
            # time.sleep(random.randrange(1, 3))
            time.sleep(1)
            lock.release_read()
            time.sleep(1)


class Librarian(Thread):
    def run(self) -> None:
        # change global parameter: share resource
        global counter
        while True:
            lock.acquire_write()
            print("Librarian waiting ...")
            counter += 1
            print(f"New value: {counter}")
            time.sleep(random.randrange(1, 3))
            lock.release_write()


if __name__ == "__main__":
    threads = [
        User(0),
        User(1),
        Librarian()
    ]

    for thread in threads:
        thread.start()

    for thread in threads:
        thread.join()