跳到主要内容

Semaphore

搜索

结构体 Semaphore 

Source
pub struct Semaphore { /* private fields */ }
展开描述

执行异步 permit 获取的计数信号量(counting semaphore)。

semaphore 维护一组 permit。 permit 用于同步 对共享资源的访问。 semaphore 与 mutex 的不同之处 在于它 可以允许 一个以上的并发调用者 同时访问 共享资源。

acquire 被调用, 且 semaphore 还有剩余 permit 时, 该函数 会立即 返回一个 permit。 但是, 如果没有剩余 permit, acquire 会(异步地)等待, 直到 一个未完成的 permit 被丢弃。 此时, 释放出的 permit 会被分配给调用者。

Semaphore 是公平的, 这意味着 permit 按 请求它们的顺序 分发。 当 acquire_many 涉及时, 这种公平性 也适用, 因此如果 队列前端对 acquire_many 的调用请求 比当前可用的 更多的 permit, 这 可能会 阻止 对 acquire 的调用 完成, 即使 semaphore 有足够的 permit 来完成该 对 acquire 的调用。

要在 poll 函数 中使用 Semaphore, 可以使用 PollSemaphore 实用工具。

§示例

基本用法:

use tokio::sync::{Semaphore, TryAcquireError};











let semaphore = Semaphore::new(3);











let a_permit = semaphore.acquire().await.unwrap();





let two_permits = semaphore.acquire_many(2).await.unwrap();











assert_eq!(semaphore.available_permits(), 0);











let permit_attempt = semaphore.try_acquire();





assert_eq!(permit_attempt.err(), Some(TryAcquireError::NoPermits));

§限制程序中同时打开的文件数

大多数操作系统 对打开的文件句柄 数量有限制。 即使在 没有明确限制的系统中, 资源约束 也会 隐式设置 打开文件数量的上限。 如果你的程序 尝试打开 大量文件 并超过此限制, 将导致错误。

此示例 使用 具有 100 个 permit 的 Semaphore。 在访问文件 之前 从 Semaphore 获取一个 permit, 你可以 确保你的程序 一次打开 不超过 100 个文件。 当尝试打开 第 101 个文件时, 程序 将等待 直到 permit 可用, 然后才 继续打开 另一个文件。

use std::io::Result;





use tokio::fs::File;





use tokio::sync::Semaphore;





use tokio::io::AsyncWriteExt;











static PERMITS: Semaphore = Semaphore::const_new(100);











async fn write_to_file(message: &[u8]) -> Result<()> {





    let _permit = PERMITS.acquire().await.unwrap();





    let mut buffer = File::create("example.txt").await?;





    buffer.write_all(message).await?;





    Ok(()) // Permit goes out of scope here, and is available again for acquisition





}

§限制同时发送的出站请求数

在某些场景下, 可能需要 限制 并行发送的 传出请求数量。 这可能是由于 所使用 API 的限制, 或应用程序运行的 系统 网络资源的限制。

此示例 使用 具有 10 个 permit 的 Arc<Semaphore>。 每个 派生的任务 通过克隆 Arc<Semaphore> 获取对 semaphore 的引用。 任务 发送请求之前, 必须 通过调用 Semaphore::acquire 从 semaphore 获取一个 permit。 这 确保 最多 10 个请求 在任何给定时间 并行发送。 任务 发送请求之后, 它 会 丢弃该 permit, 以允许其他任务 发送请求。

use std::sync::Arc;





use tokio::sync::Semaphore;











// Define maximum number of parallel requests.





let semaphore = Arc::new(Semaphore::new(5));





// Spawn many tasks that will send requests.





let mut jhs = Vec::new();





for task_id in 0..50 {





    let semaphore = semaphore.clone();





    let jh = tokio::spawn(async move {





        // Acquire permit before sending request.





        let _permit = semaphore.acquire().await.unwrap();





        // Send the request.





        let response = send_request(task_id).await;





        // Drop the permit after the request has been sent.





        drop(_permit);





        // Handle response.





        // ...











        response





    });





    jhs.push(jh);





}





