Сделать простой параллелизм поддержки классов - PullRequest
0 голосов
/ 07 июля 2019

У меня есть простой класс банковского счета, который должен пройти следующий тест

import sys
import threading
import time
import unittest

from bank_account import BankAccount


class BankAccountTest(unittest.TestCase):

    def test_can_handle_concurrent_transactions(self):
        account = BankAccount()
        account.open()
        account.deposit(1000)

        self.adjust_balance_concurrently(account)

        self.assertEqual(account.get_balance(), 1000)

    def adjust_balance_concurrently(self, account):
        def transact():
            account.deposit(5)
            time.sleep(0.001)
            account.withdraw(5)

        # Greatly improve the chance of an operation being interrupted
        # by thread switch, thus testing synchronization effectively
        try:
            sys.setswitchinterval(1e-12)
        except AttributeError:
            # For Python 2 compatibility
            sys.setcheckinterval(1)

        threads = [threading.Thread(target=transact) for _ in range(1000)]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()

Я попытался прочитать документацию по потокам, но мне показалось немного странным попытаться применить мою ситуацию.Я попробовал вот что:

class BankAccount(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.state = False
        self.balance = 0
        Thread().start()

    def get_balance(self):
        if self.state:
            return self.balance
        else:
            raise ValueError

    def open(self):
        self.state = True

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        self.balance -= amount

Это явно неправильно.Моя цель - просто понять, как заставить класс обрабатывать переключатели потоков.Если я не включил важную информацию, дайте мне знать.

1 Ответ

0 голосов
/ 07 июля 2019

Нам нужно убедиться, что несколько потоков не могут изменить баланс одновременно.

Функция deposit(), хотя кажется, что это один шаг, является многошаговой операцией.

old_balance = self.balance
new_balance = old_balance + deposit
self.balance = new_balance

Если переключение потоков происходит в середине депозита, это может повредить данные.

Например, предположим, что поток 1 вызывает deposit(10), а поток 2 вызывает deposit(20), начальный баланс равен 100

# Inside thread 1
old_balance1 = self.balance      
new_balance1 = old_balance1 + 10 
# Thread switches to thread 2    
old_balance2 = self.balance      
new_balance2 = old_balance2 + 20 
self.balance = new_balance2      # balance = 120
# Thread switches back to thread 1
self.balance = new_balance1      # balance = 110

Здесь окончательный баланс равен 110, тогда как он должен был быть 130.

Решение состоит в том, чтобы предотвратить одновременную запись в переменную balance двух потоков. Для этого мы можем использовать Lock s.

import threading

class BankAccount:

    def open(self):
        self.balance = 0
        # initialize lock
        self.lock = threading.Lock()

    def deposit(self, val):
        # if another thread has acquired lock, block till it releases
        self.lock.acquire()
        self.balance += val
        self.lock.release()

    def withdraw(self, val):
        self.lock.acquire()
        self.balance -= val
        self.lock.release()

    def get_balance(self):
        return self.balance
...