专栏首页Rust学习专栏GO、Rust这些新一代高并发编程语言为何都极其讨厌共享内存?
原创

GO、Rust这些新一代高并发编程语言为何都极其讨厌共享内存?

今天我想再来讨论一下高并发的问题,我们看到最近以Rust、Go为代表的云原生、Serverless时代的语言,在设计高并发编程模式时往往都会首推管道机制,传统意义上并发控制的利器如互斥体或者信号量都不是太推荐。

这里我们先来看一下并发和并行的概念,我们知道并发是一个处理器同时处理多个任务,这里同时是逻辑上的,而并行同一时刻多个物理器同时执行不同指令,这里的同时物理上的。并发是要尽量在目前正在执行的任务遇到阻塞或者等待操作时,释放CPU,让其它任务得以调度,而并行则是同时执行不同任务而不相互影响。

而传统的信号量、互斥体的设计都是为了让单核CPU发挥出最大的性能,让程序在阻塞时释放CPU,通过控制共享变量的访问来达到避免冲突的目的,而想控制好这些共享变量的行为,其关键因此在于设计好时序,从本质上讲控制时序就是给系统加上红绿灯并配备路障,而这里你一定要记住,高性能系统需要的是立交桥、地下隧道这些基础设施,而不是交通信号等控制手段,好的并发系统一定要用流的概念来建模,而不是到处增加关卡路障。现在的处理都是多核架构,因此编程也要向并行倾斜,不过笔者在网上看到很多所谓标榜高并发教程中所举的例子,都把信号灯设计的时序很完美,却偏偏把立交桥全给扔了…..

信号灯应该为导流服务,而不应为限流而生

下面我们来看三段分别对应信号灯控制的操作,互斥体统治的“并发”,以及单纯的串行的代码,代码的目标其实就是要完成从0一直加到3000000的操作。

信号灯控制

其实这种信号量的代码已经基本退化回了顺序执行的方案了。正如我们在前文《GO看你犯错,但是Rust帮你排坑所说》,Rust的变量生命周期检查机制,并不能支持在不同线程之间共享内存,即便可以曲线救国,也绝非官方推荐,因此这里先用Go带各位读者说明。

package main



import (

        "fmt"



        "sync"

        "time"

)



var count int

var wg1 sync.WaitGroup

var wg2 sync.WaitGroup

var wg3 sync.WaitGroup

var wg4 sync.WaitGroup



func goroutine1() {

        wg1.Wait()

        len := 1000000

        for i := 0; i < len; i++ {

                 count++

        }

        wg2.Done()

}



func goroutine2() {

        wg2.Wait()

        len := 1000000

        for i := 0; i < len; i++ {

                 count++

        }

        wg3.Done()

}



func goroutine3() {

        wg3.Wait()

        len := 1000000

        for i := 0; i < len; i++ {

                 count++

        }

        wg4.Done()

}



func main() {

        now := time.Now().UnixNano()

        wg1.Add(1)

        wg2.Add(1)

        wg3.Add(1)

        wg4.Add(1)

        go goroutine1()



        go goroutine2()

        go goroutine3()

        wg1.Done()

        wg4.Wait()



        fmt.Println(time.Now().UnixNano() - now)

        fmt.Println(count)

}

在这里三个子协程goroutine,在4个信号量的控制下以多米诺骨牌的方式依次对于共享变量count进行操作,这段代码的运行结果如下:

4984300

3000000

成功: 进程退出代码 0.

互斥体控制

与信号量完全退化成顺序执行不同,互斥体本质上同一时刻只能有一个goroutine执行到临界代码,但每个goroutine的执行顺序却无所谓,具体如下:

package main



import (

        "fmt"



        "sync"

        "time"

)



var count int

var wg1 sync.WaitGroup

var mutex sync.Mutex



func goroutine1() {

        mutex.Lock()

        len := 1000000

        for i := 0; i < len; i++ {

               count++

        }

        mutex.Unlock()

        wg1.Done()

}



func main() {

        now := time.Now().UnixNano()

        wg1.Add(3)

        go goroutine1()

        go goroutine1()

        go goroutine1()

        wg1.Wait()



        fmt.Println(time.Now().UnixNano() - now)

        fmt.Println(count)

}

