Spec-Zone.ru › Tokio

Структура Mutex

pub struct Mutex<T: ?Sized> { /* private fields */ }
Доступно только при включённой возможности crate sync.

Асинхронный тип, похожий на Mutex.

Этот тип работает подобно std::sync::Mutex, но с двумя существенными отличиями: lock — асинхронный метод, поэтому он не блокирует выполнение, а блокировка спроектирована так, чтобы её можно было удерживать между точками .await.

Мьютекс Tokio работает по гарантированному принципу FIFO. Это означает, что порядок, в котором задачи вызывают метод lock, в точности совпадает с порядком получения ими блокировки.

Какой тип мьютекса следует использовать?

Вопреки распространённому мнению, в асинхронном коде допустимо и часто предпочтительно использовать обычный Mutex из стандартной библиотеки.

Преимущество асинхронного мьютекса перед блокирующим заключается в возможности удерживать его заблокированным между точками .await. Из-за этого асинхронный мьютекс дороже блокирующего, поэтому в случаях, когда можно использовать блокирующий мьютекс, предпочтительнее выбрать именно его. Основной вариант применения асинхронного мьютекса — предоставление совместного изменяемого доступа к ресурсам ввода-вывода, например к подключению к базе данных. Если за мьютексом находятся только данные, обычно уместно использовать блокирующий мьютекс, например из стандартной библиотеки или parking_lot.

Обратите внимание: хотя компилятор не запрещает std Mutex удерживать защиту между точками .await в ситуациях, когда задача не может перемещаться между потоками, на практике это почти никогда не приводит к корректному параллельному коду, поскольку легко может вызвать взаимные блокировки.

Распространённый подход — обернуть Arc<Mutex<...>> в структуру, которая предоставляет синхронные методы для выполнения операций с содержащимися в ней данными, и блокировать мьютекс только внутри этих методов. Пример mini-redis иллюстрирует этот подход.

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

Примеры:

use tokio::sync::Mutex;
use std::sync::Arc;

let data1 = Arc::new(Mutex::new(0));
let data2 = Arc::clone(&data1);

tokio::spawn(async move {
    let mut lock = data2.lock().await;
    *lock += 1;
});

let mut lock = data1.lock().await;
*lock += 1;
use tokio::sync::Mutex;
use std::sync::Arc;

let count = Arc::new(Mutex::new(0));

for i in 0..5 {
    let my_count = Arc::clone(&count);
    tokio::spawn(async move {
        for j in 0..10 {
            let mut lock = my_count.lock().await;
            *lock += 1;
            println!("{} {} {}", i, j, lock);
        }
    });
}

loop {
    if *count.lock().await >= 50 {
        break;
    }
}
println!("Count hit 50.");

В этом примере стоит обратить внимание на несколько моментов.

  1. Мьютекс обёрнут в Arc, чтобы его можно было совместно использовать в нескольких потоках.
  2. Каждая запущенная задача получает блокировку и освобождает её на каждой итерации.
  3. Изменение данных, защищённых мьютексом, выполняется через разыменование полученной блокировки, как показано в строках 13 и 20.

Мьютекс Tokio работает по простому принципу FIFO (первым пришёл — первым вышел): все вызовы lock завершаются в порядке их выполнения. Таким образом, мьютекс является «справедливым» и предсказуемо распределяет блокировки доступа к внутренним данным. Блокировки освобождаются и захватываются снова после каждой итерации, поэтому каждый поток после однократного увеличения значения перемещается в конец очереди. Обратите внимание, что время запуска потоков может быть непредсказуемым, но после начала работы они чередуются предсказуемо. Наконец, поскольку в каждый момент времени существует только одна действующая блокировка, при изменении внутреннего значения возникновение состояния гонки невозможно.

Обратите внимание: в отличие от std::sync::Mutex, эта реализация не переводит мьютекс в отравленное состояние, если поток, удерживающий MutexGuard, завершается паникой. В таком случае мьютекс будет разблокирован. Если панику перехватить, данные, защищённые мьютексом, могут остаться в несогласованном состоянии.

Реализации

impl<T: ?Sized> Mutex<T>

pub fn new(t: T) -> Self
where T: Sized,

Создаёт новую блокировку в незаблокированном состоянии, готовую к использованию.

Примеры
use tokio::sync::Mutex;

let lock = Mutex::new(5);

pub const fn const_new(t: T) -> Self
where T: Sized,

Создаёт новую блокировку в незаблокированном состоянии, готовую к использованию.

При использовании нестабильной функции tracing объект Mutex, созданный с помощью const_new, не будет инструментирован. Поэтому он не будет виден в tokio-console. Вместо этого, если требуется создать инструментированный объект, следует использовать Mutex::new.

