# Полезные возможности языка: повторители и замыкания
Внешний вид языка Ржавчина черпал вдохновение из многих других языков и средств, среди которых значительное влияние оказало _функциональное программирование_. Программирование в функциональном исполнении подразумевает использование способов (функций) в значении предметов, передавая их в качестве переменных, возвращая их из других способов (функций), присваивая их переменным для последующего выполнения и так далее.
Внешний вид языка Ржавчина черпал вдохновение из многих других языков и средств, среди которых значительное влияние оказало _функциональное программирование_. Программирование в функциональном исполнении подразумевает использование способов (функций) в значении предметов и видов данных, передавая их в качестве переменных, возвращая их из других способов (функций), присваивая их переменным для последующего использования и так далее.
В этой главе мы не будем рассуждать о том, что из себя представляет функциональное программирование, а обсудим возможности Ржавчины, присущие многим языкам, которые принято называть функциональными.
В этой главе не будет рассматрено что из себя представляет функциональное программирование. Здесь будут рассмотрены возможности Ржавчины, которые имеются в других функциональных языках..
Более подробно мы поговорим про:
- _Замыкания_ - средства, подобные способам (функциям), которые можно помещать в переменные
- _Повторители_ — способ обработки последовательности переменных,
- То, как, используя замыкания и повторители, улучшить работу с действиями ввода-вывода в разделе из Главы 12
- _Замыкания_ - средства, подобны способам (функциям), которые можно использовать в переменных.
- _Повторители_ — средства использования круговоротов для разных видов данных.
- Использования замыканий и повторителей для улучшиения работы при использовании ввода-вывода в разделе из Главы 12
- Производительность замыканий и повторителей (К сведению: они быстрее, чем вы думаете!)
Мы уже рассмотрели другие возможности Ржавчины, такие как сопоставление с образцом и перечисления, которые также появились под влиянием функционального исполнения. Поскольку освоение замыканий и повторителей — важная часть написания отличной от других языков, быстрой рукописи на Ржавчине, мы посвятим им всю эту главу.
Ранее были рассмотрены другие возможности Ржавчины: сопоставление с образцом и перечисления, большое влияние на них оказал функциональный подход. Поскольку освоение замыканий и повторителей — важная часть написания высокопроизводительной рукописи, отличающейся от других языков, под данные вопросы будет выделена вся главу.
Безопасное и качественное управление многопоточным программированием — ещё одна из основных целей Ржавчины - воплощение *Многопоточного программирования*, когда разные части приложения выполняются независимо, а также *одновременное программирование*, когда разные части приложения выполняются одновременно. Данные возможности становятся всё более важными, поскольку всё больше компьютеров используют преимущества нескольких этапов. Так сложилось, что программирование в этих условиях было сложным и подверженным ошибкам: Ржавчина надеется изменить это.
Безопасное и качественное управление многопоточным программированием — ещё одна из основных целей Ржавчины. Воплощение в Ржавчине *Многопоточного программирования* - это когда разные части приложения выполняются независимо друг от друга, *одновременное программирование* - это когда разные части приложения выполняются одновременно. Данные возможности становятся всё более важными, поскольку всё больше компьютеров используют преимущества выполнения нескольких этапов. Так сложилось, что программирование в этих условиях было сложным и подвержено ошибкам: Ржавчина надеется изменить это.
Первоначально объединение разработчиков Ржавчины считало, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это два отдельных сбоя, которые необходимо решать различными способами. Со временем объединение разработчиков обнаружило, что устройство владения и перечень видов данных являются мощным набором средств, помогающих управлять безопасностью памяти *и* устранять неполадки как многопоточности, так и одновременности! Используя устройство владение и проверку видов данных, многие ошибки многопоточности выявляются во время сборки в Ржавчине, а не во время выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, решение будет другим . Неправильная рукопись будет выявлена во время сборки с указанием ошибок и не собрана. В итоге вы можете исправить свою рукопись во время работы над ней, а не после развёртывания на рабочем отдельном вычислителе. Мы назвали эту особенность Ржавчины *бесстрашной**многопоточностью*. Бесстрашная многопоточность позволяет вам писать рукопись, которая не содержит скрытых ошибок и легко повторно согласуется без внесения новых ошибок.
Первоначально объединение разработчиков Ржавчины считало, что обеспечение безопасности памяти и предотвращение неполадок многопоточности — это два отдельных сбоя, которые необходимо решать различными способами. Со временем объединение разработчиков обнаружило, что устройство владения и перечень видов данных являются мощным набором средств, помогающих управлять безопасностью памяти *и* устранять неполадки как многопоточности, так и одновременности! Используя устройство владения и проверку видов данных, многие ошибки многопоточности выявляются во время сборки в Ржавчине, а не во время выполнения. Поэтому вместо того, чтобы тратить много времени на попытки воспроизвести точные обстоятельства, при которых возникает ошибка многопоточности во время выполнения, решение будет другим . Неправильная рукопись будет выявлена во время сборки с указанием ошибок и не будет собрана. В итоге вы можете исправить свою рукопись во время работы над ней, а не после развёртывания на рабочем отдельном вычислителе. Мы назвали эту особенность Ржавчины *бесстрашной**многопоточностью*. Бесстрашная многопоточность позволяет вам писать рукопись, которая не содержит скрытых ошибок и легко повторно согласуется без внесения новых ошибок.
> Примечание: для простоты понимания мы введём понятие - множественные сбои *многопоточности*, хотя более точное понятие здесь * — многопоточные и/или одновременные*. Если бы эта книга была о многопоточности и/или одновременности, мы бы посвятили данным понятиям исключительно всю книгу. В данной главе, всякий раз, когда мы используем понятие *«многопоточность»*, всегда стоит считать что это сокращение от понятия *«многопоточность и/или одновременность»*.
Многие языки программирования предлагают довольно устоявшиеся решения неполадок многопоточности. Например, Erlang обладает элегантной возможностью для многопоточности при передаче сообщений, но не определяет четких способов совместного использования состояний между потоками. Поддержка только подмножества возможных решений является разумным подходом для языков более высокого уровня, поскольку язык более высокого уровня обещает выгоду при отказе от некоторого управления над получением абстракций. Однако ожидается, что языки низкого уровня обеспечат решение с наилучшей производительностью в любом случае и будут иметь меньше абстракций по сравнению с аппаратным обеспечением. Поэтому Ржавчина предлагает множество средств для расчетов неполадок любым способом, который подходит для вашего случая и требований.
Многие языки программирования предлагают довольно устоявшиеся решения неполадок многопоточности. Например, Erlang обладает элегантной возможностью для многопоточности при передаче сообщений, но не определяет четких способов совместного использования состояний между потоками. Поддержка только подмножества возможных решений является разумным подходом для языков более высокого уровня, поскольку язык более высокого уровня обещает выгоду при отказе от некоторого управления над получением абстракций. Однако ожидается, что языки низкого уровня обеспечат решение с наилучшей производительностью в любом случае и будут иметь меньше абстракций по сравнению с аппаратным обеспечением. Поэтому Ржавчина предлагает множество средств для выявления неполадок любым способом, который подходит для вашего случая и требований.
## Использование потоков для одновременного выполнения рукописи
В большинстве современных операционных систем программная рукопись выполняется в виде *этапа*, причём операционная система способна управлять несколькими этапами сразу. Приложение, в свою очередь, может состоять из нескольких независимых частей, выполняемых одновременно. Средство, благодаря которому эти независимые части выполняются, называется *потоком*. Например, сетевой отдельный вычислитель (Web) может иметь несколько потоков для того, чтобы он мог обрабатывать больше одного запроса за раз.
В большинстве современных операционных систем программная рукопись выполняется в виде *этапа*, причём операционная система способна управлять несколькими этапами сразу. Приложение, в свою очередь, может состоять из нескольких независимых частей, выполняемых одновременно. Средство, благодаря которому эти независимые части выполняются, называется *потоком*. Например, сетевой отдельный вычислитель (Web) может использовать несколько потоков для того, чтобы обрабатывать больше одного запроса за раз.
Разбиение вычислений на несколько потоков может повысить производительность приложения, поскольку приложение выполняет несколько задач одновременно, но такое разбиение также добавляет сложности. Поскольку потоки могут работать одновременно, неизвестно какой из потоков выполнится раньше - а какой позже. Это может привести к таким неполадкам, как:
Разбиение производимых вычислений в несколько потоков может повысить производительность приложения, поскольку приложение выполняет несколько задач одновременно, но такое разбиение также добавляет сложности. Поскольку потоки могут выполняться одновременно, неизвестно какой из потоков завершиться раньше - а какой позже. Это может привести к таким неполадкам, как:
- Состояния гонки, когда потоки обращаются к данным, либо внешним источникам, несогласованно.
- Взаимные запреты и ограничения, когда два потока ожидают друг друга, не позволяя тем самым продолжить работу каждому из них.
- Взаимные запреты и ограничения, когда два потока ожидают ответ друг от друга, не позволяя тем самым продолжить выполнение каждому из них.
- Ошибки, которые случаются только в определённых случаях, которые трудно воспроизвести и, соответственно исправить.
Ржавчина пытается решить возникающие сбои при использовании потоков, но программирование в многопоточной среде все ещё требует тщательного обдумывания содержимого рукописи, которая отличается от содержание рукописи программ, работающих в одном потоке.
Ржавчина пытается решить возникающие сбои при использовании потоков, но испольозование многопоточности все ещё требует тщательного обдумывания для правильного написания содержимого рукописи, которая отличается от содержания рукописи приложений, работающих в одном потоке.
Языки программирования выполняют потоки несколькими различными способами, и многие операционные системы предоставляют API, который язык может вызывать для создания новых потоков. Встроенная библиотека Ржавчины использует прообраз выполнения потоков *1:1*, при которой одному потоку операционной системы соответствует ровно один "языковой" поток. Существуют дополнения, в которых использованы другие подходы многопоточности, отличающиеся от подхода 1:1.
@ -31,26 +31,26 @@ the changes are likely to be из-за the threads запщущен differently
@@ -31,26 +31,26 @@ the changes are likely to be из-за the threads запщущен differently
changes in the compiler -->
```text
Число 1 вызвано из основного потока!
Число 1 вызвано из порожденного потока!
Число 2 вызвано из основного потока!
Число 2 вызвано из порожденного потока!
Число 3 вызвано из основного потока!
Число 3 вызвано из порожденного потока!
Число 4 вызвано из основного потока!
Число 4 вызвано из порожденного потока!
Число 5 вызвано из порожденного потока!
Число 1 выведено из основного потока!
Число 1 выведено из порожденного потока!
Число 2 выведено из основного потока!
Число 2 выведено из порожденного потока!
Число 3 выведено из основного потока!
Число 3 выведено из порожденного потока!
Число 4 выведено из основного потока!
Число 4 выведено из порожденного потока!
Число 5 выведено из порожденного потока!
```
Вызовы `thread::sleep` заставляют поток на короткое время останавливать своё выполнение, позволяя выполняться другим потокам. Очерёдность выполнения потоков вероятно будет меняться, но это не обязательно: это зависит от того, как ваша операционная система управляет потоками. В этом круговороте основной поток вызывается первым, несмотря на то, что указание вызова порождённого потока появляется раньше в рукописи. И даже несмотря на то, что мы явно указали вызов порождённого потока до тех пор, пока значение `i` не достигнет числа 9, оно успело дойти только до 5, когда основной поток завершился.
Если вы запустите эту рукопись - то увидите вызовы только из основного потока или не увидите вызовы из других потоков. Попробуйте увеличить числа в рядах, чтобы дать операционной системе больше возможностей для переключения между потоками.
Если вы запустите эту рукопись - то увидите вызовы только из основного потока или не увидите вызовы из других потоков. Попробуйте увеличить числовые указатели в рядах круговоротов `for`, чтобы дать операционной системе больше возможностей для переключения между потоками.
### Ожидание завершения работы всех потоков используя `join`
Рукопись в приложении 16-1 преждевременно останавливает порождённый поток в большинстве случаев, из-за завершения основного потока. Более того, так как порядок выполнения потоков чётко не определён, эта рукопись не даёт заверения, что порождённый поток вообще начнёт исполняться!
Мы можем исправить неполадку, когда созданный поток не запускается или завершается преждевременно, сохранив возвращаемое значение `thread::spawn` в какой-либо переменной. Вид возвращаемого значения `thread::spawn` — `JoinHandle` . `JoinHandle` — это владелец значения, которое, при вызове способа `join` , будет ждать завершения своего потока. Приложение 16-2 отображает, как использовать `JoinHandle` потока, созданного в приложении 16-1, и вызывать способ (функцию) `join` , для того, чтобы убедиться, что порождённый поток завершится раньше, чем поток `main`:
Мы можем исправить неполадку, когда созданный поток не запускается или завершается преждевременно, сохранив возвращаемое значение `thread::spawn` в какой-либо переменной. Вид возвращаемого значения `thread::spawn` — `JoinHandle` . `JoinHandle` — это владелец значения, которое, при вызове способа `join` , будет ждать завершения своего потока. Приложение 16-2 отображает, как использовать `JoinHandle` потока, созданного в приложении 16-1, и вызывать способ (функцию) `join` , для того, чтобы убедиться, что порождённый поток завершится раньше, чем основной поток `main`:
<spanclass="filename">Файл: src/main.rs</span>
@ -58,33 +58,33 @@ changes in the compiler -->
@@ -58,33 +58,33 @@ changes in the compiler -->
<spanclass="caption">Приложение 16-2. Добавления владельца `владение` для сохранение значения потока <code>thread::spawn</code> в виде <code>JoinHandle</code>, заверяет, что дополнительный поток выполнит всю необходимую работу, перед тем, как завершится основной поток `fn main`</span>
<spanclass="caption">Приложение 16-2. Добавления владельца переменной `владение` служит для присваивания возвращаемого значения потока <code>thread::spawn</code> в виде <code>JoinHandle</code>. Ипользование данного способа заверяет, что порожлённый поток выполнит все вложенные действия, до того, как основной поток `fn main` завершит все свои действия</span>
Вызов способа `join` запрещает исполнение текущему основному потоку, пока порожденный поток, представленный способом `join` не завершится. *Запрет* для потока означает, что потоку запрещено выполнять работу или завершить работу. Поскольку мы поместили вызов `join` после круговорота `for` основного потока, выполнение приложения 16-2 должно привести к выводу, примерно такому:
Использование способа потока `join` запрещает преждевреенное завершение основного потока. Он не будет завершён пока порожденный поток, представленный способом `join` не выполнит все действия (не завершится). *Запрет* для потока означает, что потоку запрещено выполнять действия или завершиться. Поскольку мы используем способ `join`, размещенный после круговорота основного потока `for` , итог рукописи приложения 16-2 долен привести примерно к следующему итогуу:
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be из-за the threads запщущен differently rather than
changes in the compiler -->
```text
Число 1 вызвано из основного потока!
Число 2 вызвано из основного потока!
Число 1 вызвано из порожденного потока!
Число 3 вызвано из основного потока!
Число 2 вызвано из порожденного потока!
Число 4 вызвано из основного потока!
Число 3 вызвано из порожденного потока!
Число 4 вызвано из порожденного потока!
Число 5 вызвано из порожденного потока!
Число 6 вызвано из порожденного потока!
Число 7 вызвано из порожденного потока!
Число 8 вызвано из порожденного потока!
Число 9 вызвано из порожденного потока!
Число 1 выведено из основного потока!
Число 2 выведено из основного потока!
Число 1 выведено из порожденного потока!
Число 3 выведено из основного потока!
Число 2 выведено из порожденного потока!
Число 4 выведено из основного потока!
Число 3 выведено из порожденного потока!
Число 4 выведено из порожденного потока!
Число 5 выведено из порожденного потока!
Число 6 выведено из порожденного потока!
Число 7 выведено из порожденного потока!
Число 8 выведено из порожденного потока!
Число 9 выведено из порожденного потока!
```
Два потока продолжают чередоваться, но основной поток находится в ожидании из-за вызова `владение.join()` и не завершается до тех пор, пока не завершится запущенный поток.
Два потока продолжают исполняться по очереди, но основной поток не может завершиться из-за использования способа `join()` и завершится только по завершении порождённого потока.
Но давайте посмотрим, что произойдёт, если мы вместо этого переместим `владение.join()` перед круговоротом`for` в `main`, например так:
Но давайте посмотрим, что произойдёт, если способ `join()` будет поставлен перед круговоротом основного потока`for` в `main`, например так:
<spanclass="filename">Файл: src/main.rs</span>
@ -92,35 +92,35 @@ changes in the compiler -->
@@ -92,35 +92,35 @@ changes in the compiler -->
Основной поток будет ждать завершения порождённого потока, а затем запустит свой круговорот `for` , поэтому выходные данные больше не будут чередоваться, как показано ниже:
Основной не будет исполняться до завершения порождённого потока, после чего будет запущен круговорот основного потока `for` . Поэтому выполнение круговоротов больше не будет чередоваться, как показано ниже:
<!-- Not extracting output because changes to this output aren't significant;
the changes are likely to be из-за the threads запщущен differently rather than
changes in the compiler -->
```text
Число 1 вызвано из порожденного потока!
Число 2 вызвано из порожденного потока!
Число 3 вызвано из порожденного потока!
Число 4 вызвано из порожденного потока!
Число 5 вызвано из порожденного потока!
Число 6 вызвано из порожденного потока!
Число 7 вызвано из порожденного потока!
Число 8 вызвано из порожденного потока!
Число 9 вызвано из порожденного потока!
Число 1 вызвано из основного потока!
Число 2 вызвано из основного потока!
Число 3 вызвано из основного потока!
Число 4 вызвано из основного потока!
Число 1 выведено из порожденного потока!
Число 2 выведено из порожденного потока!
Число 3 выведено из порожденного потока!
Число 4 выведено из порожденного потока!
Число 5 выведено из порожденного потока!
Число 6 выведено из порожденного потока!
Число 7 выведено из порожденного потока!
Число 8 выведено из порожденного потока!
Число 9 выведено из порожденного потока!
Число 1 выведено из основного потока!
Число 2 выведено из основного потока!
Число 3 выведено из основного потока!
Число 4 выведено из основного потока!
```
Небольшие подробности, такие как место вызова `join`, могут повлиять на то, выполняются ли ваши потоки одновременно.
Место постановки `join` влияет на выполнение потоков..
### Использование `move`-замыканий в потоках
Мы часто используем ключевое слово `move` с замыканиями, переданными в `thread::spawn` потому, что в этом случае замыкание получает из окружения права владения на используемые им переменные, таким образом передавая права владения этими переменными от одного потока к другому. В разделе ["Получение ссылок или передача прав владения"] Главы 13 мы обсудили `move` в среде замыканий. Теперь мы сосредоточимся на взаимодействии между `move` и `thread::spawn`.
Мы часто используем ключевое слово `move` с замыканиями, переданными в `thread::spawn` потому, что в этом случае замыкание получает из окружения права владения на используемые им переменные, таким образом передавая права владения этими переменными от одного потока к другому. В разделе ["Получение ссылок или передача прав владения"] Главы 13 мы обсудили способ `move` в среде замыканий. Теперь мы сосредоточимся на взаимодействии между `move` и `thread::spawn`.
Обратите внимание, что в приложении 16-1 замыкание, которое мы передаём в `thread::spawn` не принимает переменных: мы не используем никаких данных из основного потока в рукописи порождённого потока. Чтобы использовать данные из основного потока в порождённом потоке, замыкание порождённого потока должно получать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать вектор в главном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение.
Обратите внимание, что в приложении 16-1 замыкание, которое мы используем в `thread::spawn` не принимает переменных: мы не используем никаких данных из основного потока в рукописи порождённого потока. Чтобы использовать переменные или данные из основного потока в порождённом потоке, замыкание в порождённом потоке должно получать значения, которые ему необходимы. Приложение 16-3 показывает попытку создать ряд в основном потоке и использовать его в порождённом потоке. Тем не менее, это не будет работать, как вы увидите через мгновение.
<spanclass="filename">Файл: src/main.rs</span>
@ -179,7 +179,7 @@ after automatic regeneration, look at listings/ch16-fearless-concurrency/listing
@@ -179,7 +179,7 @@ after automatic regeneration, look at listings/ch16-fearless-concurrency/listing
Правила владения Ржавчины снова нас спасли! Мы получили те же самую ошибку, что и в рукописи из приложения 16-3, потому что Ржавчина передаёт право владения над вектором `v` только порожденному потоку, соответственно, основной поток ничего не может сделать, так как порождённый поток не возвращает право владения над вектором `v` и соответственно переменная `v` более не действительна. Сообщив Ржавчине о передаче владения `v` в порождаемый поток, мы заверяем Ржавчину, что использование `v` в основном потоке исключено . Если мы изменим Приложение 16-4 таким же образом, то мы нарушаем правила владения при попытке использовать `v` в главном потоке. Ключевое слово `move` определяет поведение, согласно устоявшихся правил Ржавчины по заимствованию, которые не позволяет нам нарушать правила владения.
Правила владения Ржавчины снова нас спасли! Мы получили те же самую ошибку, что и в рукописи из приложения 16-3, потому что Ржавчина передаёт право владения над вектором `v` только порожденному потоку, соответственно, основной поток ничего не может сделать, так как порождённый поток не возвращает право владения над вектором `v` и соответственно переменная `v` более не действительна. Сообщив Ржавчине о передаче владения `v` в порождаемый поток, мы заверяем Ржавчину, что использование `v` в основном потоке исключено . Если мы изменим Приложение 16-4 таким же образом, то мы нарушаем правила владения при попытке использовать `v` в основном потоке. Ключевое слово `move` определяет поведение, согласно устоявшихся правил Ржавчины по заимствованию, которые не позволяет нам нарушать правила владения.
Имея достаточное понимание о потоках и API потоков, давайте рассмотрим, что мы можем *сделать* с помощью потоков.
Встроенная библиотека Ржавчины предоставляет потоки для передачи сообщений и виды данных умных указателей, такие как `Mutex<T>` и `Arc<T>`, которые можно безопасно использовать в многопоточных средах. Перечень видов данных и оценщик заимствований заверяют, что рукопись использующий эти решения не будет содержать гонки данных или недействительные ссылки. Получив собирающийся рукопись, вы можете быть уверены, что она будет успешно работать в нескольких потоках без ошибок, которые трудно обнаружить в других языках. Многопоточное программирование больше не является подходом, которую стоит опасаться: иди вперёд и сделай свои приложения многопоточными безбоязненно!
Далее мы поговорим об идиоматичных способах расчетов неполадок и внутреннего выстраивания
Далее мы поговорим об идиоматичных способах выявления неполадок и внутреннего порядка
решений по мере усложнения ваших приложений на Ржавчине. Кроме того, мы обсудим как устойчивого выражения Ржавчины связаны с теми, с которыми вы, возможно, знакомы по предметно-направленному программированию.