飞雪团队

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

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

[复制链接]

9321

主题

9409

帖子

3万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
30293
发表于 2022-2-12 14:35:42 | 显示全部楼层 |阅读模式
% q  X8 W& L  ]* U( Q
<h1 id="如何实现一个线程池">如何实现一个线程池</h1>
/ N% T+ m$ T  g5 {' t& o6 W<p>线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,对于计算密集型任务,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。</p>
- g* Y$ }  x# y9 V<p>如何定义线程池Pool呢,首先最大线程数量肯定要作为线程池的一个属性,并且在new Pool时创建指定的线程。</p>, S5 x6 `% y* ~0 e  Z
<p>线程池Pool</p>
. l$ A+ R4 S- C7 Y" m1 w: k' F% [<pre><code>pub struct Pool {
- ?; G( d+ k- i6 E( z6 f8 q# I0 t  max_workers: usize, // 定义最大线程数
2 J( {5 X9 J, K# S- B# s/ l}
6 d) G  Q4 g* V2 l+ ~8 V: _6 b; C" F! K* n2 \! F0 h
impl Pool {: W: @# `* K, O5 L4 S2 D# A
  fn new(max_workers: usize) -&gt; Pool {}
# B: s# l2 y1 e  fn execute&lt;F&gt;(&amp;self, f:F) where F: FnOnce() + 'static + Send {}
" ]3 H7 d* V. X7 K* K' f}
- f) ?( m5 Z, \: v# j, p
! C# J% N) {9 X0 f, _. A8 u</code></pre>
0 q+ p# i6 ~2 \/ p" v<p>用<code>execute</code>来执行任务,<code>F: FnOnce() + 'static + Send</code> 是使用thread::spawn线程执行需要满足的trait, 代表F是一个能在线程里执行的闭包函数。</p>$ H& D$ }' X' D+ I( ~
<p>另一点自然而然会想到在Pool添加一个线程数组, 这个线程数组就是用来执行任务的。比如<code>Vec&lt;Thread&gt;</code> balabala。这里的线程是活的,是一个个不断接受任务然后执行的实体。<br>
2 k3 h* d- K  e0 g可以看作在一个线程里不断执行获取任务并执行的Worker。</p>
# b5 k6 x2 Q& H" e9 [2 e$ F7 A9 ^<pre><code>struct Worker where
9 K% _) Q, w' {{- j# U8 ?$ `3 M+ C. X" |; m3 V
    _id: usize, // worker 编号
; g3 G1 n- |# P9 `0 h3 u}: |* U& V! \( }
</code></pre>
$ B* T1 A3 E) B; o, Q' J0 {6 A# I<p>要怎么把任务发送给Worker执行呢?mpsc(multi producer single consumer) 多生产者单消费者可以满足我们的需求,<code>let (tx, rx) = mpsc::channel()</code> 可以获取到一对发送端和接收端。<br>* Y1 S0 @$ |% E; {) s6 t- D
把发送端添加到Pool里面,把接收端添加到Worker里面。Pool通过channel将任务发送给多个worker消费执行。</p>. G) ^% A) p! Y8 A" j
<p><strong>这里有一点需要特别注意,channel的接收端receiver需要安全的在多个线程间共享</strong>,因此需要用<code>Arc&lt;Mutex::&lt;T&gt;&gt;</code>来包裹起来,也就是用锁来解决并发冲突。</p># s4 \3 t  l, ?/ j  O& V
<p>Pool的完整定义</p>
) V7 `! b# H0 N: x! L7 c9 L<pre><code>pub struct Pool {8 |1 |" A( g% A; w- U* K
    workers: Vec&lt;Worker&gt;,
  P- O0 x% M0 Z" v; V# V    max_workers: usize,
+ J; m: }$ r8 P4 Q% p    sender: mpsc::Sender&lt;Message&gt;1 _1 V0 \! Q% ]9 L3 v" |3 r8 _, v% P
}% U6 M- K# n- s/ r5 R
</code></pre>
: q7 d' p! t; ^3 V% b! }) v<p>该是时候定义我们要发给Worker的消息Message了<br>
9 ]) G0 _" X* Q% T定义如下的枚举值</p>6 a8 o2 a. _) `- Q: j- i6 Q7 F3 B9 d
<pre><code>type Job = Box&lt;dyn FnOnce() + 'static + Send&gt;;
- v7 h! k1 n' x6 ?, _+ Penum Message {; D3 r6 a% S/ s6 v0 k0 I+ i5 ^3 a
    ByeBye,
