|
|
3 z' u9 C+ C/ W2 I5 p7 a<h1 id="如何实现一个线程池">如何实现一个线程池</h1>, h3 v- t' u0 K9 K$ @/ j0 ] ?8 J
<p>线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,对于计算密集型任务,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。</p>3 c3 {' @. ^) r
<p>如何定义线程池Pool呢,首先最大线程数量肯定要作为线程池的一个属性,并且在new Pool时创建指定的线程。</p>
. j, H; U6 b! V# ^' X9 Z$ l<p>线程池Pool</p>5 O3 G! j- C9 o4 r5 v: [
<pre><code>pub struct Pool {
" [' G4 G+ w$ z, z+ A max_workers: usize, // 定义最大线程数
4 T; J% M7 o5 i7 m6 V+ |}
" J* R. F' F3 a; H1 R
* k6 a; k5 I2 c* Z1 Simpl Pool {+ l7 ~8 q# p! q3 F, {+ R( w
fn new(max_workers: usize) -> Pool {}
1 f. i2 w4 j/ S& N" b! s( u fn execute<F>(&self, f:F) where F: FnOnce() + 'static + Send {}
) ]2 ~( q6 B8 I% a: A+ h8 ^}
6 @) z( `) z/ F7 l* K A l" p* o
8 N9 S! W* ?- B& H& |</code></pre>5 R' x, n3 l8 R& @9 @
<p>用<code>execute</code>来执行任务,<code>F: FnOnce() + 'static + Send</code> 是使用thread::spawn线程执行需要满足的trait, 代表F是一个能在线程里执行的闭包函数。</p>
* s C' n2 u% l/ G; }! u1 e7 N8 m2 J+ @<p>另一点自然而然会想到在Pool添加一个线程数组, 这个线程数组就是用来执行任务的。比如<code>Vec<Thread></code> balabala。这里的线程是活的,是一个个不断接受任务然后执行的实体。<br>
4 O' E: V+ O- ]可以看作在一个线程里不断执行获取任务并执行的Worker。</p>
. U5 y" F+ f% @$ I<pre><code>struct Worker where6 ]$ ?; N2 }+ ]4 j& m* C5 v
{
& C2 M6 @) ]& \3 J: J$ e/ N _id: usize, // worker 编号
6 c( Z& F: l- M# n2 }}
% U1 E K5 m* @2 A3 v! U</code></pre>
3 o+ S9 f9 |4 N8 f/ g<p>要怎么把任务发送给Worker执行呢?mpsc(multi producer single consumer) 多生产者单消费者可以满足我们的需求,<code>let (tx, rx) = mpsc::channel()</code> 可以获取到一对发送端和接收端。<br>4 G' v C5 U8 y% w
把发送端添加到Pool里面,把接收端添加到Worker里面。Pool通过channel将任务发送给多个worker消费执行。</p>
# z6 H+ c* o. |% H; t<p><strong>这里有一点需要特别注意,channel的接收端receiver需要安全的在多个线程间共享</strong>,因此需要用<code>Arc<Mutex::<T>></code>来包裹起来,也就是用锁来解决并发冲突。</p>3 Y8 Q5 O$ G) A! C' N; Q! Q
<p>Pool的完整定义</p>
2 j& ?' }" |7 u<pre><code>pub struct Pool {4 U, O+ q# H8 L9 I6 A5 x, k( T: x
workers: Vec<Worker>,
/ n- U' R4 }+ n# I. f. b max_workers: usize," N. w. z0 ^8 J9 n/ l) ]* i _
sender: mpsc::Sender<Message>
. z7 R0 N: v5 |} Q$ C1 i$ S9 ] d/ J8 D# [
</code></pre>
0 M9 r+ t1 i0 Q3 [* x: Z% R. r$ T<p>该是时候定义我们要发给Worker的消息Message了<br>
9 r9 P# \( k* M: y定义如下的枚举值</p>/ W" H) C+ a* g1 R
<pre><code>type Job = Box<dyn FnOnce() + 'static + Send>;6 m: G/ Q+ E2 `4 v
enum Message {, q0 Y" s* l0 f* j, M; Y5 h
ByeBye,0 ?( C4 O( J2 e& W/ d4 e6 x( m/ c
NewJob(Job),! P0 n( a- E, @
}; I2 ]1 n" `6 ?. S3 s
</code></pre>: j8 L p- a# u# D- y- H
<p>Job是一个要发送给Worker执行的闭包函数,这里ByeBye用来通知Worker可以终止当前的执行,退出线程。</p> C) C. ~/ F* P* y' P
<p>只剩下实现Worker和Pool的具体逻辑了。</p>. j. D" U. x! L
<p>Worker的实现</p>
$ a: W# Z" p9 Z/ S. x& Z# X _# L<pre><code>impl Worker
- v) i" E" d* Z a- H{2 V0 v# t8 o8 [: E' [- ?4 r
fn new(id: usize, receiver: Arc::<Mutex<mpsc::Receiver<Message>>>) -> Worker {$ }0 {+ c: t4 u) A A1 m
let t = thread::spawn( move || {
: |' k7 Z% `4 C" t' O loop {) e3 q A/ C# `' M6 f5 I9 v. i
let receiver = receiver.lock().unwrap();$ d3 ^' I( U1 d/ ^
let message= receiver.recv().unwrap();
) E. X; B; o6 c2 u8 R match message {0 m! }- Q+ f0 E% L' j" B
Message::NewJob(job) => {: Q! D5 @7 _; n
println!("do job from worker[{}]", id);0 j' ]8 M3 t4 O$ q$ P
job();. U7 L7 z& T9 d% C
},
9 Y, F% V1 a( I! V* o- ` Message::ByeBye => {# S- i# S4 A3 z; T! @
println!("ByeBye from worker[{}]", id);
# b. A; e, G' N* g2 `/ l break
& h1 `: W7 ~" j2 T+ b! j" y4 C },! `" \! h; E* D2 B2 a: `5 H
}
! r9 d4 {% F, v: C) t }$ i- r3 t7 o$ ~% v# F
});
! F2 u u$ `3 ?5 `* i% U; z: ~3 `& |' W! O5 u8 V$ c
Worker {
) R: Q! f/ S: b _id: id,1 z9 u2 o8 H# T* y5 R
t: Some(t),
3 Y e+ u" ?5 M6 X9 t6 `' O3 ^: J }1 f& D# u& L# N, Z& B9 D4 X5 Z l
}
8 H1 W4 M1 T9 }3 {9 L G}
: d) C6 s" f7 Z9 f. ]</code></pre>
, ` J# b" w# H% c1 x) p<p><strong>let message = receiver.lock().unwrap().recv().unwrap();</strong> 这里获取锁后从receiver获取到消息体,然后let message结束后rust的生命周期会自动释放掉锁。<br>
9 c, }: i) j: Y% _5 @4 X3 {但如果写成</p>" H$ h" \! R. W% j( ]
<pre><code>while let message = receiver.lock().unwrap().recv().unwrap() {* _2 ]7 [% h3 u6 z6 Y( N
}; B. p9 f+ p" e+ O& R* D3 \
</code></pre>
2 O9 @5 O: `; @* k7 G4 W- r4 _* I: ?7 O<p>while let 后面整个括号都是一个作用域,要在这个作用域结束后,锁才会释放,比上面let message要锁定久时间。<br>
5 j. Z& D$ E4 Y3 `rust的mutex锁没有对应的unlock方法,由mutex的生命周期管理。</p>
/ Q" O8 Q) X4 S! |3 _: r9 [<p>我们给Pool实现<code>Drop</code> trait, 让Pool被销毁时,自动暂停掉worker线程的执行。</p>$ p) |; k/ u# d8 j4 |1 T$ i
<pre><code>impl Drop for Pool {
/ U4 v6 k. i ?6 ] fn drop(&mut self) {
( j! ~ {6 Q1 a+ M. \) f for _ in 0..self.max_workers {7 V/ H& i) F/ y0 r7 q
self.sender.send(Message::ByeBye).unwrap();
8 n& L6 S! J! x4 E. B. [5 C& ]. |" { }
1 i+ x$ n0 v6 e* a& R9 H for w in self.workers.iter_mut() {# w/ i5 z9 a; x9 }1 E
if let Some(t) = w.t.take() {
" H8 u" W9 j, L4 l t.join().unwrap();$ _; C4 H- L- Q: x7 Q# O( {9 p
}
) E) ?; c8 y n [' L- t }% Z4 G2 o4 {6 x/ T) C) _' Z1 K3 o
}. g# u$ d2 D) w+ g" R: @8 h- z
}2 H! _/ S' `3 ?, B' u j) c
6 y1 @' B; t% U4 s
</code></pre>- U8 e$ [+ M$ s3 d4 d% @5 k3 a3 u1 g
<p><strong>drop方法里面用了两个循环</strong>,而不是在一个循环里做完两件事?</p>( g$ c. A* r, s
<pre><code>for w in self.workers.iter_mut() {
/ M2 X% }! P3 r- y- T4 ~: x6 P if let Some(t) = w.t.take() {
; f' e) H; Y% G% j/ | self.sender.send(Message::ByeBye).unwrap();2 T( j+ s: C( h- |8 f) E
t.join().unwrap();
2 u# m" }& y/ t }
* \1 C6 g8 L3 E( ]6 n3 i}
% i7 h# p9 q# Z" h V2 U+ }0 o* \% ^. N# \* J3 U+ m; N: m+ g% K4 t
</code></pre>
& S, r5 _8 |8 }, o<p>这里面隐藏了一个会造成死锁的陷阱,比如两个Worker, 在单个循环里面迭代所有Worker,再将终止信息发送给通道后,直接调用join,<br>
4 K% q& K; R% ]# P我们预期是第一个worker要收到消息,并且等他执行完。当情况可能是第二个worker获取到了消息,第一个worker没有获取到,那接下来的join就会阻塞造成死锁。</p># f g5 W* Z: ^# z9 |2 R$ I% I
<p><strong>注意到没有,Worker是被包装在Option内的</strong>,这里有两个点需要注意</p>
: N7 E, s& Q: Q<ol>
& e1 K% \$ r& p. R* l: p<li>t.join 需要持有t的所有权</li>( x4 o+ u8 a I4 Y' j% r
<li>在我们这种情况下,self.workers只能作为引用被for循环迭代。</li>
: e. w2 j1 a8 q3 X k$ x2 [3 z</ol>
E1 g8 ^. c ^9 \9 v, d I<p>这里考虑让Worker持有<code>Option<JoinHandle<()>></code>,后续可以通过在Option上调用take方法将Some变体的值移出来,并在原来的位置留下None变体。<br>) E0 Q$ J' E* o; w+ }9 [2 ?
换而言之,让运行中的worker持有Some的变体,清理worker时,可以使用None替换掉Some,从而让Worker失去可以运行的线程</p>
0 J( @0 `2 T8 O- H6 {<pre><code>struct Worker where# U0 C/ {) d9 l3 s% [; {0 i
{
& ~, h* Y# t; b/ R5 Y& D! t& p _id: usize, b4 o& x- e3 @; |! |4 B
t: Option<JoinHandle<()>>,
4 k% ]8 i+ d4 A! N9 T: }: E1 q' p& L}
` e- b$ r0 v; d' ]7 r! A! M</code></pre>0 ]/ s8 A$ Y$ D& v1 m9 w0 ^
<h1 id="要点总结">要点总结</h1>
/ i M4 `6 d) B4 m( p" {) P<ul>- x0 j$ `$ r) g: L8 G: [
<li>Mutex依赖于生命周期管理锁的释放,使用的时候需要注意是否逾期持有锁</li>) A3 t* }+ x/ n& M4 L' s
<li><code>Vec<Option<T>></code> 可以解决某些情况下需要T所有权的场景</li>- g4 T0 Y( s9 H% T, l6 T9 w
</ul>
& q+ P; f: B, }1 F& q* O% k<h1 id="完整代码">完整代码</h1>4 E% Z' L: d( M
<pre><code>use std::thread::{self, JoinHandle};3 \& E6 x6 H. Q3 ~
use std::sync::{Arc, mpsc, Mutex};
+ k( E' |8 ?# g1 X$ Z: t5 K$ v+ E& G6 l" V2 r/ U
! d' [* p4 W+ ~2 H; o
type Job = Box<dyn FnOnce() + 'static + Send>;6 H- \4 C# e1 D M
enum Message {
9 F) D! H& Y7 R( {7 o" { ByeBye,
3 t2 q9 W" P* A6 ?6 w NewJob(Job),$ U1 i3 R( u$ b7 x
} P- ^! o: y% Z2 C% u8 s5 a5 V
+ q$ N- Q: G5 a- i6 x8 f7 ~struct Worker where
4 G2 B3 \, J$ L ^2 ^/ Y{$ Y; l6 l9 i1 t4 u
_id: usize,0 e2 @' V% t0 I; T1 k# f& \ I0 O
t: Option<JoinHandle<()>>,
) |( y# u# n; s: |) ~7 a} e; M4 M' Q$ z# w
8 [9 i3 Q n* j
impl Worker
$ X t- f9 W, n* H0 ]{) R/ G5 O; t5 x! ?
fn new(id: usize, receiver: Arc::<Mutex<mpsc::Receiver<Message>>>) -> Worker {7 k4 S, q% R1 ^# N$ O
let t = thread::spawn( move || {
, X1 z1 |4 k# q' ~4 V$ E- m loop {6 F. Y1 ]( ~- _; {7 d2 N6 ?
let message = receiver.lock().unwrap().recv().unwrap();8 U1 R/ y N# t0 J9 S
match message {% @: J- ^8 u' ]. A9 R* w8 {8 A- n
Message::NewJob(job) => {1 X# c5 u0 y) m; C
println!("do job from worker[{}]", id);
3 P! |0 z* B2 Z" v- I0 [ job();! p# a: `' ~1 ~ Z" x6 ]" g: Y, ^
},
3 D7 }/ @# V) ^$ }7 s' W4 K Message::ByeBye => {6 O9 ~3 t1 R8 V- G4 W5 {, s, |
println!("ByeBye from worker[{}]", id);4 g8 `# g; x6 |9 B4 ]4 `% y
break
5 G/ I4 U* @% q) E: M2 G, U6 [ },
8 F' y* B3 B! Z7 L6 T& u; i0 a } / ]& h- \- ]2 a t y4 l
}2 m) G6 C8 G% y/ O$ k9 N* t
});# }) P1 r+ v2 J' i
/ U+ \$ U1 c# g0 s& T6 t Worker {. O a7 x3 @% d6 A( _* p
_id: id,2 `# V: X+ L, X, p: }- u
t: Some(t),: M* e* E: K- d; K3 U' l
}
$ F; n' I3 U! A, _' I: `$ w }
' T9 [1 r O0 e9 p& P8 M}9 M% `0 g2 `, a0 s( x$ b3 _6 h! b
! i, _/ F C7 x4 S. i. l1 Qpub struct Pool {
- P+ J r- k4 V$ Z- b# _ workers: Vec<Worker>,5 h3 j! A, W5 @* p3 l( \
max_workers: usize,
) n7 A: Y! k' b' e7 c8 {" _ sender: mpsc::Sender<Message>0 A. o( o' R% z8 v6 P
} \: V( P" C' g1 q- M
4 E3 j- _: x! C& ~) T6 b: f6 z
impl Pool where {
& J3 p" S) k( ~7 R; K7 a pub fn new(max_workers: usize) -> Pool {
! t& v' o8 [0 C$ X2 V if max_workers == 0 {7 P/ i* s& k6 j' c' H+ ^
panic!("max_workers must be greater than zero!")# b' T8 A8 t7 y
}- L6 _' h6 |6 y' V$ ~
let (tx, rx) = mpsc::channel();
+ O- F& o1 U( m1 o# b) E# A6 ? @' i1 H9 C1 P
let mut workers = Vec::with_capacity(max_workers);
+ L# |# }7 x# y; U! S7 Q let receiver = Arc::new(Mutex::new(rx));
1 {; w( g- b1 T% |: h7 W/ K4 T for i in 0..max_workers {
8 N* t' j) S, e5 m workers.push(Worker::new(i, Arc::clone(&receiver)));
& m* k/ n: y# ^* k9 t0 O }$ p( E! d7 b6 P) C' \
8 G9 X/ C' H# n" ~8 c
Pool { workers: workers, max_workers: max_workers, sender: tx }6 m* D3 }$ K' P: t& [
}: b; H5 E4 @ P6 s0 Z
3 e6 g+ Z; z+ Q; _! B pub fn execute<F>(&self, f:F) where F: FnOnce() + 'static + Send
" n; G9 T( C. E u {
8 t% n$ W7 P5 H: P* ]% E8 s- l) e& q$ g t
let job = Message::NewJob(Box::new(f));
. |. w- I& e9 X1 c& I" ^9 m6 [ self.sender.send(job).unwrap();) V' s, }) l* O" W/ c" o6 Z& I; S
}
' m2 z; Q& o1 k! K: p}0 A! f. ~ P1 d2 {3 [
- u. T0 T, F9 K+ R+ V; O+ k J
impl Drop for Pool {0 o' S% C/ e& _/ O$ } n
fn drop(&mut self) {, N. a3 M7 y7 o8 M: t7 m. A8 _
for _ in 0..self.max_workers {% h+ s$ z' E) p; j' _
self.sender.send(Message::ByeBye).unwrap();
) a% A% j) a' H' Y, k; f }8 O& S* `1 I0 m! S N A/ I
for w in self.workers {5 }' X' _8 {0 k: i$ O
if let Some(t) = w.t.take() {
8 H t' L& g4 b% P4 w t.join().unwrap();" d# D& e, l4 A
}; u& B6 Y/ d, q" }9 _) y
}+ B$ p, G. P8 d* Q" Y# |
}4 i9 A2 \, g$ k8 d
}% ~9 m6 V2 J7 ?, r
) z$ d4 m" I3 m% [! _& X
4 }& H! d5 \# k R
#[cfg(test)]
" k" n0 P4 i) L# H* [mod tests {2 I5 G. I7 p2 i
use super::*;
# y4 S: | A. G8 e; r #[test]4 O3 s$ N$ u0 f# d) }) V
fn it_works() { ~0 C( i: M7 N5 k7 W y# s
let p = Pool::new(4);' i K, ?5 e4 ^0 g/ C3 ?
p.execute(|| println!("do new job1"));3 j3 Q* X( R, m4 Z1 y) [
p.execute(|| println!("do new job2"));& G9 Z2 @2 z- b9 L+ Y1 r
p.execute(|| println!("do new job3"));
9 \% v+ P: d5 w4 i$ ~- w7 j1 ~ p.execute(|| println!("do new job4"));7 r& j* I, a: ^: N7 d' T! A
}. F" R2 P# O& Z7 T6 C
}2 s5 g) {1 O; I- E- c8 ?7 S* j4 Z# |$ F
</code></pre>
6 `2 Y& l- @: z) f' e' x) d+ H: h! p
|
|