从运行实序上来看,互斥体的方案应该和信号量差不多,不过结果却令人意,在互斥体的控制下,这个程序性能反而还下降了30%,具体结果如下:

5986800

3000000

成功: 进程退出代码 0.

串行方式:

最后用最返璞归真的做法,串行操作代码如下:

package main

import (
        "fmt"

        //"sync"
        "time"
)

var count int

func goroutine1() {

        len := 1000000
        for i := 0; i < len; i++ {
               count++
        }
}

func main() {
        now := time.Now().UnixNano()

        goroutine1()
        goroutine1()
        goroutine1()

        fmt.Println(time.Now().UnixNano() - now)
        fmt.Println(count)
}

可以看到从效率上来讲,直接串行的方式和信号量的方式是差不多的,结果如下:

4986700

3000000

成功: 进程退出代码 0.

也就是说费了半天劲,最终结果可能还不如直接串行执行呢。

Rust Future初探

Rust中的future机制有点类似于 JavaScript 中的promise机制。Future机制让程序员可以使用同步代码的方式设计高并发的异步场景。目前虽然Go当中也有一些defer的机制,但远没有Rust中的future这么强大。Future机制将返回值value与其计算方式executor分离,从而让程序员可以不再关注于具体时序机制的设计,只需要指定Future执行所需要的条件,以及执行器即可。

我们来看以下代码。

注:cargo.toml

[dependencies]

futures = { version = "0.3.5", features = ["thread-pool"] }

代码如下:

use futures::channel::mpsc;

use futures::executor;

use futures::executor::ThreadPool;

use futures::StreamExt;

fn main() {

    let poolExecutor = ThreadPool::new().expect("Failed");

    let (tx, rx) = mpsc::unbounded::<String>();

    let future_values = async {

        let fut_tx_result = async move {

       let hello = String::from("hello world");

         for c in hello .chars() {

        tx.unbounded_send(c.to_string()).expect("Failed to send");

    }

           

        };

        poolExecutor.spawn_ok(fut_tx_result);

        let future_values = rx

            .map(|v| v)

            .collect();

        future_values.await

    };

    let values: Vec<String> = executor::block_on(future_values);

    println!("Values={:?}", values);



}

上述代码中我们通过async指定了future_values ,并将这个Future指定给poolExecutor这个线程池执行,最后通过await方法,就可以让future全部执行完毕,而不必再用信号量控制具体的时序。

这样一来,只要深度掌握future机制,就可以不必再关心互斥体、信号量,具体的高度方式完全放心交给计算机去做优化,不但可以节约程序员的时间,也能充分发挥编译器的威力,尾号是避免出现那种扔掉立交桥,只要信号灯低级的错误方式。

