美文美句-经典语录-美文摘抄-心情日记 » 美文美句 » 并发编程之痛

并发编程之痛

一个程序员开始学习编程,熟悉了某个编程语言的语法和库,就可以写程序了,比如操作文件、处理数据库中的数据等等,把这些程序放到框架中,还能给互联网上的用户提供服务。那这里面会不会涉及到并发编程呢?如果你没有在代码中显式使用线程 API,那可能你的程序就是串行的。但是它为什么可以提供并发服务呢,也就是说,很多人可以同时访问你的程序功能?这是因为你是用的框架 —— Nginx、OpenResty、Tomcat 或数据库连接池等 —— 提供了并发服务。比如 Nginx 中的 worker 调度服务。

你当然可以使用现有的线程并发框架,但是,总有一天你会自己去编写多线程并发程序,这是任何一个程序员无法避开的挑战,它就像你苦练十年准备跨马提刀闯江湖时必须要打败的一个对手。事实上,并发编程并不像在花园陪妹子散步那么轻松。

在串行程序中,代码的执行路径是可预测的,遇到问题。你总可以顺藤摸瓜找到逻辑错误或某种 bug,比如内存泄露、缓存区溢出、文件和网络 IO 错误、数组越界等等。在并发算法和多线程编程中,你需要同时考虑有多个执行流程的情况,以及如何对这些流程进行协调以完成指定的计算任务。并发编程中的很多 bug 是由于线程并发执行中的不确定性和异步性导致的。在不同的机器环境,线程会按照某种次序执行而导致错误无法被发现,等上线了才知道,已经是生产事故了。

如何在多核和云平台上写出健壮、安全、高性能的并发程序呢?

其实很简单,只要你能从两个方面突破一下就可以了。一个是“跳出来,看全景”,另一个是“钻进去,看本质”。

跳出来,看全景

我们先说“跳出来”。你应该也知道,学习最忌讳的就是“盲人摸象”,只看到局部,而没有看到全局。所以,你需要从一个个单一的知识和技术中“跳出来”,高屋建瓴地看并发编程。当然,这首要之事就是你建立起一张全景图

不过,并发编程相关的知识和技术还真是错综复杂,时至今日也还没有一张普遍认可的全景图,也许这正是很多人在并发编程方面难以突破的原因吧。好在经过多年摸爬滚打,我自己已经“勾勒”出了一张全景图,不一定科学,但是在某种程度上我想它还是可以指导你学好并发编程的。

在我看来,并发编程领域可以抽象成三个核心问题:分工、同步和互斥

1、分工

所谓分工,类似于现实中一个组织完成一个项目,项目经理要拆分任务,安排合适的成员去完成。

在并发编程领域,你就是项目经理,线程就是项目组成员。任务分解和分工对于项目成败非常关键,不过在并发领域里,分工更重要,它直接决定了并发程序的性能。在现实世界里,分工是很复杂的,著名数学家华罗庚曾用“烧水泡茶”的例子通俗地讲解了统筹方法(一种安排工作进程的数学方法),“烧水泡茶”这么简单的事情都这么多说道,更何况是并发编程里的工程问题呢。

既然分工很重要又很复杂,那一定有前辈努力尝试解决过,并且也一定有成果。的确,在并发编程领域这方面的成果还是很丰硕的。Java SDK 并发包里的Executor、Fork/Join、Future 本质上都是一种分工方法。除此之外,并发编程领域还总结了一些设计模式,基本上都是和分工方法相关的,例如生产者-消费者、Thread-Per-Message、Worker Thread 模式等都是用来指导你如何分工的。

学习这部分内容,最佳的方式就是和现实世界做对比。例如生产者-消费者模式,可以类比一下餐馆里的大厨和服务员,大厨就是生产者,负责做菜,做完放到出菜口,而服务员就是消费者,把做好的菜给你端过来。不过,我们经常会发现,出菜口有时候一下子出了好几个菜,服务员是可以把这一批菜同时端给你的。其实这就是生产者-消费者模式的一个优点,生产者一个一个地生产数据,而消费者可以批处理,这样就提高了性能。

2、同步

分好工之后,就是具体执行了。在项目执行过程中,任务之间是有依赖的,一个任务结束后,依赖它的后续任务就可以开工了,后续工作怎么知道可以开工了呢?这个就是靠沟通协作了,这是一项很重要的工作。

在并发编程领域里的同步,主要指的就是线程间的协作,本质上和现实生活中的协作没区别,不过是一个线程执行完了一个任务,如何通知执行后续任务的线程开工而已。

协作一般是和分工相关的。Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是分工方法,但同时也能解决线程协作的问题。例如,用 Future 可以发起一个异步调用,当主线程通过 get() 方法取结果时,主线程就会等待,当异步执行的结果返回时,get() 方法就自动返回了。主线程和异步线程之间的协作,Future 工具类已经帮我们解决了。除此之外,Java SDK 里提供的 CountDownLatch、CyclicBarrier、Phaser、Exchanger 也都是用来解决线程协作问题的。

不过还有很多场景,是需要你自己来处理线程之间的协作的。

相关文章