// Collect responses from tasks.





let mut responses = Vec::new();





for jh in jhs {





    let response = jh.await.unwrap();





    responses.push(response);





}





// Process responses.





// ...

§限制同时处理的入站请求数

类似于 限制同时打开的文件数, 网络句柄 是一种有限的资源。 允许 无限制数量的请求 被处理 可能会 导致拒绝服务, 以及许多其他问题。

此示例 使用 Arc<Semaphore> 而不是 全局变量。 为了 限制 可同时 处理的请求数, a new task is spawned; and once finished, the permit is dropped inside of the task to allow others to spawn. Permits must be acquired via Semaphore::acquire_owned to be movable across the task boundary. (Since our semaphore is not a global variable — if it was, then acquire would be enough.)

use std::sync::Arc;





use tokio::sync::Semaphore;





use tokio::net::TcpListener;











#[tokio::main]





async fn main() -> std::io::Result<()> {





    let semaphore = Arc::new(Semaphore::new(3));





    let listener = TcpListener::bind("127.0.0.1:8080").await?;











    loop {





        // Acquire permit before accepting the next socket.





        //





        // We use `acquire_owned` so that we can move `permit` into





        // other tasks.





        let permit = semaphore.clone().acquire_owned().await.unwrap();





        let (mut socket, _) = listener.accept().await?;











        tokio::spawn(async move {





            // Do work using the socket.





            handle_connection(&mut socket).await;





            // Drop socket while the permit is still live.





            drop(socket);





            // Drop the permit, so more tasks can be created.





            drop(permit);





        });





    }





}

§防止测试并行运行

默认情况下, Rust 并行运行 同一文件中的测试。 但是, 在某些情况下, 并行运行两个测试 可能会导致问题。 例如, 当测试 使用 同一个数据库时, 就可能发生这种情况。

考虑以下场景:

  1. test_insert: Inserts a key-value pair into the database, then retrieves the value using the same key to verify the insertion.
  2. test_update: Inserts a key, then updates the key to a new value and verifies that the value has been accurately updated.
  3. test_others: A third test that doesn’t modify the database state. It can run in parallel with the other tests.

在此示例中, test_inserttest_update 需要按顺序运行 才能正常工作, 但哪个测试 先运行 并不重要。 我们可以利用 具有单个 permit 的 semaphore 来解决此问题。

use tokio::sync::Semaphore;











// Initialize a static semaphore with only one permit, which is used to





// prevent test_insert and test_update from running in parallel.





static PERMIT: Semaphore = Semaphore::const_new(1);











// Initialize the database that will be used by the subsequent tests.





static DB: Database = Database::setup();











#[tokio::test]





async fn test_insert() {





    // Acquire permit before proceeding. Since the semaphore has only one permit,





    // the test will wait if the permit is already acquired by other tests.





    let permit = PERMIT.acquire().await.unwrap();











    // Do the actual test stuff with database











    // Insert a key-value pair to database





    let (key, value) = ("name", 0);





    DB.insert(key, value).await;











    // Verify that the value has been inserted correctly.





    assert_eq!(DB.get(key).await, value);











    // Undo the insertion, so the database is empty at the end of the test.





    DB.delete(key).await;











    // Drop permit. This allows the other test to start running.





    drop(permit);





}











#[tokio::test]





async fn test_update() {





    // Acquire permit before proceeding. Since the semaphore has only one permit,





    // the test will wait if the permit is already acquired by other tests.





    let permit = PERMIT.acquire().await.unwrap();











    // Do the same insert.





    let (key, value) = ("name", 0);





    DB.insert(key, value).await;











    // Update the existing value with a new one.





    let new_value = 1;





    DB.update(key, new_value).await;











    // Verify that the value has been updated correctly.





    assert_eq!(DB.get(key).await, new_value);











    // Undo any modificattion.





    DB.delete(key).await;











    // Drop permit. This allows the other test to start running.





    drop(permit);





}











#[tokio::test]





async fn test_others() {





    // This test can run in parallel with test_insert and test_update,





    // so it does not use PERMIT.





}