Java虽然也有一定的Future实现,并且有Rust不具备的反射能力,但是冷起动一直是困扰Java的痛。因此在目前云原生的时代,Go和Rust尤其是Rust语言以其近首于C语言的启动速度,和运行效率真是很有可能在未来称王。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 大数据告诉你,最不受欢迎的编程语言居然是……

    导读:哪些编程语言被开发者喜欢?哪些让人讨厌?笔者通过在Stack Overflow分析用户创建的开发者履历,得出了最不受开发者欢迎的编程语言,还有最受开发者欢...

    华章科技
  • 为什么要在WebAssembly中使用Rust?【Programming】

    WebAssembly(Wasm)是一项技术,可以重塑我们为浏览器构建应用程序的方式。 它不仅使我们能够构建全新的Web应用程序类,而且还将使我们使用JavaS...

    Potato
  • 调查了6万多名开发者后,我们发现了这些...

    近十年来,Stack Overflow 的年度开发者调查一直是针对全球编码人员最大的调查。今年,他们调查了 60,000 多名软件开发人员,包括他们的工作时长、...

    深度学习与Python
  • 开发者调研 | Rust最受欢迎、Python最受关注、机器学习专家收入最高

    选自Stack Overflow 机器之心编译 参与:李泽南,晏奇,微胖 近日,全球最大程序员在线社区 Stack Overflow 发布了最新一期全球开发者调...

    机器之心
  • D语言架构师Andrei Alexandrescu谈D、Go、Rust取代C/C++

    【编者按】本文是D语言联合创始人、架构师Andrei Alexandrescu在问答Quora上关于“在取代C语言的道路上,D、Go和Rust谁的前途最光明?为...

    CSDN技术头条
  • 坚持还是放弃,Go语言的“美好与丑陋”解读

    链接:https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/

    23号杂货铺
  • ESR:程序语言设计的要诣和真谛

    为什么一些语言会成功,另一些语言会失败。 -- Eric Raymond 当你真正掌握了整体化的工程设计思维时,你就会发现高屋建瓴的工程设计已经远远超越了技术优...

    企鹅号小编
  • Rust 编译模型之殇

    本文转载自 PingCap 公众号。原文地址:https://mp.weixin.qq.com/s/j8qsUpphshwGh9TaaWpNTA

    MikeLoveRust
  • Rust 编译模型之殇

    我的意思并非是此乃 Rust 语言的设计目标。正如语言设计者们相互争论时经常说的那样,编程语言的设计总是充满了各种权衡。其中最主要的权衡就是:运行时性能和编译时...

    PingCAP
  • 一顿操作猛如虎,一看结果还是 0,Rust 能避免 Go 的 Bug?

    早些时候我看到这样一条新闻,在谈到Linux内核与Rust的关系时,谷歌曾表示Rust现在已经准备好加入C语言,成为实现内核的实用语言。它可以帮助减少特权代码中...

    MikeLoveRust
  • Rust到底值不值得学--Rust对比、特色和理念

    其实我一直弄不明白一点,那就是计算机技术的发展,是让这个世界变得简单了,还是变得更复杂了。 当然这只是一个玩笑,可别把这个问题当真。

    俺踏月色而来
  • 微软计划使用 Rust 取代 C 和 C++

    近日,微软安全响应中心(MSRC)团队在官网更新文章,就近日提出的最新计划,即未来将使用 Rust 作为 C、C++ 以及其他编程语言的替代方案以改善应用程序的...

    新智元
  • 如何正确看待谷歌宣布Fuchsia操作系统没有选Go作为终端开发语言

    前段时间偶尔的机会下在 Go 圈被这条新闻刷屏了(当然还有 Go1.14 发布):

    IT大咖说
  • 为什么我十分喜欢C,却很不喜欢C++?

    虽然 C 语言并不是我所学的第一门语言,也不是我的最后一门语言,但是我仍然非常喜欢 C,当需要写程序时,我的第一选择还是 C。同时,我也会关注现代编程语言及其发...

    C语言与CPP编程
  • 为什么C语言仍然占据统治地位?

    导读:C语言五十年来一直是软件开发的一种主力语言。本文介绍它在如今的2019年与C++,Java,C#,Go,Rust和Python抗衡的方式。

    华章科技
  • WebAssembly 与 Rust 综述

    首先要说一句,WebAssembly 是一项极速发展的技术,互联网上流传的很多文章(17,18年所写)已经过时了。所以,请尽量查阅最新时间的相关描述文档。

    MikeLoveRust
  • 为什么 C 语言仍然占据统治地位?

    没有什么技术可以应用长达50年之久,除非它真的比大多数其他东西都要好用——对于一种计算机行业的技术来说尤其如此。自1972年诞生以来,C语言一直保持生龙活虎的状...

    Python猫
  • 【Rust 日报】2021-03-11 教你如何用Rg3d制作一个射击游戏!

    wgpu 创建渲染管道的方式已经进行了改进。大多数属于自己的字段都被分组为结构,例如 MultisampleState 。这意味着简单的管道更容易创建,因为我们...

    MikeLoveRust
  • Java、Go和Rust间的比较

    本文对Java、Go和Rust之间的对比并非完全是基准测试,更多的是比较输出的可执行文件大小、内存使用情况、CPU使用率、运行时要求,当然会有一个小基准测试用于...

    MikeLoveRust

扫码关注云+社区

领取腾讯云代金券