飞雪团队

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

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

[复制链接]

9180

主题

9268

帖子

2万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
29870
发表于 2022-2-12 14:35:42 | 显示全部楼层 |阅读模式
: 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) -&gt; Pool {}
" R' P+ s) d) w+ p3 I  fn execute&lt;F&gt;(&amp;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&lt;Thread&gt;</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&lt;Mutex::&lt;T&gt;&gt;</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&lt;Worker&gt;,% 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&lt;Message&gt;( 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&lt;dyn FnOnce() + 'static + Send&gt;;
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::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; 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) =&gt; {
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 =&gt; {
# @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(&amp;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&lt;JoinHandle&lt;()&gt;&gt;</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&lt;JoinHandle&lt;()&gt;&gt;,
, 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&lt;Option&lt;T&gt;&gt;</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&lt;dyn FnOnce() + 'static + Send&gt;;. 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&lt;JoinHandle&lt;()&gt;&gt;,
% }/ 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::&lt;Mutex&lt;mpsc::Receiver&lt;Message&gt;&gt;&gt;) -&gt; 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) =&gt; {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 =&gt; {
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&lt;Worker&gt;,
6 M  R+ K( D+ N1 N    max_workers: usize,
& f% `, f( Z9 v+ U    sender: mpsc::Sender&lt;Message&gt;% ^( 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) -&gt; 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(&amp;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&lt;F&gt;(&amp;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(&amp;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
回复

使用道具 举报

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

本版积分规则

手机版|飞雪团队

GMT+8, 2026-9-9 11:46 , Processed in 0.062868 second(s), 22 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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