日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

连续 3 年最受欢迎:Rust,香!

發(fā)布時(shí)間:2024/9/3 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 连续 3 年最受欢迎:Rust,香! 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
簡(jiǎn)介:我們?cè)谶x擇一種開(kāi)發(fā)語(yǔ)言時(shí)會(huì)綜合考量各方面的特性,根據(jù)實(shí)際的需求適當(dāng)取舍。魚(yú)和熊掌往往不可兼得,要想開(kāi)發(fā)效率高,必然要犧牲性能和資源消耗,反之亦然。但是Rust卻出其不意,令人眼前一亮!本文將從性能、內(nèi)存安全、開(kāi)發(fā)效率、跨平臺(tái)性及生態(tài)等五個(gè)方面,對(duì)Rust這一編程語(yǔ)言進(jìn)行一些科普性質(zhì)的分享。

一 性能對(duì)比

不同的語(yǔ)言使用不同的內(nèi)存管理方式,一些語(yǔ)言使用垃圾回收機(jī)制在運(yùn)行時(shí)尋找不再被使用的內(nèi)存并釋放,典型的如Java、Golang。在另一些語(yǔ)言中,程序員必須親自分配和釋放內(nèi)存,比如C/C++。Rust 則選擇了第三種方式:內(nèi)存被一個(gè)所有權(quán)系統(tǒng)管理,它擁有一系列的規(guī)則使編譯器在編譯時(shí)進(jìn)行檢查,任何所有權(quán)系統(tǒng)的功能都不會(huì)導(dǎo)致運(yùn)行時(shí)開(kāi)銷(xiāo)。Rust 速度驚人且內(nèi)存利用率極高,標(biāo)準(zhǔn)Rust性能與標(biāo)準(zhǔn)C++性能不相上下,某些場(chǎng)景下效率甚至高于C++。由于沒(méi)有運(yùn)行時(shí)和垃圾回收,它能夠勝任對(duì)性能要求特別高的服務(wù)。網(wǎng)上已經(jīng)有了很多關(guān)于Rust性能分析對(duì)比的文章,不過(guò)為了獲得一手的資料,還是自己動(dòng)手來(lái)的更加真實(shí)。我選擇了Python,C++,Golang這3種語(yǔ)言來(lái)和Rust做性能對(duì)比。

性能測(cè)試場(chǎng)景設(shè)計(jì)

同樣的算法用4種語(yǔ)言分別實(shí)現(xiàn),對(duì)比在規(guī)定的時(shí)間內(nèi)完成任務(wù)的次數(shù)。本次測(cè)試選擇的算法是找出10000000以內(nèi)的所有素?cái)?shù),比較在一分鐘內(nèi)完成找出所有素?cái)?shù)任務(wù)的次數(shù)。

源代碼鏈接見(jiàn)[1]。

靜態(tài)編譯(或者打包)后生成的二進(jìn)制大小對(duì)比

結(jié)論:(二進(jìn)制大小)python > golang > rust > c++

運(yùn)行速度對(duì)比

本場(chǎng)景下比較1分鐘內(nèi)找出1000000以內(nèi)所有素?cái)?shù)的次數(shù)。

結(jié)論:(運(yùn)行效率)rust > c++ > golang > python

重點(diǎn)來(lái)了,在3臺(tái)不同的機(jī)器上測(cè)試四次的結(jié)果顯示:Rust效率居然高于C++!!!

內(nèi)存消耗對(duì)比(粗略計(jì)算)

結(jié)論:(內(nèi)存消耗) python > golang > rust > c++

CPU消耗對(duì)比(粗略計(jì)算)

結(jié)論:(CPU消耗)golang > python > rust = c++

以上便是我的測(cè)試結(jié)果,測(cè)試代碼、二進(jìn)制和測(cè)試結(jié)果參考附件bin.zip,第一次測(cè)試后看到結(jié)果,有些吃驚,rust的性能居然超過(guò)了c++,不可思議,于是又在網(wǎng)上搜索,找到了別人已經(jīng)完成的rust性能測(cè)試,網(wǎng)上的結(jié)果更讓人吃驚,先看第一篇,原始鏈接見(jiàn)[2]。

我直接截圖看結(jié)論:

以上為Rust vs Golang。

以上為Rust vs C++。

結(jié)論:以上截圖顯示,Rust在性能和資源消耗上不僅大幅度優(yōu)于Golang,并且和C++性能不相上下,某些場(chǎng)景下效率甚至優(yōu)于C++。

