Lukk

Lõimedel on ühine mäluruum — seetõttu jagavad nad samu lokaalseid muutujaid, faile jne. Kuna aga igal lõimel on oma ülesanne, võib tekkida race condition. Olukord, kus mitu lõime muudavad korraga sama muutuja väärtust.

2 lõime üritavad samal ajal pangakontole raha lisada = race condition:

lock = threading.Lock()
saldo = 0  # both threads take same intial value

def add_money(cash):
    temp = saldo
    temp += cash  # both threads calculate new credit
    saldo = temp  # later thread overwrites earlier threads result

Tulemuseks vale saldo. Selle vältimiseks kasuta lukku (threading.Lock()).

../_images/lock.png

Lock

Lukk teeb ühise ressursi vaid ühele lõimele korraga kättesaadavaks. Tekib „pudelikaela efekt“, sest teised lõimed ootavad ressursi vabanemist. Maksimaalne kiirus on piiratud ühe lõime töökiirusega. acquire ja release on asendatavad with märkmesõnaga ehk context manager-iga.

Meetodid

  • acquire(blocking=True, timeout=-1): ressursi lukustamine
    1. blocking=True ootab ressurssi vabanemist, et lukustada.

    2. timeout=-1 ootab piiramatu aja ressursi vabanemist.

    Hoiatus

    Korraga ei tohi olla blocking=False ja timeout väärtustatud.

  • release() - luku äravõtmine ressursilt / ressursi vabastamine

Kuigi ressurssi võib iga lõim vabastada, on soovitatav, et lukustaja lõim seda ise teeks.

Lukk ``blocking=False`` korral:

lock = threading.Lock()

def worker():
    if lock.acquire(blocking=False):
        try:
            print("Got resource, now I can work")
        finally:
            lock.release()
                # lock.release()  # -> RuntimeError, because not locked

    else:
        print("Resource already locked, I will skip this step")
  • locked(): kas ressurss on hetkel lukus

RLock (reentrant lock)

Võimaldab lõimele sama lukku järjest kasutada. RLock-i loendur tagab, et lukk vabastatakse nii mitu korda, kui sellega lukustatakse.

Kasutusjuhud:

Ühes meetodis teise meetodi kutsumine, kus kasutatakse sama lukku:

lock = threading.RLock()

def func_a():
    with lock:
        print("inside func A")
        func_b()

def func_b():
    with lock:
        print("inside func B")

Alternatiiv mitme luku loomisele.

Lukustamine rekursiivses funktsioonis:

def rec(n, shared, lock):
    with lock:
        shared.append(n)
        if n > 0:
            rec(n-1, shared, lock)

shared = []
lock = threading.RLock()
rec(3, shared, lock)
print(shared)  # [3, 2, 1, 0]

Sama luku väljakutsumine kõigepealt public ja siis private nähtavusega funktsioonis:

class Counter:
    def __init__(self):
        self.lock = threading.RLock()   # public variable
        self._value = 0                 # private variable

    def increase(self):                 # public method
        with self.lock:
            self._increase_step()       # calls private helper

    def _increase_step(self):           # private method with shared lock
        with self.lock:
            self._value += 1

    def get_value(self):                # public method with shared lock
        with self.lock:
            return self._value