summaryrefslogtreecommitdiff
path: root/PYTHON/THREADING.md
blob: 8ebc6312ae187c6367e39872768ba08ca3b71609 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
**Как работать с многопоточностью в Python**

Python поддерживает многопоточность, но из-за **GIL (Global Interpreter Lock)** потоки работают не параллельно на CPU.

✅ Использование потоков:
```python
import threading

def worker():
    print("Работает поток")

t = threading.Thread(target=worker)
t.start()
t.join()  # Ждем завершения потока
```

**⚙️ Когда использовать?**
🟢 Для задач ввода-вывода (запросы в сеть, работа с файлами)
🟠 Для вычислений лучше использовать multiprocessing, а не threading
🗣 **Потоки полезны, но знай их ограничения!**



# Python: как сделать многопоточную программу

Когда-то давно мы делали [простой таймер с напоминанием на Python](https://thecode.media/python-time/). Он работал так:

1. Мы спрашивали пользователя, о чём ему напомнить и через сколько минут.
2. Программа на это время засыпала и ничего не делала.
3. Как только время сна заканчивалось, программа просыпалась и выводила напоминание.

У такой схемы есть минус: мы не можем пользоваться программой и выделенными на неё ресурсами до тех пор, пока она не проснётся. Процессор по кругу гоняет пустые команды и ждёт, когда можно будет продолжить полезную работу. Чтобы процессор и программа могли во время работы таймера делать что-то ещё, используют потоки.

## Что такое поток

В упрощённом виде потоки — это параллельно выполняемые задачи. По умолчанию используется один поток — это значит, что программа делает всё по очереди, линейно, без возможности делать несколько дел одновременно.

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

👉 Важно понимать, что поток — это высокоуровневое понятие из области программирования. На уровне вашего «железа» эти потоки всё ещё могут обсчитываться последовательно. Но благодаря тому, что они будут обсчитываться быстро, вам может показаться, что они работают параллельно.

## Многопоточность

Представим такую ситуацию:

- У вас на руке смарт-часы, которые собирают данные о вашем пульсе, УФ-излучении и движениях. На смарт-часах работает программа, которая обрабатывает эти данные.
- Программа состоит из четырёх функций. Первая собирает данные с датчиков. Три другие обрабатывают эти данные и делают выводы.
- Пока первая функция не собрала нужные данные, ничего другого не происходит.
- Как только данные введены, запускаются три оставшиеся функции. Они не зависят друг от друга и каждая считает своё.
- Как только все три функции закончат работу, программа выдаёт нужный результат.

А теперь давайте посмотрим, как это выглядит в однопоточной и многопоточной системе. Видно, что если процессор позволяет делать несколько дел одновременно, в многопоточном режиме программа будет работать быстрее:

![Python: как сделать многопоточную программу](https://thecode.media/wp-content/uploads/2021/09/Mnogopotochnaya-zadacha.png "Многопоточность")
## Многопоточность на Python

За потоки в Python отвечает модуль threading, а сам поток можно создать с помощью класса Thread из этого модуля. Подключается он так:

```markup
from threading import Thread
```

После этого с помощью функции Thread () мы сможем создать столько потоков, сколько нам нужно. Логика работы такая:

1. Подключаем нужный модуль и класс Thread.
2. Пишем функции, которые нам нужно выполнять в потоках.
3. Создаём новую переменную — поток, и передаём в неё название функции и её аргументы. Один поток = одна функция на входе.
4. Делаем так столько потоков, сколько требует логика программы.
5. Потоки сами следят за тем, закончилась в них функция или нет. Пока работает функция — работает и поток.
6. Всё это работает параллельно и (в теории) не мешает друг другу.

Для иллюстрации запустим такой код:

```python
import time
from threading import Thread

def sleepMe(i):
    print("Поток %i засыпает на 5 секунд.\n" % i)
    time.sleep(5)
    print("Поток %i сейчас проснулся.\n" % i)
for i in range(10):
    th = Thread(target=sleepMe, args=(i, ))
    th.start()
```

А вот как выглядит результат. Обратите внимание — потоки просыпаются не в той последовательности, как мы их запустили, а в той, в какой их выполнил процессор. Иногда это может помешать работе программы, но про это мы поговорим отдельно в другой статье.

![Многопоточность в Python: обратите внимание — потоки просыпаются не в той последовательности, как мы их запустили, а в той, в какой их выполнил процессор](https://thecode.media/wp-content/uploads/2020/11/image3.png "Обратите внимание — потоки просыпаются не в той последовательности, как мы их запустили, а в той, в какой их выполнил процессор")
## Добавляем потоки в таймер

Первое, что нам нужно сделать, — вынести код таймера-напоминания в отдельную функцию, чтобы создать с ней поток. Для этого используем команду `def`:

```python
# Делаем отдельную функцию с напоминанием
def remind():
    # Спрашиваем текст напоминания, который нужно потом показать пользователю
    print("О чём вам напомнить?")
    # Ждём ответа пользователя и результат помещаем в строковую переменную text
    text = str(input())
    # Спрашиваем про время
    print("Через сколько минут?")
    # Тут будем хранить время, через которое нужно показать напоминание
    local_time = float(input())
    # Переводим минуты в секунды
    local_time = local_time * 60
    # Ждём нужное количество секунд, программа в это время ничего не делает
    time.sleep(local_time)
    # Показываем текст напоминания
    print(text)
```

Теперь сделаем новый поток, в который отправим выполняться нашу новую функцию. Так как аргументов у нас нет, то и аргументы передавать не будем, а напишем args=().

```markup
# Создаём новый поток
th = Thread(target=remind, args=())
# И запускаем его
th.start()
```

Нам осталось убедиться в том, что пока поток работает, мы можем выполнять в программе что-то ещё и наше засыпание в потоке на это не повлияет. Для этого мы через 20 секунд после запуска выведем сообщение на экран. За 20 секунд пользователь успеет ввести напоминание и время, после чего таймер уйдёт в новый поток и там уснёт на нужное количество минут. Но это не помешает работе основной программы — она тоже будет выполнять свои команды параллельно с потоком.

```markup
# Пока работает поток, выведем что-то на экран через 20 секунд после запуска
time.sleep(20)
print("Пока поток работает, мы можем сделать что-нибудь ещё.\n")
```