Rust 實現(xiàn)兩個線程交替打印 0-100 的奇偶數(shù)

這是一個JAVA的面試題岖赋,如果使用RUST能否實現(xiàn)呢巧还?

面試場景

面試官: JAVA多線程了解嗎鞭莽?你給我寫一個,起兩個線程交替打印 0-100 的奇偶數(shù)麸祷。

小黃:芭炫?

面試官:就是有兩個線程阶牍,一個線程打印奇數(shù)另一個打印偶數(shù)喷面,它們交替輸出星瘾,類似:

偶線程:0 奇線程: 1 偶線程:2 ...... 奇線程: 99 偶線程:100

小黃:啊乖酬?

面試官:......嗯死相,好的∫瘢回去等通知吧。

解說

遇到這種突如其來的面試題生宛,有時候會讓人無從下手县昂。盡管可能你學習過多線程的知識,但是面試官拋一個問題過來陷舅,短時間內(nèi)可能想不出如何使用這些知識來解決這個具體的問題倒彰。其實這個問題考察的知識點并不難,但是如果準備面試的時候沒有看過這種題莱睁,一時間還是比較難想出解決方案來的待讳,而且這種題往往是讓面試者手寫代碼。

回到題目上來仰剿。首先是兩個線程创淡,其次是交替打印。JAVA的思路是聯(lián)系到線程之間的通信南吮,以及加鎖琳彩,哪個線程拿到鎖就打印,然后釋放鎖讓另一個線程獲取鎖部凑。兩個線程輪流拿到鎖露乏,實現(xiàn)交替打印的效果。在RUST中有沒相關(guān)的實現(xiàn)方式呢涂邀?

討巧的方案

比較容易想的一個方案是瘟仿,要輸出的時候判斷一下當前需要輸出的數(shù)是不是自己要負責打印的值,如果是就輸出比勉,不是就暫停當前線程劳较。

use std::thread;
use std::time::Duration;

fn main() {
    // 線程1
    let t1 = thread::spawn(|| {
        // 循環(huán)100次 偶數(shù)就打印否則使用 sleep 暫停當前線程
        for i in 1..100 {
            if i % 2 == 0 {
                print!("偶線程: {} ", i);
            } else {
                thread::sleep(Duration::from_millis(1));
            }
        }
    });
    // 線程2
    let t2 = thread::spawn(|| {
        for i in 1..100 {
            if i % 2 != 0 {
                print!("奇線程: {} ", i);
            } else {
                thread::sleep(Duration::from_millis(1));
            }
        }
    });

    t1.join().unwrap();
    t2.join().unwrap();
}

輸出如

奇線程: 1 偶線程: 2 奇線程: 3 偶線程: 4 奇線程: 5 偶線程: 6 奇線程: 7 奇線程: 9 偶線程: 8 奇線程: 11 偶線程: 10 奇線程: 13 ......

從輸出上看,是實現(xiàn)了題目上的部分要求敷搪,兩個線程兴想,一個打印奇數(shù),一個打印偶數(shù)赡勘。但只是用了一個討巧的方式實現(xiàn)了分別打印嫂便,而并沒有交替輪流打印,有亂序闸与,明顯沒有達到面試官想考察的考點上毙替。代碼中兩個線程是分離的并沒有任何數(shù)據(jù)同步方式岸售。那RUST中如何進行線程同步以及數(shù)據(jù)交換呢?

Channel通信通道消息傳遞實現(xiàn)方案

在 RUST 中線程通信和同步相關(guān)概念有:

  • 互斥鎖 (Mutex)
  • 屏障(Barrier)
  • 條件變量(Condition Variable)
  • Channel通信通道(mpsc)

查閱了很多資料厂画,其中 Channel 通信通道是比較推薦的方式凸丸。

在Rust中,線程就是CSP(Communicating Sequential Processes袱院,通信順序進程)進程屎慢,而通信通道就是Channel。在Rust標準庫的std::sync::mpsc 模塊中為線程提供了Channel機制忽洛,其具體實現(xiàn)實際上是一個多生產(chǎn)者單消費者(Multi Producer Single Consumer腻惠,MPSC)的先進先出(FIFO)隊列。線程通過Channel進行通信欲虚,從而可以實現(xiàn)無鎖并發(fā)集灌。

根據(jù)消息傳遞機制,創(chuàng)建兩個通信通道和兩個線程复哆,一個線程獲取到數(shù)據(jù)進行加1操作欣喧,再發(fā)送到一個通信通道,另一個線程相同操作只是數(shù)據(jù)使用另一個通道發(fā)送梯找,直到數(shù)據(jù)大于100結(jié)束循環(huán)唆阿。

代碼實現(xiàn):

use std::thread;
use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;

const SLEEP: u64 = 10;

fn main() {
    let control = Arc::new(Mutex::new(true));
    let (tx1, rx1) = mpsc::channel::<i32>();
    let (tx2, rx2) = mpsc::channel::<i32>();
    
    let t1 = thread::spawn(move || {
        loop {
            let data = rx1.recv().unwrap_or(0);

            if data > 100 {
                break;
            }

            print!("偶線程:{} ", data);

            if let Err(res) = tx2.send(data + 1) {
                println!("=== {:?}", res);
                break;
            }

            thread::sleep(Duration::from_millis(SLEEP));
        }
    });

    let t2 = thread::spawn(move || {
        loop {
            let mut ctl = control.lock().unwrap();

            if *ctl {
                *ctl = false;
                tx1.send(0).unwrap();
            }

            let data = rx2.recv().unwrap_or(0);

            if data > 100 {
                break;
            }

            print!("奇線程:{} ", data);
                
            if let Err(res) = tx1.send(data + 1) {
                println!("---- {:?}", res.to_string());
                break;
            }

            thread::sleep(Duration::from_millis(SLEEP));
        }
    });

    t2.join().unwrap();
    t1.join().unwrap();
}

