167.76K

Потоки

1.

МИРЭА - Российский технологический университет
mirea.ru
Операционные системы
ФИО преподавателя: Белякова Е.В.

2.

МИРЭА - Российский технологический университет
mirea.ru
Потоки и методы работы с ними
Threading – это стандартный модуль, который поставляется вместе с
интерпретатором.
import threading
threading.Thread()
Эта конструкция позволяет создать новый поток, создав экземпляр класса Thread.
2

3.

МИРЭА - Российский технологический университет
mirea.ru
threading.Thread(group=None, target=None,
name=None, args=(), kwargs={}, *,
daemon=None)
group. Имеет значение None, зарезервирована для будущего расширения при реализации
класса ThreadGroup.
target. Это функция, которая выполняется в потоке с помощью метода run(), если передано
значение None, ничего не вызывается.
name. Это имя потока, по умолчанию оно принимает значение «Thread-X», где X –
десятичное число. Программист может задать имя вручную.
args. Это кортеж, в котором хранятся аргументы, передаваемые в вызываемую функцию.
kwargs. Это словарь, в котором хранятся аргументы, передаваемые в функцию.
daemon. Это параметр, который устанавливает, является ли поток демоническим. По
умолчанию имеет значение None, тогда свойство daemonic наследуется от текущего потока.
Программист может самостоятельно установить значение параметра.
3

4.

МИРЭА - Российский технологический университет
mirea.ru
Методы для работы с
потоками
start()
Он используется для запуска созданного потока.
После использования threading.Thread()
создаётся новый поток, однако он неактивен. Для
того чтобы он начал работу, используется метод
start().
4

5.

МИРЭА - Российский технологический университет
mirea.ru
join()
Этот метод блокирует выполнение потока, который его
вызвал, до тех пор пока не завершится поток, метод
которого был вызван. То есть если в потоке thread1 был
вызван метод потока thread2: thread2.join(), то
поток thread1 будет приостановлен до тех пор, пока
выполнение thread2 не завершится.
С помощью этого метода можно заставить программу
дождаться завершения демонического потока. Например,
если вызвать метод в основном потоке, то программа не
завершится, пока не выполнится демонический поток.
5

6.

МИРЭА - Российский технологический университет
mirea.ru
У метода join() есть аргумент timeout. По умолчанию он имеет
значение None, но можно передать в него число с плавающей точкой.
Если аргумент имеет значение по умолчанию, то выполнение потока
приостанавливается, пока выполняется поток метода.
Если передать в качестве аргумента число, то для метода join()
установится время ожидания, когда оно истечёт, поток продолжит
свою работу.
Например, thr1.join(100) означает, что будет ожидаться
завершение выполнения потока thr1 не более 100 секунд.
Так как метод join() всегда возвращает None, чтобы проверить,
успел ли полностью выполниться поток за указанный timeout,
нужно проверить, выполняется ли поток с помощью метода
is_alive().
6

7.

МИРЭА - Российский технологический университет
mirea.ru
is_alive()
Метод проверяет выполняется ли поток в данный момент. Его часто
используют в связке с методом join(). Кроме того, с его помощью
можно грамотно управлять выполнением потоков демонов, не
позволяя им неожиданно завершить работу при закрытии программы,
например:
while True:
if thr1.is_alive() == True: # Проверяем,
выполняется ли поток демон
time.sleep(1) # Если да, ждем 1 секунду и
проверяем снова
else:
break # Если нет, выходим из цикла и
закрываем программу
7

8.

МИРЭА - Российский технологический университет
mirea.ru
run()
В этом методе описываются операции, выполняемые потоком. Он используется, когда явно
создается экземпляр класса. Пример:
import threading as th
import time
class Thr1(th.Thread): # Создаём экземпляр потока Thread
def __init__(self, var):
th.Thread.__init__(self)
self.daemon = True # Указываем, что этот поток - демон
self.var = var # это интервал, передаваемый в качестве аргумента
def run(self): # метод, который выполняется при запуске потока
num = 1
while True:
y = num*num + num / (num - 10) # Вычисляем функцию
num += 1
print("При num =", num, " функция y =", y) # Печатаем
результат
time.sleep(self.var) # Ждём указанное количество секунд
x = Thr1(0.9)
x.start()
time.sleep(2)
8