Примеры
use tokio::sync::Mutex;

static LOCK: Mutex<i32> = Mutex::const_new(5);

pub async fn lock(&self) -> MutexGuard<'_, T>

Блокирует этот мьютекс, заставляя текущую задачу уступить управление до получения блокировки. После получения блокировки функция возвращает MutexGuard.

Если мьютекс доступен для немедленного получения, этот вызов обычно не передаёт управление среде выполнения. Однако это не гарантируется при любых обстоятельствах.

Безопасность отмены

Этот метод использует очередь для справедливого распределения блокировок в порядке их запроса. Отмена вызова lock приведёт к потере места в очереди.

Примеры
use tokio::sync::Mutex;

let mutex = Mutex::new(1);

let mut n = mutex.lock().await;
*n = 2;

pub fn blocking_lock(&self) -> MutexGuard<'_, T>

Блокирующим образом блокирует этот Mutex. После получения блокировки функция возвращает MutexGuard.

Этот метод предназначен для случаев, когда мьютекс необходимо использовать как в асинхронном, так и в синхронном коде.

Паника

Эта функция вызывает панику, если её вызвать в контексте асинхронного выполнения.

  • Если вы находитесь в контексте асинхронного выполнения и вам нужно вызвать синхронную функцию, выполняющую одну из этих операций blocking_, рассмотрите возможность обернуть этот вызов в spawn_blocking() (или block_in_place()).
Примеры
use std::sync::Arc;
use tokio::sync::Mutex;

#[tokio::main]
async fn main() {
    let mutex =  Arc::new(Mutex::new(1));
    let lock = mutex.lock().await;

    let mutex1 = Arc::clone(&mutex);
    let blocking_task = tokio::task::spawn_blocking(move || {
        // This shall block until the `lock` is released.
        let mut n = mutex1.blocking_lock();
        *n = 2;
    });

    assert_eq!(*lock, 1);
    // Release the lock.
    drop(lock);

    // Await the completion of the blocking task.
    blocking_task.await.unwrap();

    // Assert uncontended.
    let n = mutex.try_lock().unwrap();
    assert_eq!(*n, 2);
}

pub fn blocking_lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T>

Блокирующим образом блокирует этот Mutex. После получения блокировки функция возвращает OwnedMutexGuard.

Этот метод идентичен Mutex::blocking_lock, за исключением того, что возвращаемый объект блокировки ссылается на Mutex через Arc, а не заимствует его. Поэтому для вызова этого метода Mutex должен быть обёрнут в Arc, а объект блокировки будет существовать в течение времени жизни 'static, поскольку он удерживает Mutex в памяти, хранящий Arc.

Паника

Эта функция вызывает панику, если её вызвать в контексте асинхронного выполнения.

  • Если вы находитесь в контексте асинхронного выполнения и вам нужно вызвать синхронную функцию, выполняющую одну из этих операций blocking_, рассмотрите возможность обернуть этот вызов в spawn_blocking() (или block_in_place()).
Примеры
use std::sync::Arc;
use tokio::sync::Mutex;

#[tokio::main]
async fn main() {
    let mutex =  Arc::new(Mutex::new(1));
    let lock = mutex.lock().await;

    let mutex1 = Arc::clone(&mutex);
    let blocking_task = tokio::task::spawn_blocking(move || {
        // This shall block until the `lock` is released.
        let mut n = mutex1.blocking_lock_owned();
        *n = 2;
    });

    assert_eq!(*lock, 1);
    // Release the lock.
    drop(lock);

    // Await the completion of the blocking task.
    blocking_task.await.unwrap();

    // Assert uncontended.
    let n = mutex.try_lock().unwrap();
    assert_eq!(*n, 2);
}

pub async fn lock_owned(self: Arc<Self>) -> OwnedMutexGuard<T>

Блокирует этот мьютекс, приостанавливая текущую задачу до получения блокировки. После получения блокировки этот метод возвращает OwnedMutexGuard.

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

Этот метод идентичен Mutex::lock, за исключением того, что возвращаемый объект-охранитель ссылается на Mutex с помощью Arc, а не заимствует его. Поэтому для вызова этого метода Mutex необходимо обернуть в Arc, а объект-охранитель будет жить в течение времени жизни 'static, поскольку он удерживает Mutex живым, храня Arc.

Безопасность отмены

Этот метод использует очередь, чтобы справедливо распределять блокировки в порядке поступления запросов. Отмена вызова lock_owned приводит к потере места в очереди.

Примеры
use tokio::sync::Mutex;
use std::sync::Arc;

let mutex = Arc::new(Mutex::new(1));

