iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Software Development

Rust的多方面運用系列 第 13

[Day13] 多執行緒

最近都凌晨睡前寫鐵人賽,就怕隔天忘記Q
嘛 這次鐵人賽真的學到蠻多的,算是複習吧。
原本可能搞不懂的還是得搞懂。
總之,快要進入到專案篇的部份了,就開始今天的內容ㄅ。


今天要講的就是昨天已經預告過的多線程。
那多線程是許多人在修作業系統的惡夢,我在看清大教學的時候也覺得蠻難理解的,可能鐵人賽後會再去重看一次全部教程吧。
那如基本上多線程程式會遇到的問題有

  • Synchronization
  • DeadLock

除了上面需要注意的點還有多執行緒的程式不一定每次的執行順序會一樣

這邊舉一個錯誤例子:

static mut a: i32 = 0;

fn main() {
    unsafe {
        a += 1;
    };
}

這個在單執行緒的情況下運行是正常的
不過如果到多線程就會需要依靠運氣來讓他跑出想要的正確答案。
為什麼呢:
因為他被分成三步

  1. 從記憶體中把 a 的值放入暫存器
  2. 將暫存器裡的值 + 1
  3. 將 + 1 後的值丟回記憶體

那如果說這三步都在不同執行緒下執行就有可能發生 Race Condition 的問題。
可能就會像下面所說的執行
執行緒1. 執行第一步 暫存器 = 0
執行緒1. 執行第二步 暫存器 = 1
執行緒2. 執行第一步 暫存器 = 0
執行緒1. 執行第三步 暫存器 = 0
最後 a = 0
那這種問題一旦出現都很嚴重,假如說今天你領錢,執行緒2. 將領出的錢視為沒領出
這樣銀行早就破產了,但是我好希望有一天領錢帳戶餘額不會減少
那處理方法很簡單就直接使用 atomic 區塊包著就好了
atomic 區塊是代表說無法執行 context switch 的區塊
那麼就能夠成功避免說上述得情況

再舉個例子

use std::thread;

fn main() {
    for mut a in 0..=10 {
        thread::spawn(move || {
            print!("{} ", a);
        });
    }
}

那如果說執行這段程式這段程式是使用多線程來運行 for 迴圈
那麼執行後會發現每一次的執行的輸出值都會不同這是因為執行緒是亂數執行的。
那也就是他對於同一區塊同時進行讀寫而導致的,所以需要確保他執行的順序owo


OK 知道多執行緒會帶來的問題後就可以開始想該如何解決了以及該如何撰寫了!
那麼 Rust 有STD庫中有兩個模組正是來控制執行緒的分別是:

  • std::thread 管理執行緒
  • std::sync 定義了鎖,Channel, 條件變數跟屏障

從 std::thread 開始講吧
就以前面的程式碼當例子 他會反覆運算產生 11 個 thread
而生成方法是使用 spawn 這個函數,並且 由於要使用 a 這個變數,所以需要使用 move 關鍵字來讓 a 的所有權傳輸進新的 thread 裡
那麼前面遇到的問題要怎麼解決呢
可以使用 join 來解決
join 就是讓主 thread 去等待子 thread 停止運算

use std::thread;

fn main() {
    for mut a in 0..=10 {
        let th = thread::spawn(move || {
            print!("{} ", a);
        });
        th.join();
    }
}

就像這樣

那麼如果說想要自定執行緒的話要怎麼做呢
使用 builder 函數去創建而不是 spawn
那麼使用 builder 的好處在於可以自由定義名稱或是修改 stack 大小
這邊暫且不贅述


今天的內容大概這樣 主要是後面的內容我沒有什麼把握能夠講好講精確,希望各位讀者見諒。
多執行緒要注意的東西真的很多,能避免錯誤的工具也很多 像是 Mutex 之類的等等
詳細的話可以去 Rust Book 上尋找查閱。
總之,今天到這邊,晚安


上一篇
[Day12] Trait 與 STD 庫中的 fs
下一篇
[Day14] 引入 crate
系列文
Rust的多方面運用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言