并发编程的挑战

并发编程的挑战

上下文切换

在多线程执行代码的过程中,CPU为每个线程分配CPU时间片保证线程的执行,时间片很短,一般是几十毫秒(ms)。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存前一个任务的状态,以便下次切换回这个任务的时候可以再加载这个任务。任务从保存到再加载的过程就是一次上下文切换。

多线程由于上下文切换的存在使得其不一定比单线程快,一般减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。

  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同线程处理不同段的数据;
  • CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需要加锁;
  • 使用最少线程:避免创建不需要的线程,比如任务少时,创建很多线程会造成大量线程都处于等待状态;
  • 使用协程:在单线程中实现多任务的调度,并在单线程里维持多个任务间的切换。

    死锁

    死锁是指由于两个或者多个任务互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

线程死锁产生的四个必要条件

  1. 互斥条件:某种资源一次只允许一个线程访问,即该资源一旦分配给某个线程,其他线程就不能再访问,直到该进程访问结束并释放;
  2. 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放;
  3. 不剥夺条件:指线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放;
  4. 环路(循环)等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即线程集合{t0,t1,t2,···,tn}中的t0正在等待一个t1占用的资源;t1正在等待t2占用的资源,……,tn正在等待已被t0占用的资源。

避免死锁的几个常用方法

  • 避免一个线程同时获得多个锁;
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源;
  • 尝试使用定时锁,使用lock。tryLock(timeout)来替代使用内部锁机制;
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

资源限制的挑战

资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。例如,网络带宽的限制、硬盘读写速度的限制、CPU计算速度的限制、数据库的连接数和socket连接数限制等。

资源限制引发的问题

在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变为并发执行,但如果将某段串行代码并发执行,因为资源受限,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间。

资源限制的解决

对应硬件资源限制,可以考虑使用集群并行执行程序。既然单机的资源有限制,那么就让程序在多机上运行。比如用ODPSHadoop或者自己搭建服务器集群,不同的机器处理不同的数据。
对于软件资源限制,可以考虑使用资源池复用资源。比如使用连接池将数据库和Socket连接复用,或者在调用对方webservice接口获取数据时,只建立一个连接。

资源限制下的并发编程

根据不同的资源限制调整程序的并发度,比如下载文件程序依赖于两个资源——带宽和硬盘读写速度。有数据库操作时,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接数大很多,就会导致某些线程被阻塞,等待数据库连接,因此这个时候就应该降低线程数量。

-------------本文结束感谢您的阅读-------------