§使用令牌桶进行限流

此示例 展示了 add_permitsSemaphorePermit::forget 方法。

许多应用程序 和系统 对 某些操作 应发生的 速率 有限制。 超过此速率 可能会导致 次优的性能 甚至错误。

此示例 使用 token bucket 实现速率限制。 token bucket 是一种 速率限制形式, 不会立即生效, 以便 允许 同时到达的传入请求 出现 短暂的突发。

对于 token bucket, 每个传入请求 消耗一个 token, 且 token 以 定义速率限制的 特定速率 被补充。 当一批请求 到达时, token 会立即分发, 直到 bucket 为空。 一旦 bucket 为空, 请求必须 等待 新 token 被添加。

与 限制同时可处理 请求数的示例不同, 我们 不会在 完成处理请求后 将 token 加回。 相反, token 仅由 定时器任务 添加。

请注意, 当持续时间较小时, 此实现 是次优的, 因为它 会消耗大量 cpu 来持续循环和睡眠。

use std::sync::Arc;





use tokio::sync::Semaphore;





use tokio::time::{interval, Duration};











struct TokenBucket {





    sem: Arc<Semaphore>,





    jh: tokio::task::JoinHandle<()>,





}











impl TokenBucket {





    fn new(duration: Duration, capacity: usize) -> Self {





        let sem = Arc::new(Semaphore::new(capacity));











        // refills the tokens at the end of each interval





        let jh = tokio::spawn({





            let sem = sem.clone();





            let mut interval = interval(duration);





            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);











            async move {





                loop {





                    interval.tick().await;











                    if sem.available_permits() < capacity {





                        sem.add_permits(1);





                    }





                }





            }





        });











        Self { jh, sem }





    }











    async fn acquire(&self) {





        // This can return an error if the semaphore is closed, but we





        // never close it, so this error can never happen.





        let permit = self.sem.acquire().await.unwrap();





        // To avoid releasing the permit back to the semaphore, we use





        // the `SemaphorePermit::forget` method.





        permit.forget();





    }





}











impl Drop for TokenBucket {





    fn drop(&mut self) {





        // Kill the background task so it stops taking up resources when we





        // don't need it anymore.





        self.jh.abort();





    }





}











let capacity = 5;





let update_interval = Duration::from_secs_f32(1.0 / capacity as f32);





let bucket = TokenBucket::new(update_interval, capacity);











for _ in 0..5 {





    bucket.acquire().await;











    // do the operation





}

实现§

Source§

impl Semaphore

Source

pub const MAX_PERMITS: usize = super::batch_semaphore::Semaphore::MAX_PERMITS

信号量可持有的最大许可证数量。其值为 usize::MAX >> 3。

超出此限制通常会导致 panic。

Source

pub fn new(permits: usize) -> Self

创建具有初始许可证数量的信号量。

如果 permits 超过 Semaphore::MAX_PERMITS 则会 panic。

Source

pub const fn const_new(permits: usize) -> Self

创建具有初始许可证数量的信号量。

使用 tracing 不稳定特性时,通过 const_new 创建的 Semaphore 不会被插桩。因此,它不会出现在 tokio-console 中。如有需要,请改用 Semaphore::new 来创建可插桩的对象。

§示例
use tokio::sync::Semaphore;











static SEM: Semaphore = Semaphore::const_new(10);
Source

pub fn available_permits(&self) -> usize

返回当前可用的许可证数量。

Source

pub fn add_permits(&self, n: usize)

向信号量添加 n 个新许可证。

最大许可证数量为 Semaphore::MAX_PERMITS,如果超出该限制,此函数将 panic。

Source

pub fn forget_permits(&self, n: usize) -> usize

最多减少信号量 n 个许可证。

如果没有足够的许可证且无法减少 n,则返回实际减少的许可证数量。

Source

pub async fn acquire(&self) -> Result<SemaphorePermit<'_>, AcquireError>

从信号量获取一个许可证。

如果信号量已被关闭,则返回 AcquireError。否则,返回代表已获取许可证的 SemaphorePermit

