Многопоточность в Ruby
Каждая программа, выполняющаяся в системе, — это процесс. Каждый процесс содержит один или несколько потоков.
Поток — это единый последовательный поток управления в программе. Одновременное выполнение нескольких потоков в одной программе для выполнения разных задач называется многопоточностью.
В Ruby мы можем создать многопоточность с помощью класса Thread. Потоки Ruby легковесны и позволяют эффективно реализовать параллельный код.
Создание потоков в Ruby
Чтобы запустить новый поток, просто вызовите Thread.new:
Пример
В следующем примере показано, как использовать многопоточность в программе на Ruby:
Пример
Попробуйте »
Результат выполнения приведённого выше кода:
Started At Wed May 14 08:21:54 -0700 2014 func1 at: Wed May 14 08:21:54 -0700 2014 func2 at: Wed May 14 08:21:54 -0700 2014 func2 at: Wed May 14 08:21:55 -0700 2014 func1 at: Wed May 14 08:21:56 -0700 2014 func2 at: Wed May 14 08:21:56 -0700 2014 func1 at: Wed May 14 08:21:58 -0700 2014 End at Wed May 14 08:22:00 -0700 2014
Жизненный цикл потока
1. Thread.new можно использовать для создания потока. Аналогично Thread.start или Thread.fork можно использовать с тем же синтаксисом для создания потоков.
2. После создания потока его не нужно запускать; поток выполнится автоматически.
3. Класс Thread определяет некоторые методы для управления потоками. Поток выполняет блок кода в Thread.new.
4. Последний оператор в блоке кода потока — это значение потока, которое можно получить с помощью методов потока. Если поток завершил выполнение, он возвращает значение потока; в противном случае значение не возвращается, пока поток не завершится.
5. Метод Thread.current возвращает объект, представляющий текущий поток. Метод Thread.main возвращает главный поток.
6. Используйте метод Thread.join для выполнения потоков. Этот метод приостанавливает главный поток, пока текущий поток не завершит выполнение.
Состояния потоков
У потоков есть 5 состояний:
| Состояние потока | Возвращаемое значение |
|---|---|
| Runnable | run |
| Спящий | Спящий |
| Выход | аварийное завершение |
| Нормальное завершение | false |
| Завершён из-за исключения | nil |
Потоки и исключения
Когда в потоке возникает исключение и оно не перехватывается rescue, поток обычно завершается без предупреждения. Однако если другие потоки ожидают этот поток с помощью Thread#join, в ожидающих потоках также будет возбуждено то же исключение.
Используя следующие 3 метода, можно заставить интерпретатор прервать выполнение, когда поток завершается из-за исключения.
- Укажите при запуске скрипта-dопцию и запустите в режиме отладки.
- использовать
Thread.abort_on_exceptionУстановите флаг. - Используйте
Thread#abort_on_exceptionУстановите флаг для указанного потока.
После использования одного из трёх вышеописанных методов весь интерпретатор будет прерван.
Управление синхронизацией потоков
В Ruby есть три способа достижения синхронизации, а именно:
1. Реализация синхронизации потоков через класс Mutex
2. Реализация синхронизации потоков через класс Queue для передачи данных
3. Использование ConditionVariable для управления синхронизацией
Реализация синхронизации потоков с помощью класса Mutex
Чтобы реализовать управление синхронизацией потоков с помощью класса Mutex, если нескольким потокам одновременно нужна переменная программы, можно заблокировать эту переменную с помощью lock. Код выглядит следующим образом:
Пример
Попробуйте »
Результат выполнения приведённого выше кода:
Synchronize Thread you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets you have successfully bought 20 tickets you have successfully bought 15 tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets sorry,no enough tickets
Помимо использования lock для блокировки переменной, вы также можете использовать try_lock для блокировки переменной, а также можете использовать Mutex.synchronize для синхронизации доступа к переменной.
Реализация синхронизации потоков с помощью класса Queue для передачи данных
Класс Queue представляет потокобезопасную очередь, которая может синхронизировать доступ к концу очереди. Разные потоки могут использовать один и тот же класс очереди, не беспокоясь о том, можно ли синхронизировать данные в очереди. Кроме того, использование класса SizedQueue позволяет ограничить длину очереди.
Класс SizedQueue может очень удобно помочь нам разрабатывать приложения с синхронизацией потоков, потому что, пока элементы добавляются в эту очередь, не нужно беспокоиться о проблемах синхронизации потоков.
Классическая проблема производителя-потребителя:
Пример
Попробуйте »
Вывод программы:
SizedQuee Test 0 produced 1 produced consumed 0 2 produced consumed 1 consumed 2 3 produced consumed 34 produced consumed 4 5 produced consumed 5 6 produced consumed 6 7 produced consumed 7 8 produced 9 produced consumed 8 consumed 9
Переменные потока
Потоки могут иметь собственные приватные переменные. Приватные переменные потока записываются в поток при его создании. Их можно использовать в области видимости потока, но нельзя совместно использовать вне потока.
Но иногда, что если локальные переменные потока должны быть доступны другим потокам или главному потоку? Ruby предоставляет способ создавать переменные потока по имени, рассматривая поток как таблицу поиска в стиле хеша. Используйте []= для записи данных и [] для чтения данных. Давайте посмотрим на следующий код:
Пример
Вывод вышеприведённого кода:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
Главный поток ожидает завершения выполнения дочерних потоков, затем выводит каждое значение отдельно.
Приоритет потока
Приоритет потока — основной фактор, влияющий на планирование потоков. Другие факторы включают продолжительность времени выполнения CPU, планирование групп потоков и так далее.
Вы можете использовать метод Thread.priority, чтобы получить приоритет потока, и метод Thread.priority=, чтобы изменить приоритет потока.
Приоритет потока по умолчанию равен 0. Потоки с более высоким приоритетом выполняются быстрее.
Поток может получить доступ ко всем данным в своей собственной области, но что, если вам нужно получить доступ к данным других потоков из определённого потока? Класс Thread предоставляет методы для доступа потоков к данным друг друга. Можно просто рассматривать поток как Hash-таблицу и использовать []= в любом потоке для записи данных, а [] — для чтения данных.
Видно, что, рассматривая поток как хеш-таблицу и используя методы [] и []=, мы достигли совместного использования данных между потоками.
Взаимное исключение потоков
Мьютекс (взаимное исключение = мьютекс-блокировка) — это механизм, используемый в многопоточном программировании, чтобы не позволить двум потокам одновременно читать и записывать один и тот же общий ресурс (например, глобальную переменную).
Пример без использования мьютекса
Пример
Вывод приведенного выше примера:
count1 : 9712487 count2 : 12501239 difference : 0
Пример использования мьютекса
Пример
Вывод приведенного выше примера:
count1 : 1336406 count2 : 1336406 difference : 0
Взаимоблокировка
Когда два или более вычислительных устройства ждут, пока другое остановится, чтобы получить системные ресурсы, но ни одно не завершается досрочно, такая ситуация называется взаимоблокировкой.
Например, если процесс p1 занимает монитор и должен также использовать принтер, но принтер занят процессом p2, а p2 должен использовать монитор, возникает взаимоблокировка.
При использовании объектов Mutex нужно учитывать возможность взаимоблокировок потоков.
Пример
Вывод приведенного выше примера:
A: I have critical section, but will wait for cv (Later, back at the ranch...) B: Now I am critical, but am done with cv B: I am still critical, finishing up A: I have critical section again! I rule!
Методы класса Thread
Полный список методов класса Thread выглядит следующим образом:
| Порядковый номер | Описание метода |
|---|---|
| 1 | Thread.abort_on_exception Если его значение — true, то при завершении потока из-за исключения весь интерпретатор будет прерван. Значение по умолчанию — false. То есть при обычных обстоятельствах, если поток возбуждает исключение, не обнаруженное с помощью Thread#join или аналогичного механизма, поток будет завершён без предупреждения. |
| 2 | Thread.abort_on_exception= Если установлено вtrue, то при завершении потока из-за исключения весь интерпретатор будет прерван. Возвращает новое состояние. |
| 3 | Thread.critical Возвращает логическое значение. |
| 4 | Thread.critical= Когда его значение — true, переключение потоков не происходит. Если текущий поток приостановлен (stop) или прерван сигналом, его значение автоматически станет false. |
| 5 | Thread.current Возвращает текущий выполняющийся поток (текущий поток). |
| 6 | Thread.exit Завершает выполнение текущего потока. Возвращает текущий поток. Если текущий поток является единственным потоком, для завершения его выполнения будет использоваться exit(0). |
| 7 | Thread.fork { block } Создаёт поток так же, как Thread.new. |
| 8 | Thread.kill( aThread ) Завершает выполнение потока. |
| 9 | Thread.list Возвращает массив живых потоков, которые находятся в состоянии выполнения или приостановки. |
| 10 | Thread.main Возвращает главный поток. |
| 11 | Thread.new( [ arg ]* ) {| args | block } Создаёт поток и начинает выполнение. Аргументы передаются блоку без изменений. Это позволяет передавать значения в локальные переменные потока при запуске потока. |
| 12 | Thread.pass Уступает выполнение другим потокам. Это не изменяет состояние выполняющегося потока, но передаёт управление другим готовым к выполнению потокам (явное планирование потоков). |
| 13 | Thread.start( [ args ]* ) {| args | block } Создаёт поток и начинает выполнение. Аргументы передаются блоку без изменений. Это позволяет передавать значения в локальные переменные потока при запуске потока. |
| 14 | Thread.stop Приостанавливает текущий поток, пока другой поток не использует метод run, чтобы снова его разбудить. |
Методы экземпляра Thread
Следующий пример вызывает метод создания экземпляра потока join:
Пример
Ниже приведён полный список методов создания экземпляров:
| Нет. | Описание метода |
|---|---|
| 1 | thr[ name ] Возвращает локальные данные потока, соответствующие name. name может быть строкой или символом. Если данных, соответствующих name, нет, возвращается nil. |
| 2 | thr[ name ] = Устанавливает значение локальных данных потока, соответствующих name. name может быть строкой или символом. Если задано nil, соответствующие данные в потоке удаляются. |
| 3 | thr.abort_on_exception Возвращает логическое значение. |
| 4 | thr.abort_on_exception= Если его значение — true, то, как только поток завершается из-за исключения, прерывается весь интерпретатор. |
| 5 | thr.alive? Возвращает true, если поток жив. |
| 6 | thr.exit Завершает выполнение потока. Возвращает self. |
| 7 | thr.join Приостанавливает текущий поток до завершения потока self. Если self завершается из-за исключения, текущий поток возбудит то же исключение. |
| 8 | thr.key? Возвращает true, если локальные для потока данные, соответствующие name, уже определены. |
| 9 | thr.kill АналогичноThread.exit 。 |
| 10 | thr.priority Возвращает приоритет потока. Приоритет по умолчанию — 0. Чем больше значение, тем выше приоритет. |
| 11 | thr.priority= Задаёт приоритет потока. Также можно задать отрицательное число. |
| 12 | thr.raise( anException ) Принудительно возбуждает исключение в потоке. |
| 13 | thr.run Перезапускает приостановленный (stop) поток. В отличие от wakeup, он немедленно выполняет переключение потока. Если этот метод применяется к мёртвому потоку, возникает исключение ThreadError. |
| 14 | thr.safe_level Возвращает уровень безопасности self. safe_level текущего потока совпадает с $SAFE. |
| 15 | thr.status Представляет статус живого потока с помощью строк "run", "sleep" или "aborting". Возвращает false, если поток завершился нормально. Возвращает nil, если он завершился из-за исключения. |
| 16 | thr.stop? Возвращает true, если поток мёртв или приостановлен (stop). |
| 17 | thr.value Ожидает завершения потока self (эквивалентно join), затем возвращает возвращаемое значение блока потока. Если во время выполнения потока возникло исключение, оно возбуждается снова. |
| 18 | thr.wakeup Изменяет состояние приостановленного (stop) потока на готовый к выполнению (run). Если этот метод выполняется для мёртвого потока, возникает исключение ThreadError. |