9.

МИРЭА - Российский технологический университет
mirea.ru
Остановка потока
Бывают ситуации, когда требуется остановить поток,
который работает в фоне. Допустим у нас поток у которого
в функции run бесконечный цикл. В основной программе
нам нужно его остановить. Тут самое простое — это
создать некую переменную stop:
В бесконечном цикле делать постоянно её проверку и
если она True, то завершать его.
Не использовать функции, которые могут блокировать
выполнение на длительное время. Всегда использовать
timeout.
9

10.

МИРЭА - Российский технологический университет
mirea.ru
Состояние гонки
Состояние гонки или race condition – это ошибка,
возникающая
при
неправильном
проектировании
многопоточной программы. Она возникает тогда, когда
несколько потоков обращаются к одним и тем же данным.
Например, переменная хранит число, которое пытаются
одновременно изменить потоки thread1 и thread2, что
приводит к непредсказуемым результатам или ошибке.
Распространена ситуация, когда один поток проверяет
значение переменной на выполнение условия, чтобы
совершить какое-то действие, но между проверкой
условия и выполнением действия вмешивается второй
поток, который изменяет значение переменной, что
приводит к получению неправильных результатов.
10

11.

МИРЭА - Российский технологический университет
mirea.ru
Доступ к общим ресурсам (lock)
Для того чтобы предотвратить состояние гонки, нужно использовать
блокировку threading.Lock(), которая не позволяет сразу
нескольким потокам работать с одними и теми же данными. Иными
словами, Lock защищает данные от одновременного доступа.
threading.Lock() – возвращает объект, который, образно
выражаясь, является дверью в комнату, которая запирается, если в
комнате кто-то находится. То есть если поток использовал Lock
(вошел в комнату), то другой поток вынужден ждать до тех пор, пока
использовавший Lock поток не откажется от него (выйдет из
комнаты).
У полученного объекта есть два метода: acquire() и release().
11

12.

МИРЭА - Российский технологический университет
mirea.ru
acquire()
Метод позволяет потоку получить блокировку. Имеет два аргумента:
blocking и timeout.
Когда вызывается с аргументом blocking равным True (значение по
умолчанию), блокирует Lock до тех пор, пока он не будет разблокирован и
возвращает True. Если объект уже заблокирован, поток приостанавливается и
ждёт, пока объект не будет разблокирован, а затем сам блокирует его.
При вызове с аргументов False, если объект Lock разблокирован, метод
блокирует его и возвращает True. Если Lock уже заблокирован, метод
ничего не делает и возвращает False.
Аргумент timeout (по умолчанию -1) можно изменить, только если аргумент
blocking имеет значение True. Если в качестве аргумента передать
положительное значение, то объект блокируется на указанное количество
секунд с учётом времени ожидания блокировки. Аргумент по умолчанию
указывает методу использовать бесконечное ожидание.
12

13.

МИРЭА - Российский технологический университет
mirea.ru
release()
Этот метод разблокирует объект Lock.
Интерпретатор позволяет вызывать его из любого
потока, а не только из потока, который
заблокировал Lock в данный момент.
Метод ничего не возвращает и вызывает
ошибку RuntimeError, если вызывается, когда
объект Lock уже разблокирован.
13

14.

МИРЭА - Российский технологический университет
mirea.ru
deadlock
При использовании Lock возникает серьезная проблема,
которая приводит к полной остановки работы программы.
Если вызвать метод acquire(), а объект Lock уже
заблокирован, то вызвавший acquire() поток будет
ждать, пока заблокировавший объект поток не вызовет
release().
Если один поток вызывает метод блокировки несколько
раз подряд, то выполнение потока приостанавливается,
пока он сам не вызовет release(). Однако он не может
вызвать release, потому что его выполнение
приостановлено, что означает бесконечную блокировку
программы.
14

