這是一個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)思路,有想法歡迎留言丁寄。