飞雪团队

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

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

[复制链接]

8993

主题

9081

帖子

2万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
29309
发表于 2022-2-12 14:35:42 | 显示全部楼层 |阅读模式
0 f- f" k( r3 \  O; m
<h1 id="如何实现一个线程池">如何实现一个线程池</h1>! p; r: z9 z1 l  {9 u. I. K
<p>线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,对于计算密集型任务,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。</p>
  _2 |( ~+ l+ F4 ?5 v: ^2 h<p>如何定义线程池Pool呢,首先最大线程数量肯定要作为线程池的一个属性,并且在new Pool时创建指定的线程。</p>( f* W( l2 z; U9 A5 F8 k2 G
<p>线程池Pool</p>8 K7 e- [" q2 V8 L9 z- b
<pre><code>pub struct Pool {' F9 S3 x: ~4 Y* }
  max_workers: usize, // 定义最大线程数5 ~' C) N3 J- g) t
}
; O+ ^6 z2 ?' Z" d' T  {
6 _6 d8 b; X! {impl Pool {
# ^% Y' R: E" C1 I$ e( X% X  fn new(max_workers: usize) -&gt; Pool {}
$ c8 S# N/ ^, Z& i  fn execute&lt;F&gt;(&amp;self, f:F) where F: FnOnce() + 'static + Send {}
1 o4 s8 v2 i6 h3 M( i, k( R}, U2 J& T* G) @) l" g
4 m9 |6 r. ?" g& J! I
</code></pre>9 A  S, Q' z) A/ L8 b$ T( b
<p>用<code>execute</code>来执行任务,<code>F: FnOnce() + 'static + Send</code> 是使用thread::spawn线程执行需要满足的trait, 代表F是一个能在线程里执行的闭包函数。</p>% d, _& R& ?0 L- w; W3 ^( }
<p>另一点自然而然会想到在Pool添加一个线程数组, 这个线程数组就是用来执行任务的。比如<code>Vec&lt;Thread&gt;</code> balabala。这里的线程是活的,是一个个不断接受任务然后执行的实体。<br>
9 a; x8 Y1 }# G# g1 y$ I. v: R! R可以看作在一个线程里不断执行获取任务并执行的Worker。</p>! w9 }! z8 N# }6 M) Y% C
<pre><code>struct Worker where) J( l; C( H4 r- u
{( N  o2 W, ^& K2 N; P
    _id: usize, // worker 编号
/ i* s' ^. ?  g# q* y) p}
+ b4 G  r1 M7 j8 o2 m+ m</code></pre>
) T* `6 y7 q, m* M1 [  _6 u9 Q& b+ G<p>要怎么把任务发送给Worker执行呢?mpsc(multi producer single consumer) 多生产者单消费者可以满足我们的需求,<code>let (tx, rx) = mpsc::channel()</code> 可以获取到一对发送端和接收端。<br>
: c8 W/ c6 [0 y把发送端添加到Pool里面,把接收端添加到Worker里面。Pool通过channel将任务发送给多个worker消费执行。</p>
" f) Q: y! c  t2 S+ n+ R- h, Q<p><strong>这里有一点需要特别注意,channel的接收端receiver需要安全的在多个线程间共享</strong>,因此需要用<code>Arc&lt;Mutex::&lt;T&gt;&gt;</code>来包裹起来,也就是用锁来解决并发冲突。</p>' C8 O& q. i+ i9 F7 q! E
<p>Pool的完整定义</p>
- o* f/ f5 ^2 N8 _+ N* R" N<pre><code>pub struct Pool {, Y9 b% Z1 S, T' u" x. y% z
    workers: Vec&lt;Worker&gt;,
# s9 ]9 J" r6 G    max_workers: usize," _- l+ o& A% @; w$ e
    sender: mpsc::Sender&lt;Message&gt;! L* T- _% h7 ~& F5 W
}
6 j& H+ M- M1 M/ n' a</code></pre>7 B/ u& A3 \+ R: u$ s8 ]  U
<p>该是时候定义我们要发给Worker的消息Message了<br>
+ U2 {& ^( L. c' ^6 C! y0 Z% ?3 x" s定义如下的枚举值</p>
2 m& _5 Z+ a* S6 Z  y7 m& I; m<pre><code>type Job = Box&lt;dyn FnOnce() + 'static + Send&gt;;
; c" e* C6 [- e' R) e2 zenum Message {
. Y$ K/ C5 s$ E' u" X- s    ByeBye,
* e6 [& u. X" N0 c( g/ ^2 y$ D    NewJob(Job),
+ d# O3 {7 l! E8 t( F3 [}1 F/ W) [4 K& w! u  E7 s" [' b/ p
</code></pre>& C8 y3 Q, u6 r& u( d% p4 D
<p>Job是一个要发送给Worker执行的闭包函数,这里ByeBye用来通知Worker可以终止当前的执行,退出线程。</p>
4 B; b  @3 c: X! h3 d6 i8 _6 M. ^<p>只剩下实现Worker和Pool的具体逻辑了。</p>
- d  J7 B! g9 l/ r7 j1 ~7 p<p>Worker的实现</p>
4 }5 T/ B8 U4 l<pre><code>impl Worker
# m3 g  J( L) m: R# x{$ P* k1 K8 P% v
    fn new(id: usize, receiver: Arc::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; Worker {
2 S9 m& b* I& r4 I4 \        let t = thread::spawn( move || {9 ^2 F! n6 O! d
            loop {
% ]7 ^9 ~4 s: c9 V. Q! n' q% C                let receiver = receiver.lock().unwrap();$ T3 Z* Q( [/ z+ J
                let message=  receiver.recv().unwrap();* c" h1 I- ^0 v8 }8 S; {
                match message {
# ~. X) y8 F' s& n5 _. ]- _" e                    Message::NewJob(job) =&gt; {
  Y: U& \+ }! r# r+ ]( Q                        println!("do job from worker[{}]", id);. ~* F2 w" {4 T- C
                        job();# I) H; C/ F1 \/ U8 D+ X
                    }," G& R2 E1 ~& k( c
                    Message::ByeBye =&gt; {" f" O6 p/ K  y
                        println!("ByeBye from worker[{}]", id);
* K. `/ s9 v; |! {                        break! U$ l( d7 U2 C. F: u" K
                    },
9 U9 M8 ^: y5 Z9 ]' F) ^                }  % o8 _. e% ]8 R6 \& z
            }
2 F! L# W! G7 u1 h+ Q, c: y6 }        });+ x! `  C7 D2 c/ Q4 v7 J( `

2 l6 V3 }6 P% u; q7 w2 F        Worker {
, z6 `$ b+ c' X! `" q3 y6 ~            _id: id,
1 f- v1 u) O! L$ x1 M            t: Some(t),
. e# y) k7 ^- H3 B+ v6 V; }6 O        }0 L- i& u9 [, `7 o1 P9 r
    }6 s- E( [. {8 v
}3 m* \' ?; E, L* j, p
</code></pre>9 I3 ~2 _  ?7 @+ k
<p><strong>let message = receiver.lock().unwrap().recv().unwrap();</strong> 这里获取锁后从receiver获取到消息体,然后let message结束后rust的生命周期会自动释放掉锁。<br>
2 `& O8 N- g0 J$ l' ^但如果写成</p>6 l- Z* |0 G* F9 @+ W! N
<pre><code>while let message = receiver.lock().unwrap().recv().unwrap() {
6 T7 i( ?1 L2 d, \8 R3 s1 W};7 [( a( S! l9 T8 W1 ]
</code></pre>5 }8 i% R7 w. \3 }' ?9 N
<p>while let 后面整个括号都是一个作用域,要在这个作用域结束后,锁才会释放,比上面let message要锁定久时间。<br>
: l# ?2 A+ E/ v5 O( ?$ G) e0 wrust的mutex锁没有对应的unlock方法,由mutex的生命周期管理。</p>! A. B& v% a# P6 {7 `* m
<p>我们给Pool实现<code>Drop</code> trait, 让Pool被销毁时,自动暂停掉worker线程的执行。</p>
$ m; z: P/ m" {/ R<pre><code>impl Drop for Pool {$ x- U2 _7 A) R  |0 U
    fn drop(&amp;mut self) {
0 S- X! f* x! U8 v! n( Q7 D; w- E        for _ in 0..self.max_workers {2 v9 f5 ^% q: A( w( k5 |0 g
            self.sender.send(Message::ByeBye).unwrap();' ]! {. I5 r3 a# y
        }
' U) x+ }" u7 h- t; _6 @        for w in self.workers.iter_mut() {
7 I9 N5 H" p' F, i- g5 f  x            if let Some(t) = w.t.take() {& v& E0 }" D" q! ~9 z
                t.join().unwrap();
* O- `% v, r+ J1 I  T            }7 T+ s2 v. v6 u* }1 C/ v( k& J
        }
/ e# ^& ~* \: R: f  c; }; U    }
+ P) s* Y* W1 g}) }) X4 S- ?- L

4 ?/ D' k+ G- o! R' u$ f</code></pre>' i$ _# F$ A, r: ^- e3 e
<p><strong>drop方法里面用了两个循环</strong>,而不是在一个循环里做完两件事?</p>+ _) H9 p2 v% a  P# Y( B# |% g
<pre><code>for w in self.workers.iter_mut() {
- t( K, J2 ~6 g) y" X# b( @    if let Some(t) = w.t.take() {' Y1 ?% H0 A' `6 x: ]
        self.sender.send(Message::ByeBye).unwrap();$ x% A4 n' j- t" d. H8 p
        t.join().unwrap();
1 F4 P1 j; d) T    }9 o& t% Q* I6 _' c5 ~
}
+ {% _; b1 @- b/ W# A& q
  x; {$ A3 `% I" q4 q2 p1 F/ s1 p$ m</code></pre>
$ |- h0 Z6 W. s/ c<p>这里面隐藏了一个会造成死锁的陷阱,比如两个Worker, 在单个循环里面迭代所有Worker,再将终止信息发送给通道后,直接调用join,<br>
: ~/ D( [. H) t  n' ~我们预期是第一个worker要收到消息,并且等他执行完。当情况可能是第二个worker获取到了消息,第一个worker没有获取到,那接下来的join就会阻塞造成死锁。</p>
2 p* A& `1 x: V' t<p><strong>注意到没有,Worker是被包装在Option内的</strong>,这里有两个点需要注意</p>+ }7 v) Z- e0 |5 ^( a
<ol>) r* W' P: ~' m& _
<li>t.join 需要持有t的所有权</li>
8 c* Q3 h, M" r3 N<li>在我们这种情况下,self.workers只能作为引用被for循环迭代。</li>7 [& X5 c( _) ?1 x( [. n( w$ q  a9 A
</ol>4 c' z8 ]1 |% m6 i
<p>这里考虑让Worker持有<code>Option&lt;JoinHandle&lt;()&gt;&gt;</code>,后续可以通过在Option上调用take方法将Some变体的值移出来,并在原来的位置留下None变体。<br>/ _4 J1 ^0 C6 d/ a3 v8 ]  c7 u
换而言之,让运行中的worker持有Some的变体,清理worker时,可以使用None替换掉Some,从而让Worker失去可以运行的线程</p>
, H* c6 Y( {- |- F8 N! q<pre><code>struct Worker where
: y9 N5 ^" R# G' R4 A3 r{5 [8 F8 W9 A3 x) g- c8 [) q$ Q% @
    _id: usize,, g! A$ n! P  V" W2 `; y
    t: Option&lt;JoinHandle&lt;()&gt;&gt;,
+ l1 m1 P& @, o8 b2 b+ L- D' ?/ {}
' Q' K- Y0 \, v$ p5 x$ r3 z$ O3 [( I</code></pre>7 ?9 ^: `! q# C+ f
<h1 id="要点总结">要点总结</h1>- ?1 M  Y2 g# `! O- ~9 \7 u
<ul>: u+ [9 c) J. `6 e
<li>Mutex依赖于生命周期管理锁的释放,使用的时候需要注意是否逾期持有锁</li>
% U; _, {  j; |5 c9 g. j% G; w6 X% m<li><code>Vec&lt;Option&lt;T&gt;&gt;</code> 可以解决某些情况下需要T所有权的场景</li>7 v6 O5 \, N& c9 O' b' O7 G* q( Z
</ul>9 t) d  [/ U; g; x: o0 H
<h1 id="完整代码">完整代码</h1>. ]- [9 m+ _! ?* ], {4 Z( h
<pre><code>use std::thread::{self, JoinHandle};! ~) N. B6 f! [9 _6 e0 P( _7 x
use std::sync::{Arc, mpsc, Mutex};- s+ @5 D2 j% v# |2 R8 K, q8 V
) f4 e" Y2 E4 l; m
0 D. v2 h3 b! r# P1 J
type Job = Box&lt;dyn FnOnce() + 'static + Send&gt;;9 R" U, p3 [* @  ]' z
enum Message {' y2 K  l7 [4 F) v' |$ x1 L* |
    ByeBye,+ }9 v7 ]$ j( H: @& I: ?
    NewJob(Job),$ M1 L& q5 t& w0 ]
}1 ~/ f! p% s5 \* m( G/ h2 d
9 C5 H; y- q, o3 ^3 ^
struct Worker where$ N" M+ u- C8 z3 j  a' y* q
{
$ U. ]( d) w  a- Y0 f4 [    _id: usize,
9 M# y# S$ I' |, g5 E7 `    t: Option&lt;JoinHandle&lt;()&gt;&gt;,8 R: d. }& L! ]/ m, J
}
* g- w7 V/ S4 O, l8 W% @- S5 S
: G/ {9 p, p% f. b& b1 zimpl Worker
. `3 L- n6 `( q) G) Z$ ]{* A% a7 L! V4 Y
    fn new(id: usize, receiver: Arc::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; Worker {
9 V5 L; [( a( b, c$ x' D# M        let t = thread::spawn( move || {
5 |/ C( S+ {4 i0 C6 e/ i            loop {
+ Y  G3 W' P) H( t4 B! i                let message = receiver.lock().unwrap().recv().unwrap();5 B4 B1 ^* [" L( @9 }5 _! h
                match message {
2 t% m7 G! _7 L                    Message::NewJob(job) =&gt; {
; {& \8 w9 S5 o8 i) X                        println!("do job from worker[{}]", id);2 l) B& }8 x2 v" p
                        job();% [7 j$ {" n; \1 `- o! a4 ~
                    },7 c, P6 p4 ]( I
                    Message::ByeBye =&gt; {
" l; X: C, |! i$ A# P6 q4 y                        println!("ByeBye from worker[{}]", id);- O# H& p# p6 s7 g& @2 h& |+ E
                        break- z0 m; [2 b$ Y% y, M4 I- _
                    },
( P' w$ j6 C* n) s8 k5 g                }  7 Y/ ?8 u9 l$ Y- ]
            }
+ f- f1 T) D% w+ r7 N        });
+ H" y6 j$ B2 [3 ^8 d4 l/ S/ `, D3 B6 q7 e+ j( f: M4 {( @* Y
        Worker {0 b- v$ e7 i) u% |
            _id: id," G+ [; v# W7 [
            t: Some(t),
, p+ w1 u$ S5 T" s1 G; h        }
; D3 {8 b  y2 @& j. g- b  _    }- a, p' H9 j( N) |5 k! w
}8 B, P) q( ~7 u0 @* \. l3 H- l
! \* Q$ }2 h" i  A
pub struct Pool {3 l  @# u& c( N# d4 `
    workers: Vec&lt;Worker&gt;,
  n' Y! B% ^5 h* m; c    max_workers: usize,
$ d- W: U* G0 b( ]1 Q8 k' @3 J+ R    sender: mpsc::Sender&lt;Message&gt;
9 M! I# }2 q- u. t7 P; `% K" {}8 g3 `. G% X8 G' p6 y* h! m$ z! J

! q$ `0 e9 r+ s9 R! ]9 }impl Pool where {' U* M1 g/ A% H4 |- x) |& J7 Q
    pub fn new(max_workers: usize) -&gt; Pool {
5 h7 S0 I8 O# {  A( K8 L" M        if max_workers == 0 {) W+ F% K1 F  ~" m  }- R
            panic!("max_workers must be greater than zero!")! B, n+ T& E5 N8 v0 n) D0 i" m% \
        }
: }7 S: s* U& x' x" ]        let (tx, rx) = mpsc::channel();
. N; K6 y" D5 y* e9 w4 e* g
8 o, k4 h5 K) O( _. R        let mut workers = Vec::with_capacity(max_workers);
6 C7 y- I: e1 A7 j) q3 i5 W        let receiver = Arc::new(Mutex::new(rx));
: @5 l! ]9 _# o; j        for i in 0..max_workers {/ {4 V1 ^5 o* h5 g+ L3 X8 o
            workers.push(Worker::new(i, Arc::clone(&amp;receiver)));
, @, ?+ {( u* g" G2 Q* Q' W: U        }5 d$ U8 }2 N( ~9 J' U
- w# I2 Z% M5 M+ a8 G! r! ^8 d
        Pool { workers: workers, max_workers: max_workers, sender: tx }
5 @6 u* }. H7 F3 q    }
$ t4 D/ D5 l: L  {: _' p) L    - D, t4 l3 P$ `+ z) y$ n1 z
    pub fn execute&lt;F&gt;(&amp;self, f:F) where F: FnOnce() + 'static + Send  @% q" W$ x* O8 |) g* ]
    {
5 J% n: Y/ x4 c# r9 g5 O; F( B9 k0 `6 `& u' f
        let job = Message::NewJob(Box::new(f));: }/ f4 r( b7 o9 R
        self.sender.send(job).unwrap();! W0 M0 j4 A3 V5 B
    }
# ?/ ~5 m5 V+ B- |  n7 `+ _, M0 X}
0 _5 M, F. D) @
- R! L& g) d: {0 Wimpl Drop for Pool {# l5 J7 r8 p! u7 Z
    fn drop(&amp;mut self) {
( w/ m. ?# Q- p% g/ _. {3 G        for _ in 0..self.max_workers {0 O, |' a4 M5 V: s2 s4 |
            self.sender.send(Message::ByeBye).unwrap();2 f* ]6 g" X: c& J/ h' [6 a
        }
! O- Y. p3 B$ [# \6 L        for w in self.workers {
4 n9 X- W9 J0 a% N' P            if let Some(t) = w.t.take() {
' s4 U2 w! W: j                t.join().unwrap();
) f7 P  V8 Z6 R' A# ^9 k$ t4 Z1 \            }" ^" s$ G+ T4 M. p; u/ Q$ Y
        }! W& A1 Q% C8 [0 z. ]
    }
1 A* \5 L4 W' p  k" \7 O}! l6 |1 N+ D7 A6 `& _- l/ D" V
$ r5 l( D3 ^& u5 c, z

5 v* M, s* d, N- C0 l#[cfg(test)]
9 t; k* y! a! L' k, k3 Dmod tests {* w9 |3 B2 \8 `  l2 z
    use super::*;; _0 c* [- z% j8 G; ]0 C3 U# O
    #[test]
$ w& d% G9 L7 P+ d    fn it_works() {1 u3 {8 L9 C' l
        let p = Pool::new(4);& ~! h, F5 H8 L) G+ N
        p.execute(|| println!("do new job1"));- c' d/ \1 o+ k# g7 Y8 t0 K
        p.execute(|| println!("do new job2"));2 W: G5 F5 a; W* m: z; w! h
        p.execute(|| println!("do new job3"));6 x* e* N( `2 K) @
        p.execute(|| println!("do new job4"));
2 d* n' ]. u, b    }* g, d7 F! [$ u5 {
}
. V) ^9 s% `+ |( h2 @</code></pre>
( @+ m  A; P2 M' f
; s2 x7 g- F* P  O/ }% F
回复

使用道具 举报

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

本版积分规则

手机版|飞雪团队

GMT+8, 2026-7-12 06:32 , Processed in 0.222904 second(s), 22 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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