通用编程

概述:多线程编程的12种设计模式

近期读了一下多线程编程设计模式,大概概括一下这些设计模式吧。

也不想写太多东西,就像最初看设计模式写个小纸条让自己记住一样,随便写一些东西总是有些好处的。

其中很多模式耳熟能详,例如线程池、不可变对象。也有我以前没有怎么看到过的,例如Pipeline、半同步版异步等等。

1、Immutable Object模式(不可变对象)

通过将对象变为只读的形式来保证线程安全,是比较好的无锁实现。

无非就是将所有可以变的变量的setter全部对访问者关闭,并且防止有可修改引用泄露出去。

2、Guarded Suspension模式(保护性暂挂)

有时候一些线程在执行的时候需要满足一些条件,倘若没有满足的话便无法执行下去,这个时候我们通过暂挂(跑一跑空循环之类的)来让线程暂时挂起,等待条件满足的时候再继续执行。

在后面的Promise模式中,比如在多线程中写得很多的Future就经常会用到,结果还没算出来,那就不好意思,请等一会儿再执行咯。

3、Two-phase Termination模式(两阶段终止)

在终止线程的时候我们或许会丢掉很多东西,比如一些状态,或者还有一些必要执行的东西还未执行完。倘若直接中断线程的话会引起很多问题,所以在终止线程之前我们或许需要等待这些操作。

例如在Java的标准库中,线程池就会等待目前仍在执行的线程,执行完全之后就会完全关闭线程池。后面的Thread Pool模式中也会用到。

4、Promise模式(承诺)

Promise模式帮我们不需要等待耗时的求值操作,而是拿一个凭证。我们可以先去做些别的,最后再回来取值。在Java中的Future就是用的这个思想。

5、Producer-Consumer模式(生产者-消费者)

在多线程编程中经常有一个生成数据以及一个消费数据的两个模块,这两者的速率通常是不相等的,所以为了避免等待,该模式中引入了通道的概念。

生产者将产生的数据放入一个队列,消费者则从队列中拿数据。并且生产者与消费者是完全解耦的。

改进版本中也有用多个通道进行消费,减少锁竞争,再者也可以用工作窃取的方式进行负载均衡。

6、Active Object模式(主动对象)

该模式将方法的调用与执行解耦。使用者通过同步的方式调用,而后台以异步的方式执行。调用与执行分开在不同线程以提高并发性,当然也可以用到Promise模式来得到结果。

7、Thread Pool模式(线程池)

这个模式就是为减少开启与关闭线程带来的开销。但是在使用的时候还是有很多需要注意的地方,例如死锁、线程泄漏之类的问题,导致可用线程越来越少。

Java中直接用各种Executer,已经是很好的线程池了,也没有必要自己再实现一个。

8、Thread Specific Storage模式(线程特有存储)

通过每个线程独立储存数据来避免竞争。也是一种很好的无锁线程安全实现方式,但是要注意内存泄漏,当线程销毁之后,保证数据也能够被及时销毁。

在Java当中直接使用ThreadLocal来应用该模式。

9、Serial Thread Confinement模式(串行线程封闭模式)

并发任务涉及到非线程安全对象时我们有时候又不希望使用锁,那我们就可以使用该模式。

由于每次只有一个线程去访问该非安全对象,所以无需担心线程不安全。

其本质是将锁替代为开销更小的锁。(其实可以看做行为队列)

10、Master-Slave模式(主仆)

将任务拆解为更小的任务,小任务或许还能拆解成更小的任务,然后这些任务交给不同的工作线程进行执行。

最后将这些任务进行合并,以达成完成所有任务的目的。在工作线程中我们需要通过负载均衡以及工作窃取来达成最高效率。

当然该模式也可以用来做可靠性设计,子任务中只取一个正确完成的结果,只需要确保所有子任务中有一个任务完成就可以了。

在停止任务的时候我们也需要用到两阶段停止的思想,将子任务执行完之后自动停止。

11、Pipeline模式(流水线)

在执行任务的时候,我们通常可以将任务分解成不同的阶段,在一些阶段当中或许只有一个线程参与,而在另一些阶段中则有多个线程进行参与。

我们可以通过配置文件的方式来配置我们的自定义流水线。

需要注意的是,流水线由于独立出很多资源,所以一定会有额外的开销,所以要注意管线的深度,保证不要得不偿失。

在始终只有一个线程参与的情况下,也可以将Pipeline看做是Serial Thread Confinement模式。

12、Half-sync/Half-async模式(半同步/半异步)

在多线程编程当中我们会发现,同步模式易于理解,而异步模式对IO更加高效。

所以引入这一模式,将模块分成三层,同步层、中间层、异步层,同步层进行一些同步编写,而异步层则进行IO操作,操作完成后将数据放入中间层,而同步层从中间层取得数据。

需要注意同步层的高级任务,例如数据库、网络任务执行过慢的时候考虑使用非阻塞IO。

有兴趣的话可以到我的个人小站逛一逛,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注