let mut n = mutex.clone().lock_owned().await;
*n = 2;

pub fn try_lock(&self) -> Result<MutexGuard<'_, T>, TryLockError>

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

Примеры
use tokio::sync::Mutex;

let mutex = Mutex::new(1);

let n = mutex.try_lock()?;
assert_eq!(*n, 1);

pub fn get_mut(&mut self) -> &mut T

Возвращает изменяемую ссылку на базовые данные.

Поскольку этот вызов изменяемо заимствует Mutex, фактическая блокировка не требуется: изменяемое заимствование на этапе компиляции гарантирует отсутствие блокировок.

Примеры
use tokio::sync::Mutex;

fn main() {
    let mut mutex = Mutex::new(1);

    let n = mutex.get_mut();
    *n = 2;
}

pub fn try_lock_owned( self: Arc<Self>, ) -> Result<OwnedMutexGuard<T>, TryLockError>

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

Этот метод идентичен Mutex::try_lock, за исключением того, что возвращаемый объект-охранитель ссылается на Mutex с помощью Arc, а не заимствует его. Поэтому для вызова этого метода Mutex необходимо обернуть в Arc, а объект-охранитель будет жить в течение времени жизни 'static, поскольку он удерживает Mutex живым, храня Arc.

Примеры
use tokio::sync::Mutex;
use std::sync::Arc;

let mutex = Arc::new(Mutex::new(1));

let n = mutex.clone().try_lock_owned()?;
assert_eq!(*n, 1);

pub fn into_inner(self) -> T
where T: Sized,

Потребляет мьютекс и возвращает базовые данные.

Примеры
use tokio::sync::Mutex;

let mutex = Mutex::new(1);

let n = mutex.into_inner();
assert_eq!(n, 1);

Реализации трейтов

impl<T> Debug for Mutex<T>
where T: Debug + ?Sized,

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Форматирует значение с помощью указанного форматтера. Подробнее

impl<T> Default for Mutex<T>
where T: Default,

fn default() -> Self

Возвращает «значение по умолчанию» для типа. Подробнее

impl<T> From<T> for Mutex<T>

fn from(s: T) -> Self

Преобразует входное значение в значение этого типа.

impl<T> Send for Mutex<T>
where T: ?Sized + Send,

impl<T> Sync for Mutex<T>
where T: ?Sized + Send,

Автоматические реализации трейтов

impl<T> !Freeze for Mutex<T>

impl<T> !RefUnwindSafe for Mutex<T>

impl<T> !UnwindSafe for Mutex<T>

impl<T> Unpin for Mutex<T>
where T: Unpin + ?Sized,

impl<T> UnsafeUnpin for Mutex<T>
where T: UnsafeUnpin + ?Sized,

Общие реализации

impl<T> Any for T
where T: 'static + ?Sized,

fn type_id(&self) -> TypeId

Получает TypeId для self. Подробнее

impl<T> Borrow<T> for T
where T: ?Sized,

fn borrow(&self) -> &T

Неизменяемо заимствует из принадлежащего значения. Подробнее

impl<T> BorrowMut<T> for T
where T: ?Sized,

fn borrow_mut(&mut self) -> &mut T

Изменяемо заимствует из принадлежащего значения. Подробнее

impl<T> From<!> for T

fn from(t: !) -> T

Преобразует входное значение в этот тип.

impl<T> From<T> for T

fn from(t: T) -> T

Возвращает аргумент без изменений.

impl<T> Instrument for T

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Инструментирует этот тип указанным Span, возвращая обёртку Instrumented. Подробнее

fn in_current_span(self) -> Instrumented<Self> ⓘ

Инструментирует этот тип с помощью текущего Span, возвращая обёртку Instrumented. Подробнее

impl<T, U> Into<U> for T
where U: From<T>,

fn into(self) -> U

Вызывает U::from(self).

То есть это преобразование выполняет то, что выбирает реализация From<T> for U.

impl<T, U> TryFrom<U> for T
where U: Into<T>,

type Error = Infallible

Тип, возвращаемый в случае ошибки преобразования.

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Выполняет преобразование.

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

type Error = <U as TryFrom<T>>::Error

Тип, возвращаемый в случае ошибки преобразования.

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Выполняет преобразование.

impl<T> WithSubscriber for T

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Присоединяет предоставленный Subscriber к этому типу и возвращает обёртку WithDispatch. Подробнее

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Присоединяет текущий по умолчанию Subscriber к этому типу и возвращает обёртку WithDispatch. Подробнее

MIT License
Copyright © Tokio Contributors
https://docs.rs/tokio/1.53.1/tokio/sync/struct.Mutex.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API