§Cancel safety

此方法使用队列按请求顺序公平分配许可证。取消对 acquire 的调用会丢失在队列中的位置。

§示例
use tokio::sync::Semaphore;











let semaphore = Semaphore::new(2);











let permit_1 = semaphore.acquire().await.unwrap();





assert_eq!(semaphore.available_permits(), 1);











let permit_2 = semaphore.acquire().await.unwrap();





assert_eq!(semaphore.available_permits(), 0);











drop(permit_1);





assert_eq!(semaphore.available_permits(), 1);
Source

pub async fn acquire_many( &self, n: u32, ) -> Result<SemaphorePermit<'_>, AcquireError>

从信号量获取 n 个许可证。

如果信号量已被关闭,则返回 AcquireError。否则,返回代表已获取许可证的 SemaphorePermit

§Cancel safety

此方法使用队列按请求顺序公平分配许可证。取消对 acquire_many 的调用会丢失在队列中的位置。

§示例
use tokio::sync::Semaphore;











let semaphore = Semaphore::new(5);











let permit = semaphore.acquire_many(3).await.unwrap();





assert_eq!(semaphore.available_permits(), 2);
Source

pub fn try_acquire(&self) -> Result<SemaphorePermit<'_>, TryAcquireError>

尝试从信号量获取一个许可证。

如果信号量已被关闭,则返回 TryAcquireError::Closed;如果没有剩余许可证则返回 TryAcquireError::NoPermits。否则,返回代表已获取许可证的 SemaphorePermit

§示例
use tokio::sync::{Semaphore, TryAcquireError};











let semaphore = Semaphore::new(2);











let permit_1 = semaphore.try_acquire().unwrap();





assert_eq!(semaphore.available_permits(), 1);











let permit_2 = semaphore.try_acquire().unwrap();





assert_eq!(semaphore.available_permits(), 0);











let permit_3 = semaphore.try_acquire();





assert_eq!(permit_3.err(), Some(TryAcquireError::NoPermits));
Source

pub fn try_acquire_many( &self, n: u32, ) -> Result<SemaphorePermit<'_>, TryAcquireError>

尝试从信号量获取 n 个许可证。

如果信号量已被关闭,则返回 TryAcquireError::Closed;如果没有足够的许可证则返回 TryAcquireError::NoPermits。否则,返回代表已获取许可证的 SemaphorePermit

§示例
use tokio::sync::{Semaphore, TryAcquireError};











let semaphore = Semaphore::new(4);











let permit_1 = semaphore.try_acquire_many(3).unwrap();





assert_eq!(semaphore.available_permits(), 1);











let permit_2 = semaphore.try_acquire_many(2);





assert_eq!(permit_2.err(), Some(TryAcquireError::NoPermits));
Source

pub async fn acquire_owned( self: Arc<Self>, ) -> Result<OwnedSemaphorePermit, AcquireError>

从信号量获取一个许可证。

调用此方法时信号量必须包装在 Arc 中。如果信号量已被关闭,则返回 AcquireError。否则,返回代表已获取许可证的 OwnedSemaphorePermit

§Cancel safety

此方法使用队列按请求顺序公平分配许可证。取消对 acquire_owned 的调用会丢失在队列中的位置。

§示例
use std::sync::Arc;





use tokio::sync::Semaphore;











let semaphore = Arc::new(Semaphore::new(3));





let mut join_handles = Vec::new();











for _ in 0..5 {





    let permit = semaphore.clone().acquire_owned().await.unwrap();





    join_handles.push(tokio::spawn(async move {





        // perform task...





        // explicitly own `permit` in the task





        drop(permit);





    }));





}











for handle in join_handles {





    handle.await.unwrap();





}
Source

pub async fn acquire_many_owned( self: Arc<Self>, n: u32, ) -> Result<OwnedSemaphorePermit, AcquireError>

从信号量获取 n 个许可证。

调用此方法时信号量必须包装在 Arc 中。如果信号量已被关闭,则返回 AcquireError。否则,返回代表已获取许可证的 OwnedSemaphorePermit