以上兩種測(cè)試場(chǎng)景只是測(cè)試一些簡(jiǎn)單的算法,接下來(lái)我們看一下在實(shí)際使用中的性能資源占用對(duì)比,依然是在網(wǎng)上找到了一篇測(cè)試報(bào)告[3],該測(cè)試報(bào)告用Python、PyPy、Go、Rust四種語(yǔ)言實(shí)現(xiàn)了一個(gè)web后端,接下來(lái)使用wrk分別對(duì)四個(gè)http服務(wù)器進(jìn)行壓測(cè),該測(cè)試場(chǎng)景比較貼近實(shí)際,直接截圖看結(jié)論:

結(jié)論(性能):在實(shí)際作為后端服務(wù)使用的場(chǎng)景下,Rust比Golang依然有明顯性能優(yōu)勢(shì)。

結(jié)論(資源占用):在內(nèi)存占用上Rust的優(yōu)勢(shì)更加明顯,只用了Golang的1/3。

綜合以上3個(gè)測(cè)試,Rust在運(yùn)行效率和資源消耗上的優(yōu)勢(shì)十分明顯,和C++同一個(gè)級(jí)別,遠(yuǎn)遠(yuǎn)優(yōu)于Golang !

二 內(nèi)存安全性

Rust 最重要的特點(diǎn)就是可以提供內(nèi)存安全保證,而且沒(méi)有額外的性能損失。在傳統(tǒng)的系統(tǒng)級(jí)編程語(yǔ)言( C/C++) 的開(kāi)發(fā)過(guò)程中,經(jīng)常出現(xiàn)因各種內(nèi)存錯(cuò)誤引起的崩潰或bug ,比如空指針、野指針、內(nèi)存泄漏、內(nèi)存越界、段錯(cuò)誤、數(shù)據(jù)競(jìng)爭(zhēng)、迭代器失效等,血淚斑斑,數(shù)不勝數(shù);內(nèi)存問(wèn)題是影響程序穩(wěn)定性和安全性的重大隱患,并且是影響開(kāi)發(fā)效率的重大因素;根據(jù)google和微軟 兩大巨頭的說(shuō)法,旗下重要產(chǎn)品程序安全問(wèn)題70%由內(nèi)存問(wèn)題引發(fā)[4], 并且兩個(gè)巨頭都用利用Rust語(yǔ)言來(lái)解決內(nèi)存安全問(wèn)題的想法。Rust語(yǔ)言從設(shè)計(jì)之初就把解決內(nèi)存安全作為一個(gè)重要目標(biāo),通過(guò)一系列手段保證內(nèi)存安全,讓不安全的潛在風(fēng)險(xiǎn)在編譯階段就暴露出來(lái)。接下來(lái)根據(jù)自己粗淺的理解,簡(jiǎn)單介紹Rust解決內(nèi)存安全的手段有哪些。

1 所有權(quán)規(guī)則

1)Rust 中每一個(gè)值或者對(duì)象都有一個(gè)稱(chēng)之為其 所有者(owner)的變量。

例如:

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

obj是String對(duì)象的所有權(quán)變量。

2)值或?qū)ο笥星抑荒苡幸粋€(gè)所有者。

3)當(dāng)所有者離開(kāi)作用域,所有者所代表的對(duì)象或者值會(huì)被立即銷(xiāo)毀。

4)賦值語(yǔ)句、函數(shù)調(diào)用、函數(shù)返回等會(huì)導(dǎo)致所有權(quán)轉(zhuǎn)移,原有變量會(huì)失效。

例如:

fn main() {let s = String::from("hello");let s1 = s; //所有權(quán)發(fā)生了轉(zhuǎn)移,由s轉(zhuǎn)移給s1print!("{}",s); //s無(wú)效,不能訪問(wèn),此句編譯會(huì)報(bào)錯(cuò) } fn test(s1:String){print!("{}",s1); }fn main() {let s = String::from("hello");test(s); //傳參,所有權(quán)發(fā)生了轉(zhuǎn)移print!("{}",s); //此處s無(wú)效,編譯報(bào)錯(cuò) }

Rust的所有權(quán)規(guī)則保證了同一時(shí)刻永遠(yuǎn)只有一個(gè)變量持有一個(gè)對(duì)象的所有權(quán),避免數(shù)據(jù)競(jìng)爭(zhēng)。

2 借用規(guī)則

可能大家都發(fā)現(xiàn)了問(wèn)題,什么鬼,為什么我傳了個(gè)參數(shù)s給test函數(shù),這參數(shù)s后面還不能用了呢?如果我接下來(lái)要使用變量s怎么辦?這時(shí)候就要用到Rust的借用特性。在Rust中,你擁有一個(gè)變量的所有權(quán),如果想讓其它變量或者函數(shù)訪問(wèn),你可以把它“借”給其它變量或者你所調(diào)用的函數(shù),供它們?cè)L問(wèn)。Rust會(huì)在編譯時(shí)檢查所有借出的值,確保它們的壽命不會(huì)超過(guò)值本身的壽命。

