用Python實現讀寫鎖

起步

Python 提供的多線程模型中並無提供讀寫鎖,讀寫鎖相對於單純的互斥鎖,適用性更高,能夠多個線程同時佔用讀模式的讀寫鎖,可是隻能一個線程佔用寫模式的讀寫鎖多線程

通俗點說就是當沒有寫鎖時,就能夠加讀鎖且任意線程能夠同時加;而寫鎖只能有一個線程,且必須在沒有讀鎖時才能加上。app

簡單的實現

import threading

class RWlock(object):
    def __init__(self):
        self._lock = threading.Lock()
        self._extra = threading.Lock()
        self.read_num = 0

    def read_acquire(self):
        with self._extra:
            self.read_num += 1
            if self.read_num == 1:
                self._lock.acquire()

    def read_release(self):
        with self._extra:
            self.read_num -= 1
            if self.read_num == 0:
                self._lock.release()

    def write_acquire(self):
        self._lock.acquire()

    def write_release(self):
        self._lock.release()

這是讀寫鎖的一個簡單的實現,self.read_num 用來保存得到讀鎖的線程數,這個屬性屬於臨界區,對其操做也要加鎖,因此這裏須要一個保護內部數據的額外的鎖 self._extraide

可是這個鎖是不公平的。理想狀況下,線程得到所的機會應該是同樣的,無論線程是讀操做仍是寫操做。而從上述代碼能夠看到,讀請求都會當即設置 self.read_num += 1,無論有沒有得到鎖,而寫請求想要得到鎖還得等待 read_num 爲 0 。ui

因此這個就形成了只有鎖沒有被佔用或者沒有讀請求時,能夠得到寫權限。咱們應該想辦法避免讀模式鎖長期佔用。線程

讀寫鎖的優先級

讀寫鎖也有分 讀優先寫優先。上面的代碼就屬於讀優先。code

若是要改爲寫優先,那就換成去記錄寫線程的引用計數,讀和寫在同時競爭時,可讓寫線程增長寫的計數,這樣可以使讀線程的讀鎖一直獲取不到, 由於讀線程要先判斷寫的引用計數,若不爲0,則等待其爲 0,而後進行讀。這部分代碼不羅列了。rem

但這樣顯然不夠靈活。咱們不須要兩個類似的讀寫鎖類。咱們但願重構咱們代碼,使它更強大。get

改進

爲了可以知足自定義優先級的讀寫鎖,要記錄等待的讀寫線程數,而且須要兩個條件 threading.Condition 用來處理哪方優先的通知。計數引用能夠擴大語義:正數:表示正在讀操做的線程數,負數:表示正在寫操做的線程數(最多-1)it

在獲取讀操做時,先而後判斷時候有等待的寫線程,沒有,進行讀操做,有,則等待讀的計數加 1 後等待 Condition 通知;等待讀的計數減 1,計數引用加 1,繼續讀操做,若條件不成立,循環等待;io

在獲取寫操做時,若鎖沒有被佔用,引用計數減 1,若被佔用,等待寫線程數加 1,等待寫條件 Condition 的通知。

讀模式和寫模式的釋放都是同樣,須要根據判斷去通知對應的 Condition:

class RWLock(object):
    def __init__(self):
        self.lock = threading.Lock()
        self.rcond = threading.Condition(self.lock)
        self.wcond = threading.Condition(self.lock)
        self.read_waiter = 0    # 等待獲取讀鎖的線程數
        self.write_waiter = 0   # 等待獲取寫鎖的線程數
        self.state = 0          # 正數:表示正在讀操做的線程數   負數:表示正在寫操做的線程數(最多-1)
        self.owners = []        # 正在操做的線程id集合
        self.write_first = True # 默認寫優先,False表示讀優先

    def write_acquire(self, blocking=True):
        # 獲取寫鎖只有當
        me = threading.get_ident()
        with self.lock:
            while not self._write_acquire(me):
                if not blocking:
                    return False
                self.write_waiter += 1
                self.wcond.wait()
                self.write_waiter -= 1
        return True

    def _write_acquire(self, me):
        # 獲取寫鎖只有當鎖沒人佔用,或者當前線程已經佔用
        if self.state == 0 or (self.state < 0 and me in self.owners):
            self.state -= 1
            self.owners.append(me)
            return True
        if self.state > 0 and me in self.owners:
            raise RuntimeError('cannot recursively wrlock a rdlocked lock')
        return False

    def read_acquire(self, blocking=True):
        me = threading.get_ident()
        with self.lock:
            while not self._read_acquire(me):
                if not blocking:
                    return False
                self.read_waiter += 1
                self.rcond.wait()
                self.read_waiter -= 1
        return True

    def _read_acquire(self, me):
        if self.state < 0:
            # 若是鎖被寫鎖佔用
            return False

        if not self.write_waiter:
            ok = True
        else:
            ok = me in self.owners
        if ok or not self.write_first:
            self.state += 1
            self.owners.append(me)
            return True
        return False

    def unlock(self):
        me = threading.get_ident()
        with self.lock:
            try:
                self.owners.remove(me)
            except ValueError:
                raise RuntimeError('cannot release un-acquired lock')

            if self.state > 0:
                self.state -= 1
            else:
                self.state += 1
            if not self.state:
                if self.write_waiter and self.write_first:   # 若是有寫操做在等待(默認寫優先)
                    self.wcond.notify()
                elif self.read_waiter:
                    self.rcond.notify_all()
                elif self.write_waiter:
                    self.wcond.notify()

    read_release = unlock
    write_release = unlock
相關文章
相關標籤/搜索