§Cancel safety

此方法使用队列按请求顺序公平分配许可证。取消对 acquire_many_owned 的调用会丢失在队列中的位置。

§示例
use std::sync::Arc;





use tokio::sync::Semaphore;











let semaphore = Arc::new(Semaphore::new(10));





let mut join_handles = Vec::new();











for _ in 0..5 {





    let permit = semaphore.clone().acquire_many_owned(2).await.unwrap();





    join_handles.push(tokio::spawn(async move {





        // perform task...





        // explicitly own `permit` in the task





        drop(permit);





    }));





}











for handle in join_handles {





    handle.await.unwrap();





}
Source

pub fn try_acquire_owned( self: Arc<Self>, ) -> Result<OwnedSemaphorePermit, TryAcquireError>

尝试从信号量获取一个许可证。

调用此方法时信号量必须包装在 Arc 中。如果信号量已被关闭,则返回 TryAcquireError::Closed;如果没有剩余许可证则返回 TryAcquireError::NoPermits。否则,返回代表已获取许可证的 OwnedSemaphorePermit

§示例
use std::sync::Arc;





use tokio::sync::{Semaphore, TryAcquireError};











let semaphore = Arc::new(Semaphore::new(2));











let permit_1 = Arc::clone(&semaphore).try_acquire_owned().unwrap();





assert_eq!(semaphore.available_permits(), 1);











let permit_2 = Arc::clone(&semaphore).try_acquire_owned().unwrap();





assert_eq!(semaphore.available_permits(), 0);











let permit_3 = semaphore.try_acquire_owned();





assert_eq!(permit_3.err(), Some(TryAcquireError::NoPermits));
Source

pub fn try_acquire_many_owned( self: Arc<Self>, n: u32, ) -> Result<OwnedSemaphorePermit, TryAcquireError>

尝试从信号量获取 n 个许可证。

调用此方法时信号量必须包装在 Arc 中。如果信号量已被关闭,则返回 TryAcquireError::Closed;如果没有剩余许可证则返回 TryAcquireError::NoPermits。否则,返回代表已获取许可证的 OwnedSemaphorePermit

§示例
use std::sync::Arc;





use tokio::sync::{Semaphore, TryAcquireError};











let semaphore = Arc::new(Semaphore::new(4));











let permit_1 = Arc::clone(&semaphore).try_acquire_many_owned(3).unwrap();





assert_eq!(semaphore.available_permits(), 1);











let permit_2 = semaphore.try_acquire_many_owned(2);





assert_eq!(permit_2.err(), Some(TryAcquireError::NoPermits));
Source

pub fn close(&self)

关闭信号量。

这会阻止信号量发放新许可证,并通知所有待处理的等待者。

§示例
use tokio::sync::Semaphore;





use std::sync::Arc;





use tokio::sync::TryAcquireError;











let semaphore = Arc::new(Semaphore::new(1));





let semaphore2 = semaphore.clone();











tokio::spawn(async move {





    let permit = semaphore.acquire_many(2).await;





    assert!(permit.is_err());





    println!("waiter received error");





});











println!("closing semaphore");





semaphore2.close();











// Cannot obtain more permits





assert_eq!(semaphore2.try_acquire().err(), Some(TryAcquireError::Closed))
Source

pub fn is_closed(&self) -> bool

如果信号量已关闭则返回 true。

Trait 实现§

Source§

impl Debug for Semaphore

Source§

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

使用给定的格式化器格式化此值。 更多信息

自动 Trait 实现§

Blanket 实现§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. 更多信息
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. 更多信息
Source§

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

Source§

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

Mutably borrows from an owned value. 更多信息
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

原样返回传入的参数。

Source§

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

Source§

fn into(self) -> U

调用 U::from(self)

也就是说,此转换的具体行为取决于 From<T> for U 的实现方式。

Source§

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

Source§

type Error = Infallible

转换出错时返回的类型。
Source§

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

执行转换。
Source§

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

Source§

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

转换出错时返回的类型。
Source§

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

执行转换。