|
|
: G* P5 s! n C0 ^8 x: G
<h1 id="如何实现一个线程池">如何实现一个线程池</h1>
6 g8 R: ?* _# V5 e+ i$ E<p>线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,对于计算密集型任务,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。</p>* q' P' B6 X1 D X: y( }* ^
<p>如何定义线程池Pool呢,首先最大线程数量肯定要作为线程池的一个属性,并且在new Pool时创建指定的线程。</p>
$ n/ y" Z* b( h9 h<p>线程池Pool</p>* E; u0 q' }3 R0 w7 U2 \! p% j
<pre><code>pub struct Pool {
! A7 m% f! ?% u+ p" k7 J max_workers: usize, // 定义最大线程数5 `0 w F! z- ^& x
}. {4 [6 T1 ^3 D. T- U9 b4 F( m9 t$ q
3 Z i# Z1 r5 `* | N/ j, c
impl Pool {
7 ]4 ]& _# [6 X' c7 F. r* h$ _ fn new(max_workers: usize) -> Pool {}
" R' P+ s) d) w+ p3 I fn execute<F>(&self, f:F) where F: FnOnce() + 'static + Send {}
7 ^4 M V; P+ M& W+ s; S, m}. I, \3 o/ W r' {" Q
0 R- }9 @) C- k</code></pre>5 q9 ?$ ~# b) @
<p>用<code>execute</code>来执行任务,<code>F: FnOnce() + 'static + Send</code> 是使用thread::spawn线程执行需要满足的trait, 代表F是一个能在线程里执行的闭包函数。</p>
' R" D: _- F% u b6 e* f<p>另一点自然而然会想到在Pool添加一个线程数组, 这个线程数组就是用来执行任务的。比如<code>Vec<Thread></code> balabala。这里的线程是活的,是一个个不断接受任务然后执行的实体。<br>8 N+ L+ i- ^8 z6 X; \$ e) ~: E
可以看作在一个线程里不断执行获取任务并执行的Worker。</p>
7 \' J; N/ N# O$ ?, c<pre><code>struct Worker where( R1 b1 B; c, i! |+ u# e- y6 D. Y
{
) W- J' }- @3 z _id: usize, // worker 编号5 J7 O- g) C V) F7 X& Z# S+ E, t! b
}
, a* Z0 F. ^* L4 K+ |7 \5 ~</code></pre> Z; f7 }! t# V6 M
<p>要怎么把任务发送给Worker执行呢?mpsc(multi producer single consumer) 多生产者单消费者可以满足我们的需求,<code>let (tx, rx) = mpsc::channel()</code> 可以获取到一对发送端和接收端。<br>$ k) b9 K6 H$ q. R
把发送端添加到Pool里面,把接收端添加到Worker里面。Pool通过channel将任务发送给多个worker消费执行。</p>
/ H1 B" |9 U- N4 A( }% m! ]<p><strong>这里有一点需要特别注意,channel的接收端receiver需要安全的在多个线程间共享</strong>,因此需要用<code>Arc<Mutex::<T>></code>来包裹起来,也就是用锁来解决并发冲突。</p>
9 ^- G" c4 {- f. j. m0 U M7 k<p>Pool的完整定义</p>
, W% o) t$ D" L3 {2 Y, C<pre><code>pub struct Pool { e6 w! U6 U/ i& {1 i- G
workers: Vec<Worker>,% E7 ~9 ]2 D9 ^$ [) Z5 a5 z
max_workers: usize,8 t5 V b, b# e8 G& L8 S5 U0 P4 q
sender: mpsc::Sender<Message>( t& L# ] g G* [" ]: ?# E; B
}. V9 i2 e, n* x4 T$ T; s
</code></pre>
3 w" G, e4 j! C) e, [<p>该是时候定义我们要发给Worker的消息Message了<br>
+ s" a' T& A+ P+ x0 d4 {* v定义如下的枚举值</p>
9 y8 W% J1 z9 ~& v! _8 J<pre><code>type Job = Box<dyn FnOnce() + 'static + Send>;
6 R9 s/ d0 C9 h4 W% D8 U$ Cenum Message {# I# o7 j I3 x9 b" D6 j
ByeBye,# q1 C% N1 H- q4 [- }( {0 u! a
NewJob(Job),
$ E, n, z' L. t+ M# w; X9 \+ L9 K}7 w, r m" `7 \
</code></pre>
* K* M8 y* t2 G( h1 g3 A<p>Job是一个要发送给Worker执行的闭包函数,这里ByeBye用来通知Worker可以终止当前的执行,退出线程。</p>
! X% s9 ?2 B4 p: J/ x<p>只剩下实现Worker和Pool的具体逻辑了。</p>& o6 k8 Y( E4 ^% w$ t# k
<p>Worker的实现</p>' J9 k# F3 D! H, e
<pre><code>impl Worker) G" q! K5 }/ C
{
8 r! B. }/ v" y5 @ fn new(id: usize, receiver: Arc::<Mutex<mpsc::Receiver<Message>>>) -> Worker {
( E0 {) S4 \2 ^: \0 h let t = thread::spawn( move || {
" N! h+ L t/ A% o. ] loop {
( C5 H) E2 w( K* S# Z+ h6 { let receiver = receiver.lock().unwrap();
4 p' C" N5 t7 M# E% q1 } let message= receiver.recv().unwrap();
6 o5 c' i, @9 i5 h- X& [ match message {
. b; w+ e) X; Z Message::NewJob(job) => {
5 n) o; I# y1 X; T8 g- \ println!("do job from worker[{}]", id);
8 U# N! C2 C6 I0 m) v5 \* I job();
( i" V( G# j: k5 q4 X },
( G# ~: P5 d2 I) p9 r! g: { Message::ByeBye => {
# @8 e$ ^! ~8 L3 x# A7 } println!("ByeBye from worker[{}]", id);5 V/ @- q9 L2 O# M: e* F
break/ [6 j' t, G( A3 F0 J
},6 k1 A7 ?; {0 t
}
7 H- S: j+ z! j8 C9 D9 ] } l/ f; z5 y* L# }7 c6 a p% |3 o$ z
});! e r! C& O; H6 s+ E* [: q
/ F% j* N' h* S# R3 g, f
Worker {/ @4 k7 t2 c$ ~6 e( @! J6 Q
_id: id,& v3 W4 K" t& R* k& f1 F; h
t: Some(t),% d9 |" }8 t2 r0 v8 C0 U4 e$ C: E
}$ h, P* Z5 J$ [! T: w! e6 v t
}! o" t- l3 r. m0 z
}
p5 U7 h/ g9 B6 h, J8 t</code></pre>
1 ?; @# u" d3 K5 A! I8 I% \<p><strong>let message = receiver.lock().unwrap().recv().unwrap();</strong> 这里获取锁后从receiver获取到消息体,然后let message结束后rust的生命周期会自动释放掉锁。<br># n" L+ E) `. ?, A4 F7 \* n/ ?
但如果写成</p>
# b# G1 N+ A5 @5 k& `! i<pre><code>while let message = receiver.lock().unwrap().recv().unwrap() {
/ [& g) |" v6 p+ J* \5 `; r};
9 `# k- S, `" z</code></pre>
! e: w2 y G) P2 v! ?* D/ m: W+ I8 b' J<p>while let 后面整个括号都是一个作用域,要在这个作用域结束后,锁才会释放,比上面let message要锁定久时间。<br>. E" @3 ~0 j4 f1 @, h5 \
rust的mutex锁没有对应的unlock方法,由mutex的生命周期管理。</p>( g% m9 H. I* G" h& S* J+ S
<p>我们给Pool实现<code>Drop</code> trait, 让Pool被销毁时,自动暂停掉worker线程的执行。</p>: {" g9 G. O# E/ Z; a
<pre><code>impl Drop for Pool {; m+ y& a7 T6 K9 [' X
fn drop(&mut self) {
/ {0 s4 R8 d* C. s# ^; o' ^ for _ in 0..self.max_workers {0 @ c* y' R5 \* R n- r
self.sender.send(Message::ByeBye).unwrap();
- t% b+ N! v; S! L/ t }
, e9 M: x6 W; s* a for w in self.workers.iter_mut() {
* p) V; W' F3 v1 x" S if let Some(t) = w.t.take() {, n4 p! Q0 i; R' K8 T9 N
t.join().unwrap();
' B! J+ o- V+ [; @6 p |, t }
4 Z0 b4 o+ @% G$ `+ V( y. B }- d. w5 z% U" @
}; A) E# D" w+ w6 P' T
}
% i* j3 w3 o2 o+ J5 l; |- [2 w S6 h# T2 {% f/ y$ z
</code></pre>
( x8 N/ m& @. H0 ~+ y4 A8 I<p><strong>drop方法里面用了两个循环</strong>,而不是在一个循环里做完两件事?</p>
! d' A# A4 |3 y' t% t2 c+ N% G0 j# w<pre><code>for w in self.workers.iter_mut() {2 p6 a/ Y5 C z! \1 C- \9 @. ~6 P
if let Some(t) = w.t.take() {6 ?3 K( r5 [+ }1 |3 W# o# g7 w4 v
self.sender.send(Message::ByeBye).unwrap(); l7 f/ t. _+ k N$ T* b
t.join().unwrap();
2 y) P+ T& W& d/ Y5 {0 E1 X( A }& K; b0 c$ @8 ?
}, L( y, W4 G6 ]: F! I) Q
9 p: n) n! H/ A. L' V( J0 C9 ^</code></pre>
1 Y% l; h) f& C<p>这里面隐藏了一个会造成死锁的陷阱,比如两个Worker, 在单个循环里面迭代所有Worker,再将终止信息发送给通道后,直接调用join,<br>
8 @! e: F' a- o- o) r {' U我们预期是第一个worker要收到消息,并且等他执行完。当情况可能是第二个worker获取到了消息,第一个worker没有获取到,那接下来的join就会阻塞造成死锁。</p>0 K0 g. ~# P) ]$ X
<p><strong>注意到没有,Worker是被包装在Option内的</strong>,这里有两个点需要注意</p># L- A3 E! r! u$ }: v2 p5 w
<ol>4 d; o8 c& p& w' P+ k7 ?3 b; v
<li>t.join 需要持有t的所有权</li>; ~% T9 i+ }% t) W: P; `
<li>在我们这种情况下,self.workers只能作为引用被for循环迭代。</li>- c3 H* j# O. L
</ol>2 p2 |( y* G/ Q/ m# h! A
<p>这里考虑让Worker持有<code>Option<JoinHandle<()>></code>,后续可以通过在Option上调用take方法将Some变体的值移出来,并在原来的位置留下None变体。<br>
% L8 ^3 i. C" p5 F/ R9 Q换而言之,让运行中的worker持有Some的变体,清理worker时,可以使用None替换掉Some,从而让Worker失去可以运行的线程</p>
- U9 i" }" L& ]0 w$ z* \2 u$ C' y7 `<pre><code>struct Worker where1 |+ A! x4 P' F7 M# w
{
4 v+ x/ e0 o- a( V5 u P _id: usize,
# p: k( l: N& S* h t: Option<JoinHandle<()>>,
, s [- W, k* W+ G i, E1 P" Q: `}4 C, g0 W4 R! I% |
</code></pre>
* S8 L9 F- Q& O, a<h1 id="要点总结">要点总结</h1>
% c4 P' a( U# D; |6 D# f<ul>& r$ U; ~, z7 s9 G) E1 J2 C, {
<li>Mutex依赖于生命周期管理锁的释放,使用的时候需要注意是否逾期持有锁</li>$ C5 T, B8 @# ?( C$ t8 C0 Y1 b3 {
<li><code>Vec<Option<T>></code> 可以解决某些情况下需要T所有权的场景</li>
" ]: T8 @, G; G7 B9 @, ~+ K/ i% T</ul>8 t: h- _$ \9 Z7 P5 A
<h1 id="完整代码">完整代码</h1>
( H- D* t ~& z/ O$ x$ L<pre><code>use std::thread::{self, JoinHandle};. k' I0 [ { G- d- R. L
use std::sync::{Arc, mpsc, Mutex};
6 s4 h! j4 K7 R& o" d/ `
( d, y* ^- c, k* E& \. `$ s7 k) K4 U
type Job = Box<dyn FnOnce() + 'static + Send>;. g J( {5 u- T) G" Z& U" }; a
enum Message {0 O S" }! {1 v
ByeBye,$ @# m7 ~! u( p9 r9 F
NewJob(Job),
4 G9 X9 o$ y5 Q! O7 W} d3 B- `/ `# u$ x
$ e+ c5 i5 e, m9 _struct Worker where, M* g% r) \9 V6 [8 B% S9 x
{
$ x& y! R7 s0 Y; E5 y _id: usize,
. A$ u, o5 z- b' [3 J* Z& d8 X2 l t: Option<JoinHandle<()>>,
% }/ D$ ^% E5 E# T% X) t) a}
% }' y) n- g6 b$ d$ X5 z7 l
0 L; [& X7 q5 }8 Rimpl Worker! X( ^" K' \* ]: x& p& m) B2 x% h
{
+ R: @" ~8 q* W, ^ fn new(id: usize, receiver: Arc::<Mutex<mpsc::Receiver<Message>>>) -> Worker {6 M+ N* W4 D5 q) ^3 V* h
let t = thread::spawn( move || {
0 j) m5 ~( ^( U) }/ y/ _ loop {
3 @$ w2 l. u) S# u7 R% J& l/ F let message = receiver.lock().unwrap().recv().unwrap();' ? d& U2 G2 T# H
match message {5 [" m& Y0 @" Z
Message::NewJob(job) => {9 k* _/ K( o/ T& Q
println!("do job from worker[{}]", id);5 z9 K2 {; ~. i" v8 z. e5 ~
job();
# b7 {7 H1 b. Y% } },9 Y/ ?2 c9 y$ s; W) n1 ^5 Y8 I8 K
Message::ByeBye => {
0 C; s/ f4 B9 ~- Y V( s# J println!("ByeBye from worker[{}]", id);
0 _# F; a, Z3 Q, z5 _ break. M1 y% t. S9 a9 e3 w
},1 r# G$ ]- @ m
} 3 ?: Z( t5 \. ~5 y8 h
}
: Q# Q$ u2 u' J" s* E! C });3 c6 R& L% \% f1 d5 k( W$ |& j
, K0 D3 d9 P' ]4 W) `; s
Worker {
) N- U w( z4 |0 L* ]0 w _id: id,
& p- T1 Z2 A# K! n t: Some(t)," w3 ~$ L8 G8 I
}+ j( b4 y; H1 [
}
k, }* g9 R5 g! V}" m' j& f, c# I8 d+ U* \
8 J* g3 ]$ J5 e( g5 p& L# Zpub struct Pool {
/ m. ~6 {/ ^' O workers: Vec<Worker>,
6 M R+ K( D+ N1 N max_workers: usize,
& f% `, f( Z9 v+ U sender: mpsc::Sender<Message>% ^( L! C# B* s. F1 C+ Z
}
' l# l1 D& S# R4 s: Z7 Q- b, U+ `/ A1 T7 i% w7 c
impl Pool where {
: q9 V! Y3 q/ Z. S9 s# c pub fn new(max_workers: usize) -> Pool {/ L2 m& { Q7 \# w3 X; @7 U% t9 j
if max_workers == 0 {
$ i: u3 s6 M+ {% q; G panic!("max_workers must be greater than zero!")
5 `4 a; `. z0 V! K' S5 C }
h( z r1 \( X( b" v let (tx, rx) = mpsc::channel();
2 ^/ r8 L2 `* k( V- P$ w! h1 G8 u! ^0 o* }9 S0 t* Y- ^
let mut workers = Vec::with_capacity(max_workers);5 ^3 J2 I4 R" g1 L" P5 f6 a: s
let receiver = Arc::new(Mutex::new(rx));5 t# v) Y+ P8 f
for i in 0..max_workers { w; a. R3 n5 T P
workers.push(Worker::new(i, Arc::clone(&receiver)));7 T; @' X5 B. C' x( H+ d
}" D2 j) K! D. v9 a3 T7 _
5 r% N6 [1 u% y; I* r7 x- J
Pool { workers: workers, max_workers: max_workers, sender: tx }
2 j4 q4 `: `! ^: E# w# B }
# f7 r8 h4 }+ e: g% s+ |
3 ^9 i1 M" t" [ pub fn execute<F>(&self, f:F) where F: FnOnce() + 'static + Send. R* x5 n7 n7 E
{9 v5 j9 U& w$ y5 ~$ X% F
# `/ z N ^; W* ]- Y) H! X7 m let job = Message::NewJob(Box::new(f));
6 s$ {/ E& {+ {5 D( j) v self.sender.send(job).unwrap();
$ v1 L1 ~4 u+ D: u }
8 v$ W- k* Y% `}
+ ~+ e/ k9 T7 n. ]3 `' |7 `; ~, n3 T. T" [7 q5 u5 c0 q1 {
impl Drop for Pool {; _( t* D l0 P, m. e
fn drop(&mut self) {: e! B! H# y W
for _ in 0..self.max_workers {
4 g4 v/ E" o, X9 [ self.sender.send(Message::ByeBye).unwrap();3 M8 E- Y4 H0 C! h% H
}1 M2 a6 ~( E0 A* o
for w in self.workers {6 u" }6 C8 c& ^' J- X7 `7 f3 h9 M
if let Some(t) = w.t.take() {4 e4 v9 ~ i, W. f4 v5 A
t.join().unwrap();
0 \1 N" y, N+ c! O }8 d- L `3 s/ j+ l2 u
}3 w* K& w+ }# k5 v& Y6 f" u
}
- j5 Z& |; n, h% |} m. ^# H1 N; x5 _" x" i
/ u4 y$ P2 K) e+ ~ Y0 h
- F. c$ S+ D# m* i#[cfg(test)]4 B2 V+ p9 w# b( X
mod tests {
; w/ H& y0 a- N# a0 s# t, e use super::*;2 p. {& A$ u7 A; D0 a; N5 c
#[test]7 m" }% [4 b- G( c9 b
fn it_works() {/ p! d) [, Z7 Z( U" W/ N; n: Q. |- I( W
let p = Pool::new(4);
! z8 a0 o& [, s3 d* b p.execute(|| println!("do new job1"));
X* ]5 U3 a2 O p.execute(|| println!("do new job2"));3 I. w7 B9 g8 v, C, {0 |2 w
p.execute(|| println!("do new job3"));
3 x( ~9 F# G/ [7 o T p.execute(|| println!("do new job4"));
7 {- N n! m7 B+ }1 F, T }( z: m' e$ ]( b. r
}" A" X) t# \9 j" E( F+ z
</code></pre>" b; S9 f) b) Y; [3 J
+ q" o9 s0 u- X$ V7 S" A9 r, i, p |
|