' ~- B0 B5 F- ]5 I$ L1 |    NewJob(Job),
4 C% ]7 r$ }  S2 o9 y}9 G/ P2 l- u/ [
</code></pre>
% G7 c) k2 a" s' ^' s  `<p>Job是一个要发送给Worker执行的闭包函数,这里ByeBye用来通知Worker可以终止当前的执行,退出线程。</p>
) \: R! h5 J8 r& V$ e5 k<p>只剩下实现Worker和Pool的具体逻辑了。</p>5 N. r3 E9 E8 j8 ^( z
<p>Worker的实现</p>
& l, i8 i) {( ^0 l% z! u& E% \<pre><code>impl Worker
' I4 m# n( p7 ~+ b' o! S{. }4 U9 A- v% s5 l- x8 U
    fn new(id: usize, receiver: Arc::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; Worker {
0 f3 j4 a1 A4 u        let t = thread::spawn( move || {4 l3 ]6 j- K8 F' `2 ]  p
            loop {
( X- ]4 j( `4 ]+ R6 V                let receiver = receiver.lock().unwrap();
3 M; U6 A; g2 N2 f7 q# a* ?                let message=  receiver.recv().unwrap();
8 h- `. g5 a; J% i0 q9 i% c                match message {; O. S  c: n' g1 P+ c+ a$ w1 ^
                    Message::NewJob(job) =&gt; {
% C8 a0 ~3 n6 x/ ^1 J                        println!("do job from worker[{}]", id);+ D) N, @$ w- ~, U0 x
                        job();
* h- }, F% x5 @2 O6 k                    },: f. e/ e5 G4 `. Z& j) ~: M
                    Message::ByeBye =&gt; {
/ U! ~& {" b( L# A% K6 y                        println!("ByeBye from worker[{}]", id);9 K$ z% T, {/ H+ T' E, j$ K
                        break9 M2 @! i7 b& ]* V2 K
                    },6 b( P6 S( Q. b& f
                }  
: A7 r3 F. B  v& ^            }
: G& E2 v& V9 c5 C) ?3 }& s% a        });. |5 v7 `  @5 s2 n+ G) W

6 @9 u1 _" p: {: p. [% S        Worker {
7 d# D9 L1 s7 X+ h" u            _id: id,/ b' ~' k/ M3 K& w% m# J
            t: Some(t),& f" g( }$ w9 Y; E- u
        }- B/ S8 h  R' J5 g! I
    }
1 r" K: n. @) ]- ~}
! w% M' j7 y# ^6 y& U& \9 a</code></pre>; i" I/ Q5 c4 a/ f( n
<p><strong>let message = receiver.lock().unwrap().recv().unwrap();</strong> 这里获取锁后从receiver获取到消息体,然后let message结束后rust的生命周期会自动释放掉锁。<br>
5 w( Q/ ?+ w: m: D但如果写成</p>
/ j4 \9 J  p2 H7 B2 y<pre><code>while let message = receiver.lock().unwrap().recv().unwrap() {" F6 s" r. Y8 S* |! e
};9 k9 C1 O+ c: o- y
</code></pre>
5 N' A2 T& j" b9 n<p>while let 后面整个括号都是一个作用域,要在这个作用域结束后,锁才会释放,比上面let message要锁定久时间。<br>
$ Z( t# w0 ]& T6 u8 D- |( u2 h5 jrust的mutex锁没有对应的unlock方法,由mutex的生命周期管理。</p>
, _" T; W/ Q6 _<p>我们给Pool实现<code>Drop</code> trait, 让Pool被销毁时,自动暂停掉worker线程的执行。</p>4 U0 o* g+ [1 H; N7 c* G5 A+ }
<pre><code>impl Drop for Pool {
8 H3 B) l$ Y. V% V( P+ X- k    fn drop(&amp;mut self) {
, X9 b' \2 b1 b5 p) K" Q        for _ in 0..self.max_workers {3 Y6 y( s$ W# i+ o# W
            self.sender.send(Message::ByeBye).unwrap();
2 h5 v* J: F3 q        }* D  D2 |" k6 v7 Q% Q0 g
        for w in self.workers.iter_mut() {  ~3 q4 G2 m& M
            if let Some(t) = w.t.take() {, J$ C6 c" u3 ]( _. `
                t.join().unwrap();
, V$ I, `' S! ]1 G/ N            }' |* I0 S: d1 Y2 B9 s0 W- m4 A
        }
0 @4 I) L  Y- ?    }
; D" |6 }0 g, x3 g! e}. T5 m# b" b0 D. {) g) t% ]$ \
* q- F3 C2 f% Z& b
</code></pre>- v) m) j0 s$ ^2 _" d
<p><strong>drop方法里面用了两个循环</strong>,而不是在一个循环里做完两件事?</p>( z0 f, [" o& r( t; S- z9 @" R
<pre><code>for w in self.workers.iter_mut() {
; H& s7 l1 K/ O2 r- |" D2 W    if let Some(t) = w.t.take() {3 r1 I5 D% E* z0 h; V( {8 ~8 w8 V
        self.sender.send(Message::ByeBye).unwrap();
4 X: Y5 C6 _- D4 O" B        t.join().unwrap();
# {% ~3 ~4 {; M; H% @3 Q9 z- k    }
' f  M& }; C+ z; ~' a1 P}# C  e# {% W1 q5 G, j9 i
: `! W# i. c! S4 d0 p
</code></pre>
" c- x9 ~) R9 s. Y" f. O: a<p>这里面隐藏了一个会造成死锁的陷阱,比如两个Worker, 在单个循环里面迭代所有Worker,再将终止信息发送给通道后,直接调用join,<br>
) n, b  S, ~$ X& M( \我们预期是第一个worker要收到消息,并且等他执行完。当情况可能是第二个worker获取到了消息,第一个worker没有获取到,那接下来的join就会阻塞造成死锁。</p>9 M* `( [" h  p* {5 U/ u# @: ?
<p><strong>注意到没有,Worker是被包装在Option内的</strong>,这里有两个点需要注意</p>* o5 G# |: a4 @! l! ~
<ol>4 K5 W1 p% f" i* C
<li>t.join 需要持有t的所有权</li>
  w' ~5 [+ e* i. {<li>在我们这种情况下,self.workers只能作为引用被for循环迭代。</li>
2 Y/ {2 e5 _" \" \( K: G( e$ ^</ol>6 ~/ Q  T7 C5 I/ b
<p>这里考虑让Worker持有<code>Option&lt;JoinHandle&lt;()&gt;&gt;</code>,后续可以通过在Option上调用take方法将Some变体的值移出来,并在原来的位置留下None变体。<br>; i5 Z+ F0 r$ r( j2 B
换而言之,让运行中的worker持有Some的变体,清理worker时,可以使用None替换掉Some,从而让Worker失去可以运行的线程</p>- q' @3 n$ ^- n% p" a$ t4 g& b/ d
<pre><code>struct Worker where* @% M: G: _. R* m  M$ g9 Y
{5 |/ q" O: {$ [2 h
    _id: usize,
* D. k* l: e4 k' C3 |, H# d    t: Option&lt;JoinHandle&lt;()&gt;&gt;,
- I* T3 y5 o' o# `}
" p9 I  a1 ~9 C* x4 F</code></pre>3 r. E0 d! m, _& Z
<h1 id="要点总结">要点总结</h1>6 y* g1 j! H+ Y6 w% y" R* p
<ul>
0 P% v* i; f9 l! P3 ^% Z<li>Mutex依赖于生命周期管理锁的释放,使用的时候需要注意是否逾期持有锁</li>
, Q5 o! N6 ^8 ^* o( j6 _8 q4 V* U' r) B5 A<li><code>Vec&lt;Option&lt;T&gt;&gt;</code> 可以解决某些情况下需要T所有权的场景</li>
+ ]2 U2 {" x6 \2 ?</ul>
( X. m  Z/ _1 [- z0 o<h1 id="完整代码">完整代码</h1>9 Y# y+ Y+ _. g- G. L; u; a
<pre><code>use std::thread::{self, JoinHandle};9 [3 Q( y: v& J$ I
use std::sync::{Arc, mpsc, Mutex};8 S7 b0 F7 n8 _+ r* h! r2 ^

6 B4 Y$ c! ^! K- E  z) W1 m: m8 l* X" C3 k) c7 ]0 W
type Job = Box&lt;dyn FnOnce() + 'static + Send&gt;;
" K+ s) V$ Y- q0 Kenum Message {
$ k5 G/ g* N- c. h4 B    ByeBye,4 L$ |5 F" H. o
    NewJob(Job),
7 _; Z  Z4 b3 O5 S: I}' s9 r2 U! \7 a5 B" p3 M, V$ @

/ `( D: _% Z* S# @$ {- q, m$ L5 Ostruct Worker where
" D( r# I$ W, a1 y, |& P! t{/ A' h# v" o" E7 D4 [% ^% X3 s9 u  A1 Y
    _id: usize," x' j% k, l/ H
    t: Option&lt;JoinHandle&lt;()&gt;&gt;,0 v. E" x1 N! Q, w6 V9 R
}
( l# ~+ h9 C/ G: }: D6 b  e# y( J1 ]0 S
impl Worker" g( N0 {$ o4 s, {% k3 t7 x
{8 C. Z7 `+ l7 ?; h- t+ x
    fn new(id: usize, receiver: Arc::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; Worker {; \7 d% j" }, v" x9 f
        let t = thread::spawn( move || {; T$ }# X, P7 ^0 V( s5 ?, p9 q
            loop {: z/ |" _) o* s
                let message = receiver.lock().unwrap().recv().unwrap();/ F2 N$ q, `  U9 i3 G' H- a8 y, W
                match message {
7 U; f' H: M: ~8 d  U$ L' t                    Message::NewJob(job) =&gt; {' A' q% Q$ c4 Z8 h  v5 Y- D
                        println!("do job from worker[{}]", id);! |" L( G' V& J
                        job();
" F9 V) o( r- ]& R5 S- Z& d                    },6 x) h2 v: i; H
                    Message::ByeBye =&gt; {" ^6 b# `. u* `! M4 r
                        println!("ByeBye from worker[{}]", id);
/ S9 y/ y! g+ N* P9 O                        break: `9 C/ E. g1 l
                    },
5 Y, ?$ h4 V0 o9 C                }  1 f' Z. Y* U: j/ g; X" |
            }
1 W8 f- V. a, m* I; x2 }: Q+ `# V8 W        });# P+ `0 \' [+ O) P* b% t3 f2 Z% a

; Q5 L7 m# b, {& U3 p0 \* j+ I        Worker {
* Z* t7 `- ]- u/ J% e1 _            _id: id,
. r, {% J1 V3 J- E0 ~3 Z3 n            t: Some(t),
4 f+ I& G' h+ V        }
6 o9 _! d- _- X9 T' d) K    }
: K- D( p0 S( X}
$ \" w7 ^: o; ~- W% i. X6 Q3 ^- ?' u, G7 b, `0 x* t9 g. P) g6 j
pub struct Pool {
1 c8 b# k- @  I9 Y" f0 P/ A* Q) ^    workers: Vec&lt;Worker&gt;,
( C, M1 X1 g% |- E" x0 {    max_workers: usize,* }; s- j/ P- A; t, k
    sender: mpsc::Sender&lt;Message&gt;; g! @' d& T$ x4 P: E% H4 u/ W
}
4 B0 H0 D; L- M0 M0 l: O4 n
7 H, j, N0 H: s; c0 Y) D$ w! ^impl Pool where {1 E4 s" p& I; X3 P9 o+ U
    pub fn new(max_workers: usize) -&gt; Pool {
: n2 x7 Q- {1 o3 M        if max_workers == 0 {
) K9 O! k" w; v4 b            panic!("max_workers must be greater than zero!")
: W5 Z! n2 {* ^+ ^9 y) V        }/ F" y9 K, ~' T& @3 j: t0 t
        let (tx, rx) = mpsc::channel();, M- \" u& x7 b6 ]( m2 b
( K! ]& i8 p; U! {9 z
        let mut workers = Vec::with_capacity(max_workers);6 r& D1 X1 [  ]. ]. }& h, c
        let receiver = Arc::new(Mutex::new(rx));
! a5 }1 |) b+ {2 r$ H        for i in 0..max_workers {
: }/ C( s3 }/ L% S* }            workers.push(Worker::new(i, Arc::clone(&amp;receiver)));+ A- I5 }5 D( {& ?  G/ F8 R
        }3 ]3 p+ b. D  ]
9 s( [! k4 X, ^8 [
        Pool { workers: workers, max_workers: max_workers, sender: tx }; _, s  N5 h* r) K8 n7 a5 D4 [/ S$ J
    }1 x6 r% C: L0 N) u4 c
   
' k! Z8 q: {" _4 \* O6 Y7 c8 P- P; L    pub fn execute&lt;F&gt;(&amp;self, f:F) where F: FnOnce() + 'static + Send* P! z! A! q8 ~
    {
2 [& y/ V# @! c* b/ p! R. y0 c8 d% l2 c( B8 G- {: S  s
        let job = Message::NewJob(Box::new(f));# x* a$ A0 h5 f  w" Y0 e
        self.sender.send(job).unwrap();
  o7 Z3 E% ^2 f! E: H1 |" a8 u    }
0 L2 l6 C) v* ^4 D% `}
! f6 t: O3 l" C( S
% j2 i7 e) T- ^$ [: }8 E' Jimpl Drop for Pool {% j  Y( R: V5 I/ M
    fn drop(&amp;mut self) {' L3 {* F) \+ ?( R
        for _ in 0..self.max_workers {
5 {! X  _8 b+ Y, u8 Q            self.sender.send(Message::ByeBye).unwrap();
( G5 ~+ p2 H# I9 L1 L        }% q$ j  g$ D/ ~
        for w in self.workers {
. h0 k, b3 G: |" i, E            if let Some(t) = w.t.take() {
% Q5 e! e( h% x/ p                t.join().unwrap();  F5 j8 p9 `9 R" c, G
            }5 }( D- C0 {. a% S8 }
        }: P& N; Y+ c" N6 v6 ~& m. j
    }  y  I5 r/ }- P# {0 F1 \0 r. {; @# ?6 o
}
* |" b9 d# V" ?4 x/ y. W
+ _4 @/ u2 t  e% X) Y8 ?' g* b6 E" {  I4 q4 j
#[cfg(test)]
* O! [1 b6 [+ E1 W1 mmod tests {6 Y7 O) S1 [9 S( p$ n5 O
    use super::*;+ g, J/ r# `) n/ R, l8 D* @
    #[test]
  P1 X" H; U( W1 H$ K    fn it_works() {* _% b7 m/ q+ b* \
        let p = Pool::new(4);
& |, z0 K  d" _" [        p.execute(|| println!("do new job1"));
- M1 e  L; T$ y# k        p.execute(|| println!("do new job2"));* y) E$ r  e, D! r
        p.execute(|| println!("do new job3"));) T8 z/ z" y7 @
        p.execute(|| println!("do new job4"));
( Q! `0 L1 I: Y9 Z9 T7 {    }
7 t5 x' Z% w3 v7 C6 v}
0 M  l+ S* u2 M2 E</code></pre>
) |/ N% X8 Z! P- Q8 |/ ~0 _! p& j0 u# t
回复

使用道具 举报

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

本版积分规则

手机版|飞雪团队

GMT+8, 2026-11-5 14:23 , Processed in 0.065201 second(s), 21 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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