Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Rust并发控制之Semaphore-两线程交替打印

Rust并发控制之Semaphore-两线程交替打印

作者头像
newbmiao
发布于 2023-11-27 04:33:41
发布于 2023-11-27 04:33:41
49800
代码可运行
举报
文章被收录于专栏:学点Rust学点Rust
运行总次数:0
代码可运行

信号量(Semaphore)是一种对资源并发访问控制的方式。

区别于互斥锁(Mutex)是对共享资源的独占访问,Semaphore 允许指定多个并发访问共享资源。

就是说 Semaphore 像一个持有令牌(permit/token)的桶,每一个并发访问需要持有(acquire)一个令牌来访问共享资源,

当没有令牌时,没法访问共享资源,直到有新的令牌加入(add)或者原来发出的令牌放回(release)桶中。

接下来,我们尝试用通过用它来实现两个线程交替打印 1 和 2,来更直观了解如何使用 semaphore

Rust std 库中没有正式发布的 semaphore(std::sync::Semaphore 在 1.7.0 废弃了)。下边用 tokio 库提供的 semaphore

首先安装 tokio 库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 手动添加tokio到cargo.toml
# 或使用cargo-add: cargo add tokio --features sync,macros,rt-multi-thread
[dependencies]
tokio = { version = "1.34.0", features = ["sync", "macros", "rt-multi-thread"] }

先来一版常规实现,初始化一个只有一个令牌的 semahore,两个线程去并发持有令牌,用后释放(通过 drop)令牌,实现交替打印

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use std::sync::Arc;
use tokio::sync::Semaphore;

#[tokio::main]
async fn main() {
    let semaphore = Arc::new(Semaphore::new(1));
    let cnt = 3;
    let semaphore2 = semaphore.clone();

    let t1 = tokio::spawn(async move {
        for _ in 0..cnt {
            let permit = semaphore.acquire().await.unwrap();
            print!("1 ");
            // 可不写,离开scope时自动释放,放回令牌桶
            drop(permit);
        }
    });

    let t2 = tokio::spawn(async move {
        for _ in 0..cnt {
            // 或用 _ ignore返回值,即时回收令牌
            let _ = semaphore2.acquire().await.unwrap();
            print!("2 ");
        }
    });

    tokio::try_join!(t1, t2).unwrap();
}

乍看没什么问题,但是打印其实不一定是1 2 1 2 1 2的顺序。

原因很简单,我们只是约束了令牌同时只能有一个线程获取到,但是没有约束谁先谁后啊。所以其实没有实现交替打印。

怎么交替打印呢?

要控制顺序,我们可以让每个线程所持有的 semaphore 里的令牌时动态增加和消耗,然后一个令牌桶数量的增加滞后于另一个。

增加可以用 add_permits, 消耗后不放回可以用 forgot, 代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use std::sync::Arc;
use tokio::sync::Semaphore;

#[tokio::main]
async fn main() {
    // 线程1的令牌桶1初始一个令牌,可以先打印1
    let semaphore = Arc::new(Semaphore::new(1));
    let cnt = 3;
    let semaphore2 = semaphore.clone();

    // 线程2的令牌桶2初始没有令牌,直到1打印后增加令牌
    let semaphore_wait = Arc::new(Semaphore::new(0));
    let semaphore_wait2 = semaphore_wait.clone();

    let t1 = tokio::spawn(async move {
        for _ in 0..cnt {
            let permit = semaphore.acquire().await.unwrap();
            print!("1 ");
            // 消耗令牌,不放回令牌桶1
            permit.forget();
            // 令牌桶2增加令牌,可以打印2
            semaphore_wait2.add_permits(1);
        }
    });

    let t2 = tokio::spawn(async move {
        for _ in 0..cnt {
            let permit = semaphore_wait.acquire().await.unwrap();
            print!("2 ");
            // 消耗令牌,不放回令牌桶2
            permit.forget();
            // 令牌桶1增加令牌,可以打印1
            semaphore2.add_permits(1);
        }
    });

    tokio::try_join!(t1, t2).unwrap();
}

通过两个动态的令牌桶(semaphore)线程的执行顺序就能交替执行了。

可以和上篇 condvar 实现的版本 对比下, 感受下 semaphore 的魅力。


推荐阅读

如果有用,点个 在看,让更多人看到