例如,以下的寫(xiě)法就沒(méi)有問(wèn)題:

fn test(s1:&String){print!("{}",s1); }fn main() {let s = String::from("hello");test(&s); //傳參,注意只是傳遞了引用,所有權(quán)還歸屬于sprint!("{}",s); //此處s依然有效,可以訪問(wèn) } fn main() {let s = String::from("hello");let s1 = &s; //s1借用s,所有權(quán)還歸屬于sprint!("{}",s); //此處s依然有效,可以訪問(wèn)print!("{}",s1); //此處s1和s指向同一個(gè)對(duì)象 }

如果我們嘗試修改借用的變量呢?

fn main() {let s = String::from("hello");change(&s);}fn change(some_string: &String) {some_string.push_str(", world"); }

借用默認(rèn)是不可變的,上面的代碼編譯時(shí)會(huì)報(bào)錯(cuò):

error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable--> error.rs:8:5| 7 | fn change(some_string: &String) {| ------- use `&mut String` here to make mutable 8 | some_string.push_str(", world");| ^^^^^^^^^^^ cannot borrow as mutable

根據(jù)編譯錯(cuò)誤的提示,通過(guò)mut關(guān)鍵字將默認(rèn)借用修改為可變借用就OK,如下代碼可以編譯通過(guò):

fn main() {let mut s = String::from("hello");change(&mut s);}fn change(some_string: &mut String) {some_string.push_str(", world"); }

不過(guò)可變引用有一個(gè)很大的限制:在特定作用域中的特定數(shù)據(jù)有且只能有一個(gè)可變引用,這個(gè)限制的好處是 Rust 可以在編譯時(shí)就避免數(shù)據(jù)競(jìng)爭(zhēng),這些代碼會(huì)失敗:

let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s;

報(bào)錯(cuò)如下:

error[E0499]: cannot borrow `s` as mutable more than once at a time--> borrow_twice.rs:5:19| 4 | let r1 = &mut s;| - first mutable borrow occurs here 5 | let r2 = &mut s;| ^ second mutable borrow occurs here 6 | }| - first borrow ends here

在存在指針的語(yǔ)言中,容易通過(guò)釋放內(nèi)存時(shí)保留指向它的指針而錯(cuò)誤地生成一個(gè) 懸垂指針(dangling pointer),所謂懸垂指針是其指向的內(nèi)存可能已經(jīng)被分配給其它持有者或者已經(jīng)被釋放。相比之下,在 Rust 中編譯器確保引用永遠(yuǎn)也不會(huì)變成懸垂?fàn)顟B(tài):當(dāng)我們擁有一些數(shù)據(jù)的引用,編譯器確保數(shù)據(jù)不會(huì)在其引用之前離開(kāi)作用域。

讓我們嘗試創(chuàng)建一個(gè)懸垂引用,Rust 會(huì)通過(guò)一個(gè)編譯時(shí)錯(cuò)誤來(lái)避免:

fn main() {let reference_to_nothing = dangle();}fn dangle() -> &String {let s = String::from("hello");&s }

這里是編譯錯(cuò)誤:

error[E0106]: missing lifetime specifier--> dangle.rs:5:16| 5 | fn dangle() -> &String {| ^ expected lifetime parameter|= help: this function's return type contains a borrowed value, but there isno value for it to be borrowed from= help: consider giving it a 'static lifetime

讓我們簡(jiǎn)要的概括一下之前對(duì)引用的討論,以下3條規(guī)則在編譯時(shí)就會(huì)檢查,違反任何一條,編譯報(bào)錯(cuò)并給出提示。

1)在任意給定時(shí)間,只能 擁有如下中的一個(gè):

  • 一個(gè)可變引用。
  • 任意數(shù)量的不可變引用。

2)引用必須總是有效的。

3)引用的壽命不會(huì)超過(guò)值本身的壽命。

3 變量生命周期規(guī)則

生命周期檢查的主要目標(biāo)是避免懸垂引用,考慮以下示例 中的程序,它有一個(gè)外部作用域和一個(gè)內(nèi)部作用域,外部作用域聲明了一個(gè)沒(méi)有初值的變量 r,而內(nèi)部作用域聲明了一個(gè)初值為 5 的變量 x。在內(nèi)部作用域中,我們嘗試將 r 的值設(shè)置為一個(gè) x 的引用。接著在內(nèi)部作用域結(jié)束后,嘗試打印出 r 的值:

