飞雪团队

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 20370|回复: 0

rust 实战 - 实现一个线程工作池 ThreadPool

[复制链接]

9321

主题

9409

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
30293
发表于 2022-2-12 14:35:42 | 显示全部楼层 |阅读模式

8 L2 @2 {6 ~& M& p7 F<h1 id="如何实现一个线程池">如何实现一个线程池</h1>2 r! [# _6 o. E
<p>线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,对于计算密集型任务,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。</p>
! B- o2 I5 Y9 n) A* @$ w<p>如何定义线程池Pool呢,首先最大线程数量肯定要作为线程池的一个属性,并且在new Pool时创建指定的线程。</p>
. @5 _! Q5 y% y: g7 @1 O) O<p>线程池Pool</p>
7 F" n7 U& u, ?7 h. W4 z2 B2 b<pre><code>pub struct Pool {% k4 I& O  ?2 ^
  max_workers: usize, // 定义最大线程数) P/ t- m$ u  A' d4 r0 j0 [1 n
}  ~( @" E$ P7 `4 E* l* O; z

: L% G' G9 Q: C* fimpl Pool {
' {* v" d) _: h4 |  fn new(max_workers: usize) -&gt; Pool {}
# X. v: n& I6 r  fn execute&lt;F&gt;(&amp;self, f:F) where F: FnOnce() + 'static + Send {}$ t% d8 ?- r9 f- y7 |& V- B
}8 T9 m6 c) y  o1 `9 S
* s- t/ ]4 _1 b1 d, g9 D
</code></pre>. G0 o, o5 ?2 X! Y
<p>用<code>execute</code>来执行任务,<code>F: FnOnce() + 'static + Send</code> 是使用thread::spawn线程执行需要满足的trait, 代表F是一个能在线程里执行的闭包函数。</p>' L6 j8 C4 g' x6 u7 p1 Z9 h
<p>另一点自然而然会想到在Pool添加一个线程数组, 这个线程数组就是用来执行任务的。比如<code>Vec&lt;Thread&gt;</code> balabala。这里的线程是活的,是一个个不断接受任务然后执行的实体。<br>) a" h' _% n! J; h& R2 k
可以看作在一个线程里不断执行获取任务并执行的Worker。</p>
# Z! w; S/ k: u- L% ^2 x) W<pre><code>struct Worker where2 H3 G/ F) T6 I* H  H
{% `4 h9 U  U/ k$ k% s1 s) e- g
    _id: usize, // worker 编号4 R3 f; Y& a7 A1 g  \1 q
}& ^+ v( h2 `4 `  \4 s
</code></pre>
* @2 R- l& S) ]<p>要怎么把任务发送给Worker执行呢?mpsc(multi producer single consumer) 多生产者单消费者可以满足我们的需求,<code>let (tx, rx) = mpsc::channel()</code> 可以获取到一对发送端和接收端。<br>( f+ w% X- G2 J* T
把发送端添加到Pool里面,把接收端添加到Worker里面。Pool通过channel将任务发送给多个worker消费执行。</p>' w( ^, c; _: e/ A' ]. |& l
<p><strong>这里有一点需要特别注意,channel的接收端receiver需要安全的在多个线程间共享</strong>,因此需要用<code>Arc&lt;Mutex::&lt;T&gt;&gt;</code>来包裹起来,也就是用锁来解决并发冲突。</p>
9 Q9 Q1 F4 B! w4 ~- @<p>Pool的完整定义</p>1 n6 v( ^. K6 g" @) E( W6 R
<pre><code>pub struct Pool {
5 P6 C4 y% f2 I) c7 Y- L    workers: Vec&lt;Worker&gt;,, G% J  |, F5 w3 L! r" [
    max_workers: usize,
* @# h  K$ ]* S2 c    sender: mpsc::Sender&lt;Message&gt;
  g  F0 u& h' D! o6 p! e}& M! l$ L/ t3 W  a2 E4 c6 G
</code></pre>4 F9 A+ Y; n3 ~7 Z  a' y8 U2 _2 L
<p>该是时候定义我们要发给Worker的消息Message了<br># w( H' W' R% {" _( Z- L1 W
定义如下的枚举值</p>+ C( j9 F: Y! }5 C  k5 b  G: Y
<pre><code>type Job = Box&lt;dyn FnOnce() + 'static + Send&gt;;( l9 d# B; Y6 K  I+ T
enum Message {
2 D& s1 y* v2 ^" `    ByeBye,
: ^* K* e8 i+ j9 z3 b    NewJob(Job),
" x- }% k/ o( Z2 S$ m8 D}6 e: X5 E6 u. f! F
</code></pre>
1 k7 P1 q, i" J<p>Job是一个要发送给Worker执行的闭包函数,这里ByeBye用来通知Worker可以终止当前的执行,退出线程。</p>8 R+ e0 P& H2 t! v( k% c
<p>只剩下实现Worker和Pool的具体逻辑了。</p>7 e1 b+ _) a8 C5 D; O2 }, \3 [9 s
<p>Worker的实现</p>' P9 h$ X8 j, ^' T5 t6 D
<pre><code>impl Worker/ l* b8 f6 i' Y5 r7 c& F$ L
{( W1 X5 }" O  t, |/ G
    fn new(id: usize, receiver: Arc::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; Worker {
4 R" B* y( I. @# ]% X  C& z        let t = thread::spawn( move || {9 \( [8 j. c; X+ y" J1 m4 l5 I- g5 C
            loop {/ Y# c6 @8 h2 D3 N+ Y' B& E3 i
                let receiver = receiver.lock().unwrap();
9 _; Y# c9 Z% N+ t3 @- ?  v                let message=  receiver.recv().unwrap();; z& Q. [, `+ L* X  |3 U
                match message {
. [3 N% L& c, ^: l5 ?                    Message::NewJob(job) =&gt; {2 V$ @8 n- @. ^6 q( U4 e( P* A
                        println!("do job from worker[{}]", id);
6 z; `  J( b  p5 p& o                        job();
: n. q2 ~% B" G& L1 K                    },+ L& ]1 e$ j, ]& a8 ~" f$ i
                    Message::ByeBye =&gt; {
4 R% Y3 K8 O0 Z                        println!("ByeBye from worker[{}]", id);
! @: L5 E4 v/ J" z) i# |                        break/ F! U; t# r5 j2 d8 O& h- {
                    },
; D' C& S5 a# x, X8 R                }  
- q6 ]. ?% D0 j# y: C. f            }" m  H, x. K, U4 P5 v) k
        });
6 T0 X/ e! `* Z0 E" h; P0 E% k& e; a) l& T' v8 y
        Worker {
; l* @. o" f5 v6 q7 S+ Q            _id: id,
. k% A' w5 y8 ~7 H) [+ {8 J! F0 d  c            t: Some(t),
9 i+ k; }+ ~: j7 P& u        }2 R" i9 o1 A# B& H2 \( T, F# b
    }8 p7 J/ P5 e8 `
}! K; d- p! r. q- ]' w
</code></pre>" x0 B2 x/ Y; m4 I+ d% g* q
<p><strong>let message = receiver.lock().unwrap().recv().unwrap();</strong> 这里获取锁后从receiver获取到消息体,然后let message结束后rust的生命周期会自动释放掉锁。<br>" S/ {: [% g. @1 W( n; A3 |
但如果写成</p>
  }8 u( P" W! ^" h<pre><code>while let message = receiver.lock().unwrap().recv().unwrap() {1 T* C3 q# B2 E+ l$ x6 e" O+ b+ x
};
: h* y7 R6 w9 V, F% o) N; |</code></pre>8 W3 D. X3 Q0 m0 X" d7 ~
<p>while let 后面整个括号都是一个作用域,要在这个作用域结束后,锁才会释放,比上面let message要锁定久时间。<br>
! T+ S  |& b& L/ Frust的mutex锁没有对应的unlock方法,由mutex的生命周期管理。</p>
0 H+ O, r2 Z% ^3 \. N+ j<p>我们给Pool实现<code>Drop</code> trait, 让Pool被销毁时,自动暂停掉worker线程的执行。</p>
- \& A0 o  E* K5 V<pre><code>impl Drop for Pool {
9 t+ v7 X/ p' Q1 A4 _    fn drop(&amp;mut self) {
" n& e8 e5 i% ^        for _ in 0..self.max_workers {
# p) u, D- J# b4 A            self.sender.send(Message::ByeBye).unwrap();" M% B- v' b) V
        }2 u( H+ b$ V1 G4 P: z8 Z
        for w in self.workers.iter_mut() {
5 Q4 E% a+ i  V" h8 e, o: _+ u1 e            if let Some(t) = w.t.take() {
! j9 M2 f# q. h1 {                t.join().unwrap();5 \8 r2 c# e8 q! ?# B8 ~- p
            }$ Z) }3 i% u" V& a; N
        }
: q* `) @- U& k( K0 t9 U    }
* C9 o* X' d1 s# r% w2 [& W5 |}1 B6 Y0 e3 p. A3 w
4 |8 k1 G% G; T) D  z
</code></pre>
. Y; D: F  D- P. V& L<p><strong>drop方法里面用了两个循环</strong>,而不是在一个循环里做完两件事?</p>
. o4 r, {3 V8 L: }<pre><code>for w in self.workers.iter_mut() {
5 ?3 T- F& b4 K% \$ ?& E( g: l$ i    if let Some(t) = w.t.take() {
4 B3 m& c: t6 Q9 Y- d        self.sender.send(Message::ByeBye).unwrap();
8 g/ h  m) d/ M. H4 D7 a& F" G        t.join().unwrap();" O/ d- M5 M) i, p! x
    }5 I0 F3 u( I$ p3 g
}5 \; ?" u7 D/ X# C0 E) N

1 c- @* O( z( Y  D; A</code></pre>0 }& l+ U: K6 s- p7 P
<p>这里面隐藏了一个会造成死锁的陷阱,比如两个Worker, 在单个循环里面迭代所有Worker,再将终止信息发送给通道后,直接调用join,<br>$ f6 g7 i- @0 }* ^- z  _0 n
我们预期是第一个worker要收到消息,并且等他执行完。当情况可能是第二个worker获取到了消息,第一个worker没有获取到,那接下来的join就会阻塞造成死锁。</p>4 w0 r; M. |- X8 H4 d+ b# }
<p><strong>注意到没有,Worker是被包装在Option内的</strong>,这里有两个点需要注意</p>
% @! t+ O( X) I<ol>
% O+ _  z! S! _) \- J& f; {<li>t.join 需要持有t的所有权</li>. y% h0 J4 R1 i& b- m. e1 o
<li>在我们这种情况下,self.workers只能作为引用被for循环迭代。</li>  G6 |: |- P. n8 ?4 B5 w
</ol>
( n% q, d/ O6 c' @  ]5 x<p>这里考虑让Worker持有<code>Option&lt;JoinHandle&lt;()&gt;&gt;</code>,后续可以通过在Option上调用take方法将Some变体的值移出来,并在原来的位置留下None变体。<br>
! }1 W) y; p# r) m* I/ o换而言之,让运行中的worker持有Some的变体,清理worker时,可以使用None替换掉Some,从而让Worker失去可以运行的线程</p>) h7 n! w3 o' G$ ]! c1 O
<pre><code>struct Worker where
4 A9 X8 c/ K+ P  i. d$ W0 g( z{
  K' T. k3 o+ N3 b2 v! X3 O    _id: usize,' j8 A7 I. C- f) i
    t: Option&lt;JoinHandle&lt;()&gt;&gt;,
* Y3 N6 c$ J# C/ V7 p) }) N3 E}* N5 A/ |6 b# T
</code></pre>- z1 \3 v# m4 }
<h1 id="要点总结">要点总结</h1>( C* `% \1 _: n  L
<ul>% s7 R( K: |1 r7 l
<li>Mutex依赖于生命周期管理锁的释放,使用的时候需要注意是否逾期持有锁</li>& U9 b2 S& u9 b9 ^
<li><code>Vec&lt;Option&lt;T&gt;&gt;</code> 可以解决某些情况下需要T所有权的场景</li>- P$ l2 N' D  w
</ul>: i. f. U3 z2 }! W# P
<h1 id="完整代码">完整代码</h1>* M. z8 l$ U) o8 \$ K" U
<pre><code>use std::thread::{self, JoinHandle};0 g) r( C& Y2 j, m: I* n8 Q( c
use std::sync::{Arc, mpsc, Mutex};0 W( s  e7 V" T% F2 O
3 k) F7 g; q; n

$ |" G  R) t6 W) z. _4 n) k) Z2 ~type Job = Box&lt;dyn FnOnce() + 'static + Send&gt;;
3 b2 K% _' X! W: |# ~5 z$ H  Tenum Message {
8 P9 Y) T% U7 C* c$ `$ d4 S! A    ByeBye,
6 E3 @( z4 j$ L- E3 M    NewJob(Job),/ V% U1 f. G! u
}
: }% o- s+ c1 X2 `3 g6 w( A) |# d' @" R' E
struct Worker where. H* I# G# o9 {" ~& M( a) g2 X! L8 s
{
# D" w, d( o) u    _id: usize,
# ]( o( K* A1 z% v; r9 k. t* ^    t: Option&lt;JoinHandle&lt;()&gt;&gt;,
+ q4 y/ [: T' Y}6 A& e1 O5 h' t3 ]; D$ x

$ \5 U3 N* H  s2 _1 t1 f( n6 ]* @4 Gimpl Worker* @& r. [; F# }5 a% A
{
- o# R. k; ^& _3 c% Q6 d& u    fn new(id: usize, receiver: Arc::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; Worker {
0 x3 D( G/ B7 x  n& V: k! H9 r5 `        let t = thread::spawn( move || {5 f( F( B( s& E& q0 p
            loop {- \  O2 m) |/ X% M
                let message = receiver.lock().unwrap().recv().unwrap();
5 n: S( X% _" W* @; {- e- H                match message {: s1 n: V$ ]0 Y1 R
                    Message::NewJob(job) =&gt; {
3 B0 g7 P( K. b/ ^3 y' Q                        println!("do job from worker[{}]", id);' Q6 {7 H: P% t' [; P
                        job();$ s1 N( D2 d) j, J, R
                    },
/ m, y4 m/ y3 ?+ a                    Message::ByeBye =&gt; {
$ d6 e) _- Y; ~7 k  U3 Y                        println!("ByeBye from worker[{}]", id);
8 F+ |( g9 w8 h1 N  E                        break; _2 {$ u+ E7 g$ {$ t
                    },/ b) C/ S, w. z5 r
                }  2 K: ~/ `) ^0 J, b
            }
/ Q& l$ \% n  z$ _1 I3 d. s        });
$ k6 y4 }0 A5 B5 V5 a
/ i" k. P& k% k% ]' M8 q+ K, p        Worker {/ H- I; Y% X3 \1 g" o
            _id: id,0 K( b7 _1 [/ X8 Q- J: Z( H  T6 U/ ^
            t: Some(t),
7 W# n# @8 I6 c& H6 h        }- b# q+ ?3 K- j  E- Y9 l/ ^
    }
7 e% \; H& O# _, k# [+ [! I, ?}
. o  o* Q# \) L' l/ @! `2 m$ q1 |. \9 R: J
pub struct Pool {- }6 [8 i' V, n
    workers: Vec&lt;Worker&gt;,7 P) T; @! q0 W- Q
    max_workers: usize,6 x$ s; y+ P4 P
    sender: mpsc::Sender&lt;Message&gt;
. t1 ~- `* V/ t' Z) y0 e! t7 b: r}
! f6 y! l4 N4 J) e, ]
4 X2 Z4 r6 Z- Jimpl Pool where {
8 Z' q, v( I$ j+ i& l    pub fn new(max_workers: usize) -&gt; Pool {' D/ l+ C. |4 H# d* t! D
        if max_workers == 0 {' Z8 k, d* X/ a6 Z3 @7 }5 V
            panic!("max_workers must be greater than zero!")# w& f7 U# Y$ n) o& g4 [: T  R5 u
        }6 D) ]1 H4 W; j' K9 g
        let (tx, rx) = mpsc::channel();
% G8 q( U5 V: t( ]' m: z- _" P) O
, ?  a- n+ g! U8 k  n5 ?        let mut workers = Vec::with_capacity(max_workers);% b1 F/ M* Q2 _2 x. p: p; F
        let receiver = Arc::new(Mutex::new(rx));& o" o& J4 m! d
        for i in 0..max_workers {
% D- d# T9 Y2 G$ b9 p& d8 C# v            workers.push(Worker::new(i, Arc::clone(&amp;receiver)));9 o# ?- i. m+ u: Z9 s( s1 N
        }; G; ?+ I' K+ m

- w" V3 F3 ~  a0 j3 |  t) q$ l! J& z        Pool { workers: workers, max_workers: max_workers, sender: tx }
: [& [7 T( x' I4 N, x    }
" s1 t9 \4 D2 A! `0 S2 g' l    # y2 r( ~7 |  d+ S* `
    pub fn execute&lt;F&gt;(&amp;self, f:F) where F: FnOnce() + 'static + Send
6 d% b7 d5 i" ]& _    {- y1 _$ x! o. \$ g( d/ K* `8 \% b

  ^; _4 T) ?3 O. ]7 H/ ^        let job = Message::NewJob(Box::new(f));
+ ]) `$ a- H7 Y9 m        self.sender.send(job).unwrap();
, x9 W5 S7 |7 C) L; s7 N4 b8 N    }, y( m2 B8 q" N( {% Q
}5 ~5 l; ^: U+ j8 \' R8 p% d, O

3 N3 z; ]; l9 |- L5 t& f: Uimpl Drop for Pool {
3 z: z# ?0 _5 `. P: u9 d* o    fn drop(&amp;mut self) {
( h  \6 s' Q$ ^* e2 _        for _ in 0..self.max_workers {( y/ I/ e" c1 I  M3 w2 U& L
            self.sender.send(Message::ByeBye).unwrap();
0 z2 P/ f, s$ z) F9 v: c        }
( L: ^" Z1 n. e# n        for w in self.workers {
5 `( D3 o$ f5 O; A; Z            if let Some(t) = w.t.take() {
' {7 Y, F( J" D; w6 l; D* k$ f                t.join().unwrap();& A6 u. X; F; u0 g
            }
. ~9 B0 v  L9 j  X; z$ E        }
/ z! d2 K9 F  ]9 ~. _7 P. a+ Z    }
- @6 A6 b6 A7 ], i7 i8 @}  d. w, U2 R! R3 E$ ]7 q4 [
' @8 \( l) d% H( w: X) P' \: X

' ?4 l& @9 b; q' _#[cfg(test)]( f  k# Z+ [: B; L8 O. a4 i
mod tests {8 j; X5 _" C7 f0 ]$ t6 O
    use super::*;) I0 K! y( z! a( X, P+ ^  S5 O
    #[test]
, a5 [7 W+ A; i+ b4 w    fn it_works() {
& Y+ F6 ?7 i* a* N# ]( Y3 L/ X        let p = Pool::new(4);
, H, {2 l8 [$ ~' h- E+ p        p.execute(|| println!("do new job1"));
0 \- Z# d, X! [7 M! N& f8 x        p.execute(|| println!("do new job2"));. N- O, v2 l0 h) y: H
        p.execute(|| println!("do new job3"));% l" M* ]7 p& D5 P1 w" \0 Z
        p.execute(|| println!("do new job4"));
7 p" Q2 |0 f% l& @7 @    }3 R( T4 ^1 m" D" w/ ?" ^
}/ K8 Q! r: T- _3 u
</code></pre>0 j- R  T4 n3 D- A

1 z( t$ s$ u9 G& y) k* T" y
回复

使用道具 举报

懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|飞雪团队

GMT+8, 2026-11-4 15:59 , Processed in 0.067620 second(s), 22 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表