15.

МИРЭА - Российский технологический университет
mirea.ru
Самоблокировку можно предотвратить, если удалить
лишний вызов acquire(), но это не всегда
возможно. Самоблокировка может происходить из-за
следующий вещей:
Возникновение
ошибок,
заблокированным.
когда
Lock
остаётся
Неправильное проектирование программы, когда одна
функция вызывается другой функцией, у которой
отсутствует блокировка.
В
случае
возникновения
ошибок
достаточно
воспользоваться
конструкцией
try-finally
или
оператором with.
15

16.

МИРЭА - Российский технологический университет
пример с with:
lock = threading.Lock()
with lock:
# операторы
pass
mirea.ru
Конструкция try-finally позволяет
удалять блокировку даже в случае
возникновения ошибок, что позволяет
избежать deadblock. Пример:
lock = threading.Lock()
lock.acquire()
try:
# операторы
pass
finally:
lock.release()
16

17.

МИРЭА - Российский технологический университет
mirea.ru
RLock
RLock блокирует поток только в том случае, если объект
заблокирован другим потоком. Используя RLock, поток никогда не
сможет заблокировать сам себя.
Использовать RLock нужно для управления вложенным доступом к
разделяемым объектам.
Также следует помнить, что, хотя и можно вызывать acquire()
несколько раз, метод release() нужно вызвать столько же раз. При
каждом вызове acquire() уровень рекурсии увеличивается на единицу,
соответственно при каждом вызове release() он уменьшается на
единицу.
17

18.

МИРЭА - Российский технологический университет
mirea.ru
Это один из старейших примитивов для
синхронизации в истории информатики. Семафор
использует
внутренний
счётчик,
который
уменьшается при каждом вызове acquire() и
увеличивается при каждом вызове release().
Счётчик не может стать меньше нуля, когда он
становится равным нулём, acquire() блокирует
поток.
18

19.

МИРЭА - Российский технологический университет
mirea.ru
Semaphore
Для работы с семафорами в Python есть класс Semaphore,
при создании его объекта можно указать начальное
значение
счетчика
через
параметр value. Semaphore предоставляет два метода:
acquire(blocking=True, timeout=None)
Если значение внутреннего счетчика больше нуля, то счетчик
уменьшается на единицу и метод возвращает True. Если значение
счетчика равно нулю, то вызвавший данный метод поток
блокируется, до тех пор, пока не будет кем-то вызван
метод release(). Дополнительно при вызове метода можно указать
параметры blocking и timeout, их назначение совпадает
с acquire() для Lock.
release()
Увеличивает значение внутреннего счетчика на единицу.
19

20.

МИРЭА - Российский технологический университет
mirea.ru
Существует ещё один класс, реализующий
алгоритм семафора BoundedSemaphore, в отличии
от Semaphore, он проверяет, чтобы значение
внутреннего счетчика было не больше того, что
передано
при
создании
объекта
через
аргумент value, если это происходит, то
выбрасывается исключение ValueError.
С помощью семафоров удобно управлять
доступом к ресурсу, который имеет ограничение
на количество одновременных обращений к нему
(например, количество подключений к базе
данных и т.п.)
20

21.

МИРЭА - Российский технологический университет
mirea.ru
В качестве примера приведем программу, моделирующую продажу
билетов: обслуживание одного клиента занимает одну секунду, касс
всего три, клиентов пять.
from threading import Thread, BoundedSemaphore
from time import sleep, time
ticket_office = BoundedSemaphore(value=3)
def ticket_buyer(number):
start_service = time()
with ticket_office:
sleep(1)
print(f"client {number}, service time: {time() - start_service}")
buyer = [Thread(target=ticket_buyer, args=(i,)) for i in range(5)]
for b in buyer:
b.start()
21

22.

МИРЭА - Российский технологический университет
Спасибо за внимание!
mirea.ru
English     Русский Правила