error[E0106]: missing lifetime specifier--> dangle.rs:5:16| 5 | fn dangle() -> &String {| ^ expected lifetime parameter|= help: this function's return type contains a borrowed value, but there isno value for it to be borrowed from= help: consider giving it a 'static lifetime

當(dāng)編譯這段代碼時(shí)會(huì)得到一個(gè)錯(cuò)誤:

error: `x` does not live long enough| 6 | r = &x;| - borrow occurs here 7 | }| ^ `x` dropped here while still borrowed ... 10 | }| - borrowed value needs to live until here

編譯錯(cuò)誤顯示:變量 x 并沒(méi)有 “活的足夠久”,那么Rust是如何判斷的呢?

編譯器的這一部分叫做 借用檢查器(borrow checker),它比較作用域來(lái)確保所有的借用都是有效的。如下:r 和 x 的生命周期注解,分別叫做 'a 和 'b:

{let r; // -------+-- 'a// |{ // |let x = 5; // -+-----+-- 'br = &x; // | |} // -+ |// |println!("r: {}", r); // | } // -------+

我們將 r 的生命周期標(biāo)記為 'a 并將 x 的生命周期標(biāo)記為 'b。如你所見(jiàn),內(nèi)部的 'b 塊要比外部的生命周期 'a 小得多。在編譯時(shí),Rust 比較這兩個(gè)生命周期的大小,并發(fā)現(xiàn) r 擁有生命周期 'a,不過(guò)它引用了一個(gè)擁有生命周期 'b 的對(duì)象。程序被拒絕編譯,因?yàn)樯芷?'b 比生命周期 'a 要小:被引用的對(duì)象比它的引用者存在的時(shí)間更短。

關(guān)于借用生命周期檢查,Rust還有一套復(fù)雜的生命周期標(biāo)記規(guī)則,使Rust能在編譯時(shí)就能發(fā)現(xiàn)可能存在的懸垂引用,具體鏈接見(jiàn)[5]。

4 多線程安全保證

內(nèi)存破壞很多情況下是由數(shù)據(jù)競(jìng)爭(zhēng)(data race)所引起,它可由這三個(gè)行為造成:

  • 兩個(gè)或更多指針同時(shí)訪問(wèn)同一數(shù)據(jù)。
  • 至少有一個(gè)這樣的指針被用來(lái)寫(xiě)入數(shù)據(jù)。
  • 不存在同步數(shù)據(jù)訪問(wèn)的機(jī)制。

那么在多線程環(huán)境下,Rust是如何避免數(shù)據(jù)競(jìng)爭(zhēng)的?

先從一個(gè)簡(jiǎn)單的例子說(shuō)起,嘗試在另一個(gè)線程使用主線程創(chuàng)建的 vector:

use std::thread; fn main() {let v = vec![1, 2, 3];let handle = thread::spawn(|| {println!("Here's a vector: {:?}", v);});handle.join().unwrap(); }

閉包使用了 v,所以閉包會(huì)捕獲 v 并使其成為閉包環(huán)境的一部分。因?yàn)?thread::spawn 在一個(gè)新線程中運(yùn)行這個(gè)閉包,所以可以在新線程中訪問(wèn) v。然而當(dāng)編譯這個(gè)例子時(shí),會(huì)得到如下錯(cuò)誤:

error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function--> src/main.rs:6:32| 6 | let handle = thread::spawn(|| {| ^^ may outlive borrowed value `v` 7 | println!("Here's a vector: {:?}", v);| - `v` is borrowed here| help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword| 6 | let handle = thread::spawn(move || {| ^^^^^^^

Rust 會(huì)“推斷”如何捕獲 v,因?yàn)?println! 只需要 v 的引用,閉包嘗試借用 v。然而這有一個(gè)問(wèn)題:Rust 不知道這個(gè)新建線程會(huì)執(zhí)行多久,所以無(wú)法知曉 v 的引用是否一直有效。所以編譯器提示:
closure may outlive the current function, but it borrows v 。

下面展示了一個(gè) v 的引用很有可能不再有效的場(chǎng)景:

use std::thread; fn main() {let v = vec![1, 2, 3];let handle = thread::spawn(|| {println!("Here's a vector: {:?}", v);});drop(v); // 強(qiáng)制釋放變量vhandle.join().unwrap(); }

為了修復(fù)示上面的編譯錯(cuò)誤,我們可以聽(tīng)取編譯器的建議:

help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword| 6 | let handle = thread::spawn(move || {

接下來(lái)是正確的寫(xiě)法:

use std::thread; fn main() {let v = vec![1, 2, 3];let handle = thread::spawn(move || { //使用 move 關(guān)鍵字強(qiáng)制獲取它使用的值的所有權(quán),接下來(lái)就可以正常使用v了println!("Here's a vector: {:?}", v);});handle.join().unwrap(); }

從上面簡(jiǎn)單例子中可以看出多線程間參數(shù)傳遞時(shí),編譯器會(huì)嚴(yán)格檢查參數(shù)的生命周期,確保參數(shù)的有效性和可能存在的數(shù)據(jù)競(jìng)爭(zhēng)。

大家注意到?jīng)]有,上面的例子雖然能正確編譯通過(guò),但是有個(gè)問(wèn)題,變量v的所有權(quán)已經(jīng)轉(zhuǎn)移到子線程中,main函數(shù)已經(jīng)無(wú)法訪問(wèn)v,如何讓main再次擁有v呢?如果用C++或者Golang等語(yǔ)言,你可以有很多種選擇,比如全局變量,指針,引用之類(lèi)的,但是Rust沒(méi)有給你過(guò)多的選擇,在Rust中,為了安全性考慮,全局變量為只讀不允許修改,并且引用不能直接在多線程間傳遞。Rust 中一個(gè)實(shí)現(xiàn)消息傳遞并發(fā)的主要工具是 通道(channel),這種做法時(shí)借鑒了Golang的通道,用法類(lèi)似。

示例:

use std::thread; use std::sync::mpsc; fn main() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let val = String::from("hi");tx.send(val).unwrap();});let received = rx.recv().unwrap();println!("Got: {}", received); }

上例中,我們可以在main函數(shù)中通過(guò)channel得到了子線程中的對(duì)象val。

注意,tx.send(val).unwrap(); 之后,val的所有權(quán)已經(jīng)發(fā)生了變化,接下來(lái)在子線程中不能再對(duì)val進(jìn)行操作,否則會(huì)有編譯錯(cuò)誤,如下代碼:

use std::thread; use std::sync::mpsc; fn main() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let val = String::from("hi");tx.send(val).unwrap();println!("val is {}", val);//在這里會(huì)發(fā)生編譯錯(cuò)誤});let received = rx.recv().unwrap();println!("Got: {}", received); }

這里嘗試在通過(guò) tx.send 發(fā)送 val 到通道中之后將其打印出來(lái)。允許這么做是一個(gè)壞主意:一旦將值發(fā)送到另一個(gè)線程后,那個(gè)線程可能會(huì)在我們?cè)俅问褂盟熬蛯⑵湫薷幕蛘邅G棄。這會(huì)由于不一致或不存在的數(shù)據(jù)而導(dǎo)致錯(cuò)誤或意外的結(jié)果。對(duì)于上面的代碼,編譯器給出錯(cuò)誤:

error[E0382]: use of moved value: `val`--> src/main.rs:10:31| 9 | tx.send(val).unwrap();| --- value moved here 10 | println!("val is {}", val);| ^^^ value used here after move|= note: move occurs because `val` has type `std::string::String`, which does not implement the `Copy` trait

我們通過(guò)channel能夠?qū)崿F(xiàn)多線程發(fā)送共享數(shù)據(jù),但是依然有個(gè)問(wèn)題:通道一旦將一個(gè)值或者對(duì)象send出去之后,我們將無(wú)法再使用這個(gè)值;如果面對(duì)這樣一個(gè)需求:將一個(gè)計(jì)數(shù)器counter傳給10條線程,每條線程對(duì)counter加1,最后在main函數(shù)中匯總打印出counter的值,這樣一個(gè)簡(jiǎn)單的需求如果使用C++或者Golang或者其它非Rust語(yǔ)言實(shí)現(xiàn),非常容易,一個(gè)全局變量,一把鎖,幾行代碼輕松搞定,但是Rust語(yǔ)言可就沒(méi)那么簡(jiǎn)單,如果你是一個(gè)新手,你可能會(huì)經(jīng)歷如下“艱難歷程”:

首先很自然寫(xiě)出第一版:

use std::sync::Mutex; use std::thread; fn main() {let counter = Mutex::new(0);let mut handles = vec![];for _ in 0..10 {let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap()); }

多線程有了,Mutex鎖也有了,能保證每一次加一都是原子操作,代碼看起來(lái)沒(méi)什么問(wèn)題,但是編譯器會(huì)無(wú)情報(bào)錯(cuò):

error[E0382]: capture of moved value: `counter`--> src/main.rs:10:27| 9 | let handle = thread::spawn(move || {| ------- value moved (into closure) here 10 | let mut num = counter.lock().unwrap();| ^^^^^^^ value captured here after move|= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,which does not implement the `Copy` trait error[E0382]: use of moved value: `counter`--> src/main.rs:21:29| 9 | let handle = thread::spawn(move || {| ------- value moved (into closure) here ... 21 | println!("Result: {}", *counter.lock().unwrap());| ^^^^^^^ value used here after move|= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,which does not implement the `Copy` trait error: aborting due to 2 previous errors

錯(cuò)誤信息表明 counter 值的所有權(quán)被move了,但是我們又去引用了,根據(jù)所有權(quán)規(guī)則,所有權(quán)轉(zhuǎn)移之后不允許訪問(wèn),但是為什么會(huì)發(fā)生?

讓我們簡(jiǎn)化程序來(lái)進(jìn)行分析。不同于在 for 循環(huán)中創(chuàng)建 10 個(gè)線程,僅僅創(chuàng)建兩個(gè)線程來(lái)觀察發(fā)生了什么。將示例中第一個(gè) for 循環(huán)替換為如下代碼:

let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1; }); handles.push(handle); let handle2 = thread::spawn(move || {let mut num2 = counter.lock().unwrap();*num2 += 1; }); handles.push(handle2);

這里創(chuàng)建了兩個(gè)線程并將用于第二個(gè)線程的變量名改為 handle2 和 num2,編譯會(huì)給出如下錯(cuò)誤:

error[E0382]: capture of moved value: `counter`--> src/main.rs:16:24| 8 | let handle = thread::spawn(move || {| ------- value moved (into closure) here ... 16 | let mut num2 = counter.lock().unwrap();| ^^^^^^^ value captured here after move|= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,which does not implement the `Copy` trait error[E0382]: use of moved value: `counter`--> src/main.rs:26:29| 8 | let handle = thread::spawn(move || {| ------- value moved (into closure) here ... 26 | println!("Result: {}", *counter.lock().unwrap());| ^^^^^^^ value used here after move|= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,which does not implement the `Copy` trait error: aborting due to 2 previous errors

啊哈!第一個(gè)錯(cuò)誤信息中說(shuō),counter 所有權(quán)被移動(dòng)進(jìn)了 handle 所代表線程的閉包中。因此我們無(wú)法在第二個(gè)線程中再次捕獲 counter , Rust 告訴我們不能將 counter 的所有權(quán)移動(dòng)到多個(gè)線程中。所以錯(cuò)誤原因明朗了,因?yàn)槲覀冊(cè)谘h(huán)中創(chuàng)建了多個(gè)線程,第一條線程獲取了 counter 所有權(quán)后,后面的線程再也拿不到 counter 的所有權(quán)。如何讓多條線程同時(shí)間接(注意,只能是間接)擁有一個(gè)對(duì)象的所有權(quán),哦,對(duì)了,引用計(jì)數(shù)!

通過(guò)使用智能指針 Rc 來(lái)創(chuàng)建引用計(jì)數(shù)的值,嘗試使用 Rc 來(lái)允許多個(gè)線程擁有 Mutex 于是寫(xiě)了第二版:

use std::rc::Rc; use std::sync::Mutex; use std::thread; fn main() {let counter = Rc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Rc::clone(&counter);let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap()); }

再一次編譯并…出現(xiàn)了不同的錯(cuò)誤!編譯器真是教會(huì)了我們很多!

error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>: std::marker::Send` is not satisfied in `[closure@src/main.rs:11:36: 15:10 counter:std::rc::Rc<std::sync::Mutex<i32>>]`--> src/main.rs:11:22| 11 | let handle = thread::spawn(move || {| ^^^^^^^^^^^^^ `std::rc::Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely|= help: within `[closure@src/main.rs:11:36: 15:10 counter:std::rc::Rc<std::sync::Mutex<i32>>]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::sync::Mutex<i32>>`= note: required because it appears within the type `[closure@src/main.rs:11:36: 15:10 counter:std::rc::Rc<std::sync::Mutex<i32>>]`= note: required by `std::thread::spawn`

編譯錯(cuò)誤信息中有關(guān)鍵的一句:
std::rc::Rc<std::sync::Mutex<i32>> cannot be sent between threads safely。

不幸的是,Rc 并不能安全的在線程間共享。當(dāng) Rc 管理引用計(jì)數(shù)時(shí),它必須在每一個(gè) clone 調(diào)用時(shí)增加計(jì)數(shù),并在每一個(gè)克隆被丟棄時(shí)減少計(jì)數(shù)。Rc 并沒(méi)有使用任何并發(fā)原語(yǔ),來(lái)確保改變計(jì)數(shù)的操作不會(huì)被其他線程打斷。在計(jì)數(shù)出錯(cuò)時(shí)可能會(huì)導(dǎo)致詭異的 bug,比如可能會(huì)造成內(nèi)存泄漏,或在使用結(jié)束之前就丟棄一個(gè)值。我們所需要的是一個(gè)完全類(lèi)似 Rc,又以一種線程安全的方式改變引用計(jì)數(shù)的類(lèi)型。所幸 Arc 正是 這么一個(gè)類(lèi)似 Rc 并可以安全的用于并發(fā)環(huán)境的類(lèi)型。字母 “a” 代表 原子性(atomic),所以這是一個(gè)原子引用計(jì)數(shù)(atomically reference counted)類(lèi)型。

于是改寫(xiě)了第三版:

use std::sync::{Mutex, Arc}; use std::thread; fn main() {let counter = Arc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Arc::clone(&counter);let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap()); }

這次編譯通過(guò),并且打印出了正確的結(jié)果,最終,在嚴(yán)厲的編譯器的逐步引導(dǎo),“諄諄教誨”下,我們總算寫(xiě)出了正確的代碼。

Rust編譯器對(duì)多線程數(shù)據(jù)共享,多線程數(shù)據(jù)傳遞這種內(nèi)存安全事故多發(fā)區(qū)進(jìn)行了極其嚴(yán)苛的檢查和限制,確保編譯時(shí)就能發(fā)現(xiàn)潛在的內(nèi)存安全問(wèn)題。在多線程傳遞數(shù)據(jù)時(shí),除了通過(guò)channel,你沒(méi)有第二種選擇;在多線程數(shù)據(jù)共享時(shí),除了Arc+Mutex(如果多線程共享的只是int bool這類(lèi)簡(jiǎn)單數(shù)據(jù)類(lèi)型,你還可以使用原子操作) ,你同樣沒(méi)有別的選擇。雖然 Rust極其缺乏靈活性,但是這同樣是它的有點(diǎn),因?yàn)榫幾g器一直在逼著你寫(xiě)出正確的代碼,極大減少了程序的維護(hù)成本。

以上是我對(duì)Rust內(nèi)存安全保障手段的一些理解,Rust使用一些乍一看很奇怪的特性,非常清晰的定義了一個(gè)安全的邊界,并在上面做以足夠的檢查,保證你的代碼不會(huì)出問(wèn)題。Rust做到了沒(méi)有垃圾回收的內(nèi)存安全,沒(méi)有數(shù)據(jù)競(jìng)爭(zhēng)的并發(fā)安全。同時(shí)一個(gè)新手Rust程序員剛?cè)肟覴ust時(shí),大部分的時(shí)間都是在解決編譯問(wèn)題。一個(gè)新手C++程序員初期可能會(huì)寫(xiě)出很多不安全的代碼,埋下很多坑,但是新手Rust不會(huì),因?yàn)橐粋€(gè)新手Rust寫(xiě)出的不安全代碼在編譯階段就被攔截了,根本沒(méi)有機(jī)會(huì)埋坑,Rust承諾編譯通過(guò)的Rust程序不會(huì)存在內(nèi)存安全問(wèn)題(注意:如果通過(guò)unsafe關(guān)鍵字強(qiáng)制關(guān)閉安全檢查,則依然有可能出現(xiàn)內(nèi)存安全問(wèn)題)。

三 Rust開(kāi)發(fā)效率問(wèn)題

關(guān)于Rust開(kāi)發(fā)效率問(wèn)題,沒(méi)有一個(gè)統(tǒng)一的客觀評(píng)價(jià)標(biāo)準(zhǔn),基本靠個(gè)人主觀感覺(jué)而定。每個(gè)人對(duì)不同語(yǔ)言掌握的熟練度也是影響開(kāi)發(fā)效率的重要因素。關(guān)于開(kāi)發(fā)效率,談一談個(gè)人的感受:先說(shuō)入門(mén),由于Rust一些奇葩的語(yǔ)法的存在(最麻煩的莫過(guò)于生命周期標(biāo)記),導(dǎo)致Rust入門(mén)不像Python和Golang等語(yǔ)言那樣輕松,但是因?yàn)镽ust主要是為了替代C/C++這類(lèi)系統(tǒng)語(yǔ)言而存在,其借鑒了大量C++的語(yǔ)法,如果對(duì)C++熟悉,Rust入門(mén)不是難事;其次說(shuō)說(shuō)開(kāi)發(fā)速度,對(duì)于初學(xué)者,Rust開(kāi)發(fā)體驗(yàn)就像在上海開(kāi)始實(shí)行的垃圾分類(lèi)時(shí)上海人民的那種困惑和凌亂,編譯器檢查太嚴(yán)格了,大多數(shù)時(shí)間都是在解決編譯問(wèn)題,一種在其它語(yǔ)言中理所當(dāng)然的寫(xiě)法,在Rust中就是不行,不過(guò)好在編譯器的提示非常友好,根據(jù)編譯錯(cuò)誤提示大多數(shù)時(shí)候能夠找到答案,不過(guò)編譯雖然費(fèi)事,可一旦編譯通過(guò),程序員就不需要關(guān)心內(nèi)存安全,內(nèi)存泄漏等頭疼問(wèn)題,只需要關(guān)注于業(yè)務(wù)邏輯,寫(xiě)了一個(gè)多月的Rust,debug次數(shù)屈指可數(shù),而且每次debug都是因?yàn)闃I(yè)務(wù)邏輯,從來(lái)沒(méi)有因?yàn)榇a內(nèi)存錯(cuò)誤,崩潰等問(wèn)題debug;如果對(duì)Rust稍微熟練一些,其開(kāi)發(fā)速度絕對(duì)不會(huì)比Python和Golang慢,因?yàn)樵诰幾g階段,Rust就解決了大部分的問(wèn)題,省去了大量的debug時(shí)間。

