飞雪团队

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

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

[复制链接]

9321

主题

9409

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

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

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) -&gt; Pool {}
1 f. i2 w4 j/ S& N" b! s( u  fn execute&lt;F&gt;(&amp;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&lt;Thread&gt;</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&lt;Mutex::&lt;T&gt;&gt;</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&lt;Worker&gt;,
/ n- U' R4 }+ n# I. f. b    max_workers: usize," N. w. z0 ^8 J9 n/ l) ]* i  _
    sender: mpsc::Sender&lt;Message&gt;
. 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&lt;dyn FnOnce() + 'static + Send&gt;;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::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; 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) =&gt; {: 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 =&gt; {# 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(&amp;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&lt;JoinHandle&lt;()&gt;&gt;</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&lt;JoinHandle&lt;()&gt;&gt;,
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&lt;Option&lt;T&gt;&gt;</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&lt;dyn FnOnce() + 'static + Send&gt;;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&lt;JoinHandle&lt;()&gt;&gt;,
) |( 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::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; 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) =&gt; {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 =&gt; {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&lt;Worker&gt;,5 h3 j! A, W5 @* p3 l( \
    max_workers: usize,
) n7 A: Y! k' b' e7 c8 {" _    sender: mpsc::Sender&lt;Message&gt;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) -&gt; 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(&amp;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&lt;F&gt;(&amp;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(&amp;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
回复

使用道具 举报

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

本版积分规则

手机版|飞雪团队

GMT+8, 2026-11-4 15:58 , Processed in 0.062384 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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