外链不能跳转,戳 阅读原文 查看参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 菜鸟Miao 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
tokio之如何观测Rust异步任务的调度
通过之前的《Rust 异步编程之 Future 初探》我们知道Rust的异步是以task的调度来构建的。task作为抽象在语言层面的调度单元。
newbmiao
2024/02/26
4890
tokio之如何观测Rust异步任务的调度
Rust常用并发示例代码
如果method1()被多次调用,就会创建多个线程,如果希望不管调用多少次,只能有1个线程,在不使用线程池的前提下,有1个简单的办法:
菩提树下的杨过
2022/09/28
1K0
Rust常用并发示例代码
Rust并发控制之Condvar-两线程交替打印
考察的是如何做并发线程的同步控制,实现的方式有很多,今天我们先用上篇提到的 condvar 试试。
newbmiao
2023/11/27
2430
Rust并发控制之Condvar-两线程交替打印
Rust网络编程框架-Tokio进阶
我们在上文《小朋友也能听懂的Rust网络编程框架知识-Tokio基础篇》对于Tokio的基础知识进行了一下初步的介绍,本文就对于Tokio的用法及原理进行进一步的介绍与说明。
beyondma
2021/09/19
2.6K0
Rust并发控制之Channel
Rust 官方sync包中提供了mpsc模式的 (多生产者,单消费者:multi-producer, single-consumer) channel,可以实现基于消息并发控制,而不是依赖控制内存共享(加锁)。这正是 go 语言作者 R. Pike 所推崇的方式:
newbmiao
2023/12/13
3500
Rust并发控制之Channel
rust多线程
在rust中,多线程编程不算困难,但是也需要留心和别的编程语言中不同的地方。rust的标准库中提供的thread库来帮助我们进行多线程编程。在使用的时候需要使用use std::thread来引入thread库即可。
zy010101
2023/05/28
1K0
Rust网络编程框架-深入理解Tokio中的管道
我们在上文《Rust网络编程框架-Tokio进阶》介绍了async/await和锁的基本用法,并完成了一个Server端的DEMO代码。本文继续来探讨这个话题。
beyondma
2021/09/25
1.7K0
透过 Rust 探索系统的本原:并发原语
几周前我写了篇关于并发的文章(透过 rust 探索系统的本原:并发篇),从使用者的角度介绍了常用的处理并发的工具:Mutex / RwLock / Channel,以及 async/await。今天我们讲讲这些并发手段背后的原语。这些原语,大家在操作系统课程时大多学过,但如果不是做一些底层的开发,估计大家都不记得了。今天,我们就来简单聊聊这些基础的并发原语,了解它们的差异,明白它们使用的场景,对撰写高性能的并发应用有很大的帮助。
tyrchen
2021/04/07
1.2K0
透过 Rust 探索系统的本原:并发原语
Rust并发控制之Condvar
上次提到的 Barrier 用到了 Rust 的 condvar 和 mutex,今天来看下 condvar 的用法。
newbmiao
2023/11/27
4220
Rust并发控制之Condvar
Rust语法之多线程(Tokio)
该示例代码创建了一个包含 9 个元素的 Vec,然后使用 Arc 和 Mutex 包装了该 Vec。接着,我们创建了 3 个线程,每个线程负责修改 Vec 的三分之一元素的值。在每个线程的执行体中,我们使用 Mutex 来获取 Vec 的写锁,并修改 Vec 中的元素。最后,我们等待所有线程完成,并输出修改后的 Vec。
码客说
2023/04/17
1.9K0
Rust中的多线程编程实战:从Mutex到Actor模型
在现代编程中,随着多核处理器的普及和大规模数据处理的需求,多线程编程成为了提升应用程序性能和响应速度的重要技术。在Rust中,多线程编程不仅可以通过传统的线程模型来实现,还可以通过更高层次的抽象,如Mutex和Actor模型,来实现高效的并发处理。
数字扫地僧
2024/12/19
1710
深入理解Rust的Atomic及Ordering
之前提到的Mutex、Condvar是Rust中比较偏高层的共享数据型并发控制,更底层的并发控制也有,比如Atomic(原子操作)。
newbmiao
2023/12/26
6300
深入理解Rust的Atomic及Ordering
Rust并发控制之Barrier
Rust 有很多种控制并发的方式,Barrier(屏障)是其中一种用来同步多线程计算的方式。
newbmiao
2023/11/27
3130
Rust并发控制之Barrier
【翻译】200行代码讲透RUST FUTURES (7)
我们将用一个伪reactor和一个简单的执行器创建我们自己的Futures,它允许你在浏览器中编辑和运行代码
MikeLoveRust
2020/08/05
1.3K0
【Rust日报】2019-12-19 Writing BPF code in Rust
Includes new APIs, utilities, and fixes. Some highlights:
MikeLoveRust
2019/12/25
6600
【Rust日报】2019-12-19 Writing BPF code in Rust
【Rust日报】2022-09-09 攻击 Firecracker
来自 Grapl 的博客文章。在 Grapl,我们相信为了构建最好的防御系统,我们需要深入了解攻击者的行为。作为该目标的一部分,我们正在投资于进攻性安全研究。随时关注我们的博客,了解有关高风险漏洞、利用和高级威胁策略的新研究。
MikeLoveRust
2022/11/28
3310
JAVA并发编程系列(7)Semaphore信号量剖析
其实,面对这样的面试要求,现实中的头部大厂,甚至一些普通大厂都是设计了很多编程题考查大家的基础功底。但是都不会很复杂,毕竟时间有限,往往都是经典题目,涉及一个或多个核心关键技术点。
拉丁解牛说技术
2024/09/18
1280
理解Java并发工具类Semaphore
Semaphore是Java里面另外一个基本的并发工具包类,主要的的作用是用来保护共享资源的访问的,也就是仅仅允许一定数量的线程访问共享资源。Semaphore维护了有限数量的许可证,只有得到了许可证的线程才能进行共享资源的访问,如果得不到许可证,说明当前共享资源的访问已经达到最大限制,所以会挂起当前线程,直到前面的线程处理完任务之后,把许可证归还,后面排队的线程才有机会获取,然后处理任务。
我是攻城师
2018/09/30
1.2K0
理解Java并发工具类Semaphore
【翻译】从头实现Rust异步执行器
原文:https://stjepang.github.io/2020/01/31/build-your-own-executor.html 现在我们已经构建了block_on函数,是时候进一步将其转换为一个真正的执行器了。我们希望我们的遗执行器不只是一次运行一个future,而是同时运行多个future!
MikeLoveRust
2020/07/23
9150
掌握Rust:从初学者到开发者的成长之路
Rust语言以其内存安全性、高性能和无运行时(No GC)特性,逐渐成为现代系统编程语言的代表。对于像我这样从其他编程语言转向Rust的开发者来说,这是一段充满挑战和收获的旅程。在本文中,我将分享我从零开始学习Rust的过程,讨论在学习中的挑战、心得体会,并展示如何将Rust应用到实际项目中。
一键难忘
2024/08/29
1530
相关推荐
tokio之如何观测Rust异步任务的调度
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验