四 跨平臺(tái)性

Rust跨平臺(tái)性和Golang一樣,擁有優(yōu)秀的跨平臺(tái)性,支持交叉編譯,一份代碼可編譯出支持windows、 linux、arm、macos、freebsd等平臺(tái)上運(yùn)行的二進(jìn)制,且完全靜態(tài)編譯,運(yùn)行時(shí)不依賴任何第三方庫(kù)。這個(gè)特性對(duì)于飽受C++跨平臺(tái)編譯折磨的程序員來(lái)說(shuō)簡(jiǎn)直是福音。Rust對(duì)嵌入式環(huán)境同樣支持友好,有人用Rust寫(xiě)了一個(gè)簡(jiǎn)單的操作系統(tǒng)[6]。

五 生態(tài)問(wèn)題

這一方面應(yīng)該是Rust最弱的地方,作為一個(gè)后起之秀,其生態(tài)遠(yuǎn)遠(yuǎn)不如Python和Golang豐富,不過(guò)使用率很高的一些常用庫(kù)都能找到;并且Rust連續(xù)3年成為Stack Overflow最受歡迎的語(yǔ)言[7],受到的關(guān)注度越來(lái)越高[8],相信未來(lái)Rust的社區(qū)一定會(huì)越來(lái)越豐富。

