SICP. Потоки

В прошлой статье мы разобрали модульность по файлам и по коду. Суть такая: делим код на объекты, распихиваем их по файлам и получается двойная модульность.
В этой статье разберёмся с модульностью через потоки.

Поток выполнения

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

Это пример для наглядности, потому что на самом деле это процессы внутри ОСки, но суть та же

Затраты на ветви

Когда появляется ветвь, она забирает под себя память и производительность у основного потока, либо просит ещё ресурсов. С расслоением потоков нужно следить за двумя вещами:
Контроль очерёдности
Код выполняется параллельно и приходится запоминать, какой выполнять первее. Сейчас значение переменной равно 2, через две секунды в переменной уже лежит 5, а ещё через три секунды там уже строка.
Общие переменные
Когда потоки начинают работать с одной и той же переменной или объектом начинаются ошибки. Пока первый поток читает список товаров из базы данных, второй удаляет оттуда купленные товары. Третий поток запрашивает список, а там белиберда из товаров на складе и уже купленных.

Такое происходит потому что потоки на самом деле работают не параллельно. В ОСке есть хитрая система для работы с потоками: она запускает поток и даёт ему какое-то время на выполнение, когда время закончится она остановит этот поток и запустит второй, потом третий, а потом снова первый. ОСка делает это быстро, поэтому на выходе кажется, что все потоки работают одновременно.

Реализация в коде

Чтобы решать такие проблемы, можно использовать блокировки или очереди. Например, потоки работают с файлом:

  • Блокировка — это ручное управление. Пока первый поток не снимет блокировку, второй не сможет забрать файл себе.
  • Очередь — автоматическое, как только первый поток закончился, сразу запускается второй.

Чтобы писать хороший многопоточный код нужно глубже залезть в ОС и знать её особенности, потому что это её владения. Мы разберём простые и искусственные примеры для наглядности.

Библиотека

Чтобы создать поток нужно импортировать библиотеку threading и вызвать класс Thread. У Thread пять аргументов, но обязательный только один — имя функции, которая будет выполняться в потоке. Чтобы запустить поток нужно вызвать у него метод start

Чтобы заставить второй поток ждать пока закончит первый, нужно вызвать у первого потока метод join в коде второго потока. Тогда второй поток будет ждать пока первый закончит со своей работой.
Для примера сделаем два потока: первый вычисляет сумму чисел от 1 до 9, второй произведение чисел после первого потока.

Запускаем и видим, что код работает не так. Второй поток начал работать раньше, поэтому на выходе мы получили только сумму чисел от 1 до 9.

$ total после mul_sequence = 0
$ total после add_sequence = 45

Чтобы решить эту проблему нужно остановить второй поток, пока первый не закончит со сложением.

Запускаем и видим, что всё хорошо:

$ total после add_sequence = 45
$ total после mul_sequence = 16329600

Чтобы потоки не соревновались при работе с общими данными, можно использовать блокировку, в библиотеке threading их четыре: mutex, semaphore, event и condition. Нам нужны мьютексы.

Это объект в состоянии истина или ложь. Поток захватывает мьютекс на какой-то объект и пока он у него, другие потоки не получат доступ к объекту. Чтобы другой поток смог получить объект, предыдущий должен освободить мьютекс. Этим занимается программист. Если он забыл освободить мьютекс в потоке, программа зависнет из-за одного бесконечного потока, это называется deadlock

Объясним мьютексы через запись в общий файл. Напишем два потока для записи в файл, создадим по десять копий каждого потока и запустим.

После выполнения в файле будет что-то такое

Потоки соревнуются между собой и перебивают друг друга

Теперь добавим блокировку через мьютекс

Теперь потоки не соревнуются и терпеливо ждут пока другой поток закончит запись
Поделиться
Отправить
Запинить
 35   25 дн   sicp
Популярное