Главная страница
Навигация по странице:

  • Листинг 20-23: Отправка и получение значений перечисления Message и выход из цикла, если Workerполучает перечисление

  • Листинг 20-24: Отправка Message::Terminate рабочим потокам перед вызовом join в каждом потоке

  • Код 20-25. Выключение сервера после обслуживания двух запросов с помощью выхода из цикла

  • Дополнительная информация Следующие разделы содержат справочные материалы, которые могут оказаться полезными в вашем путешествии по Rust. Приложение A: Ключевые слова

  • Ключевые слова, использующиеся в Rust в настоящее время

  • Ключевые слова, зарезервированные для будущего использования

  • Дополнение Б: Операторы и обозначения

  • Таблица Б-1: Операторы Оператор Пример Объяснение Перегружаемость

  • Оператор Пример Объяснение Перегружаемость

  • Язык программирования Rust


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница60 из 62
    1   ...   54   55   56   57   58   59   60   61   62
    Сигнализация потокам прекратить прослушивание получения задач
    Теперь со всеми внесёнными нами изменениями код компилируется без каких-либо предупреждений. Но плохая новость в том, что этот код ещё не работает так, как мы этого хотим. Ключом является логика в замыканиях, запускаемых потоками экземпляров
    Worker
    . В данный момент мы вызываем join
    , но это не завершит потоки, потому что они работают в цикле loop бесконечно в поиске новой задачи (job). Если мы попытаемся удалить
    ThreadPool в текущей реализации drop
    , то основной поток навсегда заблокируется в ожидании завершения первого потока из пула.
    Для решения этой проблемы, мы изменим потоки так, чтобы они прослушивали либо задачи
    Job для её выполнения, либо сигнал, что они должны прекратить прослушивание и выйти из бесконечного цикла. Вместо отправки экземпляров задач
    Job
    , наш канал отправит один из этих двух вариантов перечисления.
    Файл: src/lib.rs
    Это перечисление
    Message будет либо вариантом
    NewJob
    , который внутри держит
    Job с
    потоком для выполнения, или это будет вариант
    Terminate
    , который сделает так, чтобы поток вышел из цикла и остановился.
    Нам нужно настроить канал для использования значений типа
    Message
    , а не типа
    Job как показано в листинге 20-23.
    Файл: src/lib.rs impl
    Drop for
    ThreadPool { fn drop
    (&
    mut self
    ) { for worker in
    &
    mut self
    .workers { println!
    (
    "Shutting down worker {}"
    , worker.id); if let
    Some
    (thread) = worker.thread.take() { thread.join().unwrap();
    }
    }
    }
    }
    {{#rustdoc_include ../listings/ch20-web-server/no-listing-
    07
    -define-message- enum
    /
    src
    /lib.rs:here}}

    Листинг 20-23: Отправка и получение значений перечисления
    Message
    и выход из цикла, если
    Worker
    получает перечисление
    Message::Terminate
    Чтобы встроить в код перечисление
    Message
    , нужно изменить
    Job на
    Message в двух местах: в объявлении
    ThreadPool и сигнатуре
    Worker::new
    . Метод execute у
    ThreadPool должен отправлять задания, завёрнутые в вариант
    Message::NewJob
    . Затем в
    Worker::new
    , где
    Message получен из канала, задание (job) будет обработано, если получен вариант
    NewJob и поток выйдет из цикла, если получен вариант
    Terminate
    С такими изменениями код компилируется и продолжит функционировать так же, как и после кода 20-20. Но мы получим предупреждение, потому что мы не создаём сообщения типа
    Terminate
    . Давайте исправим это предупреждение, изменив нашу реализацию
    Drop как в листинге 20-25.
    pub struct
    ThreadPool
    { workers:
    Vec
    , sender:
    Option
    >,
    }
    // --snip-- impl
    ThreadPool { pub fn new
    (size: usize
    ) -> ThreadPool {
    // --snip--
    ThreadPool { workers, sender:
    Some
    (sender),
    }
    } pub fn execute
    (&
    self
    , f: F) where
    F:
    FnOnce
    () +
    Send
    +
    'static
    ,
    { let job =
    Box
    ::new(f); self
    .sender.as_ref().unwrap().send(job).unwrap();
    }
    } impl
    Drop for
    ThreadPool { fn drop
    (&
    mut self
    ) { drop
    (
    self
    .sender.take()); for worker in
    &
    mut self
    .workers { println!
    (
    "Shutting down worker {}"
    , worker.id); if let
    Some
    (thread) = worker.thread.take() { thread.join().unwrap();
    }
    }
    }
    }

    Файл: src/lib.rs
    Листинг 20-24: Отправка
    Message::Terminate
    рабочим потокам перед вызовом
    join
    в каждом потоке
    Теперь мы дважды проходим по потокам "работников": один раз для отправки одного сообщения
    Terminate каждому потоку и один раз для вызова join у каждого рабочего потока. Если бы мы попытались отправить сообщение и сразу же выполнить join в этом же цикле, мы не смогли бы гарантировать, что рабочий поток в текущей итерации получит сообщение из канала.
    Чтобы лучше понять, почему нужны два отдельных цикла, представьте сценарий с двумя работниками. Если бы мы использовали один цикл для перебора каждого работника, на первой итерации сообщение о прекращении работы было бы отправлено в канал и метод join вызывался бы у первого рабочего потока. Если этот первый поток занят обработкой запроса в данный момент, второй рабочий поток получит сообщение о завершении из канала и завершит свою работу. В главном потоке мы бы остались в ожидании завершения работы первого рабочего потока, но этого не произошло, потому что второй поток получил сообщение о завершении. Это ситуация взаимной блокировки
    (deadlock)!
    Чтобы предотвратить такой сценарий, мы сначала помещаем все сообщения
    Terminate в канал в первом цикле; затем мы объединяем (join) для завершения все потоки во втором цикле. Каждый рабочий поток прекратит получать запросы из канала, как только получит сообщение о завершении. Таким образом, мы можем быть уверены, что если мы отправим количество завершающих сообщений равное количеству рабочих потоков то,
    каждый получит сообщение о завершении до вызова join в его потоке.
    impl
    Worker { fn new
    (id: usize
    , receiver: Arc>>) -> Worker { let thread = thread::spawn(
    move
    || loop
    { match receiver.lock().unwrap().recv() {
    Ok
    (job) => { println!
    (
    "Worker {id} got a job; executing."
    ); job();
    }
    Err
    (_) => { println!
    (
    "Worker {id} disconnected; shutting down."
    ); break
    ;
    }
    }
    });
    Worker { id, thread:
    Some
    (thread),
    }
    }
    }

    Чтобы увидеть этот код в действии, давайте изменим main
    , чтобы принимать только два запроса, прежде чем корректно завершить работу сервера как показано в листинге 20-
    25.
    Файл: src/bin/main.rs
    Код 20-25. Выключение сервера после обслуживания двух запросов с помощью выхода из цикла
    Вы бы не хотели, чтобы реальный веб-сервер отключался после обслуживания только двух запросов. Этот код всего лишь демонстрирует, что корректное завершение работы и освобождение ресурсов находятся в рабочем состоянии.
    Метод take определён в типаже
    Iterator и ограничивает итерацию максимум первыми двумя элементами.
    ThreadPool выйдет из области видимости в конце main и будет запущена его реализация drop
    Запустите сервер с cargo run и сделайте три запроса. Третий запрос должен выдать ошибку и в терминале вы должны увидеть вывод, подобный следующему:
    Вы возможно увидите другой порядок рабочих потоков и напечатанных сообщений. Мы можем увидеть, как этот код работает по сообщениям: "работники" номер 0 и 3 получили первые два запроса, затем на третьем запросе сервер прекратил принимать соединения.
    Когда
    ThreadPool выходит из области видимости в конце main
    , то срабатывает его реализация типажа
    Drop и пул сообщает всем рабочим потокам прекратить выполнение. Каждый рабочий поток распечатывает сообщение, когда видит сообщение о завершении, а затем пул потоков вызывает join
    , чтобы завершить работу каждого рабочего потока.
    {{#rustdoc_include ../listings/ch20-web-server/listing-
    20
    -
    25
    /src/bin/main.rs:here}}
    $
    cargo run
    Compiling hello v0.1.0 (file:///projects/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 1.0s
    Running `target/debug/main`
    Worker 0 got a job; executing.
    Worker 3 got a job; executing.
    Shutting down.
    Sending terminate message to all workers.
    Shutting down all workers.
    Shutting down worker 0
    Worker 1 was told to terminate.
    Worker 2 was told to terminate.
    Worker 0 was told to terminate.
    Worker 3 was told to terminate.
    Shutting down worker 1
    Shutting down worker 2
    Shutting down worker 3

    Обратите внимание на один интересный аспект этого конкретного выполнения:
    ThreadPool отправил сообщения о завершении в канал и прежде чем какой-либо рабочий получил сообщение, мы пытались присоединить (join) "работника" с номером 0.
    Рабочий поток 0 ещё не получил сообщение о прекращении, поэтому основной поток заблокировал ожидание потока 0 для завершения. Тем временем каждый из рабочих потоков получил сообщения об завершении. Когда рабочий поток 0 завершился,
    основной поток ждал окончания завершения выполнения остальных рабочих потоков. В
    этот момент все они получили сообщение о завершении и смогли завершиться.
    Поздравления! Теперь мы завершили проект; у нас есть базовый веб-сервер, который использует пул потоков для асинхронных ответов. Мы можем выполнить корректное завершение работы сервера, который очищает все потоки в пуле.
    Вот полный код для справки:
    Файл: src/bin/main.rs
    Файл: src/lib.rs
    В коде можно сделать больше! Если вы хотите продолжить совершенствование этого проекта, вот несколько идей:
    Добавьте больше документации в
    ThreadPool и его публичным методам.
    Добавьте тесты функционалу из библиотеки.
    Заменить вызовы unwrap на более правильную обработку ошибок.
    Используйте
    ThreadPool для выполнения некоторых других задач, помимо обслуживания веб-запросов.
    Найдите крейт для пула потоков на crates.io и реализуйте аналогичный веб-сервер,
    используя такой крейт. Затем сравните его API и надёжность с пулом потоков,
    который мы реализовали.
    Итоги
    Отличная работа! Вы сделали это к концу книги! Мы хотим поблагодарить вас за то, что присоединились к нам в этом путешествии по языку Rust. Теперь вы готовы реализовать свои собственные проекты на Rust и помочь с проектами другим людям. Имейте в виду,
    что существует приветливое сообщество других Rust разработчиков, которые хотели бы помочь вам с любыми сложными задачами с которыми вы столкнётесь в своём Rust путешествии.
    {{#rustdoc_include ../listings/ch20-web-server/no-listing-
    08
    - final
    - code/src/bin/main.rs}}
    {{#rustdoc_include ../listings/ch20-web-server/no-listing-
    08
    - final
    - code/src/lib.rs}}

    Дополнительная информация
    Следующие разделы содержат справочные материалы, которые могут оказаться полезными в вашем путешествии по Rust.

    Приложение A: Ключевые слова
    Следующий список содержит ключевые слова, зарезервированные для текущего или будущего использования в языке Rust. Как таковые их нельзя использовать в качестве идентификаторов (за исключением сырых идентификаторов, которые мы обсудим в разделе
    «Сырые идентификаторы»
    ). Идентификаторы — это имена функций,
    переменных, параметров, полей структур, модулей, крейтов, констант, макросов,
    статических значений, атрибутов, типов, свойств или времён жизни.
    Ключевые слова, использующиеся в Rust в настоящее время
    Ниже приведён список используемых в настоящее время ключевых слов с описанием их функций.
    as
    — выполнить примитивное преобразование, уточнить конкретную характеристику, которую содержит объект, или переименовать элемент в выражении use async
    — вернуть
    Future вместо блокирования текущего потока await
    — приостановить выполнение до тех пор, пока не будет готов результат
    Future break
    — немедленно завершить цикл const
    — определить константу или неизменяемый указатель continue
    — досрочно перейти к следующей итерации цикла crate
    — в пути модуля ссылается на корень пакета dyn
    — динамическая отсылка к объекту характеристики else
    — ветвь для конструкций потока управления if и if let в случае, если никакая другая ветвь не была исполнена enum
    — определить перечисление extern
    — подключить внешнюю функцию или переменную false
    — логический литерал «ложь»
    fn
    — определить функцию или тип указателя на функцию for
    — перебор элементов итератора, реализация характеристики или указание срока жизни более продолжительного периода if
    — ветвление на основе результата условного выражения impl
    — реализовать функциональность непосредственно или через характеристику in
    — часть синтаксиса определения цикла for let
    — объявить переменную loop
    — безусловный (бесконечный) цикл match
    — сопоставить значение с образцами mod
    — определить модуль move
    — заставить замыкание принять на себя владение всеми своими захватами
    mut
    — обозначить изменяемость для ссылок, сырых указателей или привязок к шаблонам pub
    — обозначить публичную доступность полей структур, блоков impl или модулей ref
    — привязка по ссылке return
    — возврат из функции
    Self
    — псевдоним для типа, который мы определяем или реализуем self
    — объект, содержащий этот метод или текущий модуль static
    — глобальная переменная или время жизни на протяжении всего выполнения программы struct
    — определить структуру super
    — родительский модуль текущего модуля trait
    — определить характеристику true
    — логический литерал «истина»
    type
    — определить псевдоним типа или ассоциированный тип union
    — определение union
    ; является ключевым словом только при использовании в объявлении союза unsafe
    — обозначить небезопасный код, функции, характеристики или реализации use
    — ввести объекты в область действия where
    обозначить утверждения, которые ограничивают тип while
    — цикл, работающий относительно результата условного выражения
    Ключевые слова, зарезервированные для будущего использования
    Следующие ключевые слова ещё не имеют никакой функциональности, но зарезервированы Rust для возможного использования в будущем.
    abstract become box do final macro override priv try typeof unsized virtual yield
    Сырые идентификаторы

    Сырые идентификаторы — это синтаксис, позволяющий использовать ключевые слова там, где обычно они не могут быть. Для создания и использования сырого идентификатора к ключевому слову добавляется префикс r#
    Например, ключевое слово match
    . Если вы попытаетесь скомпилировать следующую функцию, использующую в качестве имени match
    :
    Файл: src/main.rs вы получите ошибку:
    Ошибка говорит о том, что вы не можете использовать ключевое слово match в качестве идентификатора функции. Чтобы получить возможность использования слова match в
    качестве имени функции, нужно использовать синтаксис «сырых идентификаторов»,
    например так:
    Файл: src/main.rs
    Этот код скомпилируется без ошибок. Обратите внимание, что префикс r#
    в определении имени функции указан так же, как он указан в месте её вызова в main
    Сырые идентификаторы позволяют вам использовать любое слово, которое вы выберете, в качестве идентификатора, даже если это слово окажется зарезервированным ключевым словом. Это даёт нам больше свободы в выборе имён идентификаторов, а также позволяет нам интегрироваться с программами, написанными на языке, где эти слова не являются ключевыми. Кроме того, необработанные идентификаторы позволяют вам использовать библиотеки, написанные в версии Rust, отличной от используемой в вашем крейте. Например, try не является ключевым словом в выпуске 2015 года, но есть в выпуске 2018 года. Если вы зависите от библиотеки, написанной с использованием версии 2015 года и имеющей функцию try
    , вам потребуется использовать синтаксис сырого идентификатора, в данном случае r#try
    , для вызова этой функции из кода fn match
    (needle: &
    str
    , haystack: &
    str
    ) -> bool
    { haystack.contains(needle)
    } error: expected identifier, found keyword `match`
    --> src/main.rs:4:4
    |
    4 | fn match(needle: &str, haystack: &str) -> bool {
    | ^^^^^ expected identifier, found keyword fn r
    #
    match
    (needle: &
    str
    , haystack: &
    str
    ) -> bool
    { haystack.contains(needle)
    } fn main
    () { assert!
    (r#
    match
    (
    "foo"
    ,
    "foobar"
    ));
    }
    версии 2018 года. См.
    Приложение E
    для получения дополнительной информации о редакциях Rust.

    Операторы_и_обозначения'>Дополнение Б: Операторы и обозначения
    Это дополнение содержит глоссарий синтаксиса Rust, включая операторы и другие обозначения, которые появляются сами по себе или в контексте путей, обобщений,
    типажей, макросов, атрибутов, комментариев, кортежей и скобок.
    Операторы
    Таблица Б-1 содержит операторы языка Rust, пример появления оператора, короткое объяснение, возможность перегрузки оператора. Если оператор можно перегрузить, то показан типаж, с помощью которого его можно перегрузить.
    Таблица Б-1: Операторы
    Оператор
    Пример
    Объяснение
    Перегружаемость
    !
    ident!(...)
    , ident!{...}
    , ident![...]
    Вызов макроса
    !
    !expr
    Побитовое или логическое отрицание
    Not
    !=
    expr != expr
    Сравнение "не равно"
    PartialEq
    %
    expr % expr
    Остаток от деления
    Rem
    %=
    var %= expr
    Остаток от деления и присваивание
    RemAssign
    &
    &expr
    ,
    &mut expr
    Заимствование
    &
    &type
    ,
    &mut type
    ,
    &'a type
    ,
    &'a mut type
    Указывает что данный тип заимствуется
    &
    expr & expr
    Побитовое И
    BitAnd
    &=
    var &= expr
    Побитовое И и присваивание
    BitAndAssign
    &&
    expr && expr
    Логическое И
    *
    expr * expr
    Арифметическое умножение
    Mul
    *=
    var *= expr
    Арифметическое умножение и присваивание
    MulAssign

    Оператор
    Пример
    Объяснение
    Перегружаемость
    *
    *expr
    Разыменование ссылки
    Deref
    *
    *const type
    ,
    *mut type
    Указывает, что данный тип является сырым указателем
    +
    trait + trait
    ,
    'a + trait
    Соединение ограничений типа
    +
    expr + expr
    Арифметическое сложение
    Add
    +=
    var += expr
    Арифметическое сложение и присваивание
    AddAssign
    ,
    expr, expr
    Аргумент и разделитель элементов
    -
    - expr
    Арифметическое отрицание
    Neg
    - expr - expr
    Арифметическое вычитание
    Sub
    - var -= expr
    Арифметическое вычитание и присваивание
    SubAssign
    ->
    fn(...) -> type
    ,
    |...|
    -> type expr.ident
    Доступ к элементу
    , expr..
    ,
    ..expr
    , expr..expr
    Указывает на диапазон чисел,
    исключая правый
    PartialOrd
    ..=
    ..=expr
    , expr..=expr
    Указывает на диапазон чисел,
    включая правый
    PartialOrd
    ..expr
    Синтаксис обновления структуры variant(x, ..)
    ,
    struct_type { x, .. }
    Привязка «И все остальное»

    1   ...   54   55   56   57   58   59   60   61   62


    написать администратору сайта