最后靈魂一問(wèn)收尾:

沒(méi)有垃圾回收的內(nèi)存安全,沒(méi)有數(shù)據(jù)競(jìng)爭(zhēng)的并發(fā)安全、資源消耗低而性能強(qiáng)勁、開(kāi)發(fā)效率高并且跨平臺(tái)性優(yōu)良,這樣的Rust香不香?要不要擁抱一個(gè)?

相關(guān)鏈接

[1]https://github.com/famzah/langs-performance
[2]https://benchmarksgameteam.pages.debian.net/benchmarksgame/fastest/rust-gpp.html
[3]https://deavid.wordpress.com/2019/10/12/benchmarking-python-vs-pypy-vs-go-vs-rust/
[4]https://www.chromium.org/Home/chromium-security/memory-safetyhttps://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/
[5]https://www.bookstack.cn/read/trpl-zh-cn/src-ch10-03-lifetime-syntax.md
[6]https://github.com/redox-os/redox
[7]https://stackoverflow.blog/2020/06/05/why-the-developers-who-use-rust-love-it-so-much/
[8]https://blog.discord.com/why-discord-is-switching-from-go-to-rust-a190bbca2b1f

原文鏈接:https://developer.aliyun.com/article/768337?

版權(quán)聲明:本文中所有內(nèi)容均屬于阿里云開(kāi)發(fā)者社區(qū)所有,任何媒體、網(wǎng)站或個(gè)人未經(jīng)阿里云開(kāi)發(fā)者社區(qū)協(xié)議授權(quán)不得轉(zhuǎn)載、鏈接、轉(zhuǎn)貼或以其他方式復(fù)制發(fā)布/發(fā)表。申請(qǐng)授權(quán)請(qǐng)郵件developerteam@list.alibaba-inc.com,已獲得阿里云開(kāi)發(fā)者社區(qū)協(xié)議授權(quán)的媒體、網(wǎng)站,在轉(zhuǎn)載使用時(shí)必須注明"稿件來(lái)源:阿里云開(kāi)發(fā)者社區(qū),原文作者姓名",違者本社區(qū)將依法追究責(zé)任。 如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,歡迎發(fā)送郵件至:developer2020@service.aliyun.com 進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。

總結(jié)

以上是生活随笔為你收集整理的连续 3 年最受欢迎:Rust,香!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。