輸出結(jié)果:

偶線程:0 奇線程:1 偶線程:2 奇線程:3 偶線程:4 ...... 奇線程:99 偶線程:100

本方案實現(xiàn)中使用了線程間通信和數(shù)據(jù)共享互斥鎖,并且輸出結(jié)果是交替輪流打印輸出初肉,完全滿足面試題目要求酷鸦。

代碼優(yōu)化

兩個線程主邏輯公用部分抽離,最終代碼如下:

use std::thread;
use std::sync::{mpsc, mpsc::{Sender, Receiver}, Arc, Mutex};
use std::time::Duration;

// 線程延時處理時間
const SLEEP: u64 = 10;

/**
 * 線程操作共有邏輯處理
 * 
 * @param name: String 線程名稱
 * @param rx: Receiver<i32> 信號接收
 * @param tx: Sender<i32> 信號發(fā)送
 * @param control Arc<Mutex<bool>> 控制器
 * @param send_start bool 是否發(fā)送初始數(shù)據(jù)
 */
fn handle (
    name: String,
    rx: Receiver<i32>,
    tx: Sender<i32>,
    control: Arc<Mutex<bool>>,
    send_start: bool,
) {
    // 無限循環(huán)
    loop {
        // 初始運行需要發(fā)送數(shù)據(jù) 0
        if send_start {
            // 獲取控制器鎖
            let mut ctl = control.lock().unwrap();

            if *ctl {
                *ctl = false;
                // 發(fā)送數(shù)據(jù) 0
                tx.send(0).unwrap();
            }
        }
        // 從信息通道接收數(shù)據(jù)
        let data = rx.recv().unwrap_or(0);

        // 數(shù)據(jù)超過100停止循環(huán) 會自動停止當前線程
        if data > 100 {
            break;
        }

        // 每次接收到數(shù)據(jù) 進行加1操作再發(fā)送到另一個信號通道
        if let Ok(_) = tx.send(data + 1) {
            // 輸出結(jié)果
            print!("{}:{} ", name, data);
        } else {
            // 如果信號通道關(guān)閉停止當前循環(huán)
            break;
        }

        // 當前線程延時處理 運行看起來慢一些 無實際意義
        thread::sleep(Duration::from_millis(SLEEP));
    }
}

fn main() {
    // 共享控制器 第一次運行需要先發(fā)送一次初始數(shù)據(jù) 0
    let control = Arc::new(Mutex::new(true));
    // 信號通道1
    let (tx1, rx1) = mpsc::channel::<i32>();
    // 信號通道2
    let (tx2, rx2) = mpsc::channel::<i32>();

    let c1 = Arc::clone(&control);
    let name1 = String::from("偶線程");
    // 生成線程1
    let t1 = thread::spawn(move || {
        handle(name1, rx1, tx2, c1, false);
    });

    let c2 = Arc::clone(&control);
    let name2 = String::from("奇線程");
    // 生成線程2
    let t2 = thread::spawn(move || {
        handle(name2, rx2, tx1, c2, true);
    });

    t2.join().unwrap();
    t1.join().unwrap();
}

至此牙咏,本題解決臼隔。

擴展

兩個線程使用通信實現(xiàn)交替打印的問題解決了,如果

  • 要使用JAVA一樣獲取鎖再釋放鎖應(yīng)該如何實現(xiàn)呢妄壶?
  • 或是有3個線程進行交替打印又如何實現(xiàn)呢摔握?即:
線程1:1 線程2:2 線程3:3 線程1:4 線程2:5 線程3:6

這兩個問題暫時還沒有實現(xiàn)思路,有想法歡迎留言丁寄。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氨淌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伊磺,更是在濱河造成了極大的恐慌盛正,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屑埋,死亡現(xiàn)場離奇詭異豪筝,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門续崖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敲街,“玉大人,你說我怎么就攤上這事严望《嗤В” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵像吻,是天一觀的道長峻黍。 經(jīng)常有香客問我,道長萧豆,這世上最難降的妖魔是什么奸披? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮涮雷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轻局。我一直安慰自己洪鸭,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布仑扑。 她就那樣靜靜地躺著览爵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镇饮。 梳的紋絲不亂的頭發(fā)上蜓竹,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音储藐,去河邊找鬼俱济。 笑死,一個胖子當著我的面吹牛钙勃,可吹牛的內(nèi)容都是我干的蛛碌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼辖源,長吁一口氣:“原來是場噩夢啊……” “哼蔚携!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起克饶,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤酝蜒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后矾湃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亡脑,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了远豺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奈偏。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖躯护,靈堂內(nèi)的尸體忽然破棺而出惊来,到底是詐尸還是另有隱情,我是刑警寧澤棺滞,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布裁蚁,位于F島的核電站,受9級特大地震影響继准,放射性物質(zhì)發(fā)生泄漏枉证。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一移必、第九天 我趴在偏房一處隱蔽的房頂上張望室谚。 院中可真熱鬧,春花似錦崔泵、人聲如沸秒赤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽入篮。三九已至,卻和暖如春幌甘,著一層夾襖步出監(jiān)牢的瞬間潮售,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工锅风, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酥诽,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓遏弱,卻偏偏與公主長得像盆均,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漱逸,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容