求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
要资料
 
追随技术信仰

随时听讲座
每天看新闻
 
 
C++并发编程(中文版)
前言
第1章 你好,C++的并发世界!
1.1 何谓并发
1.2 为什么使用并发?
1.3 C++中的并发和多线程
1.4 开始入门
1.5 本章总结
第2章 线程管理
2.1 线程管理的基础
2.2 向线程函数传递参数
2.3 转移线程所有权
2.4 运行时决定线程数量
2.5 识别线程
2.6 本章总结
第3章 线程间共享数据
3.1 共享数据带来的问题
3.2 使用互斥量保护共享数据
3.3 保护共享数据的替代设施
3.4 本章总结
第4章 同步并发操作
4.1 等待一个事件或其他条件
4.2 使用期望等待一次性事件
4.3 限定等待时间
4.4 使用同步操作简化代码
4.5 本章总结
第5章 C++内存模型和原子类型操作
5.1 内存模型基础
5.2 C++中的原子操作和原子类型
5.3 同步操作和强制排序
5.4 本章总结
第6章 基于锁的并发数据结构设计
6.1 为并发设计的意义何在?
6.2 基于锁的并发数据结构
6.3 基于锁设计更加复杂的数据结构
6.4 本章总结
第7章 无锁并发数据结构设计
7.1 定义和意义
7.2 无锁数据结构的例子
7.3 对于设计无锁数据结构的指导建议
7.4 本章总结
第8章 并发代码设计
8.1 线程间划分工作的技术
8.2 影响并发代码性能的因素
8.3 为多线程性能设计数据结构
8.4 设计并发代码的注意事项
8.5 在实践中设计并发代码
8.6 本章总结
第9章 高级线程管理
9.1 线程池
9.2 中断线程
9.3 本章总结
第10章 多线程程序的测试和调试
10.1 与并发相关的错误类型
10.2 定位并发错误的技术
10.3 本章总结
第11章 C++11语言特性简明参考(部分)
11.1 右值引用
11.2 删除函数
11.3 默认函数
11.4 常量表达式函数
11.5 Lambda函数
11.6 变参模板
11.7 自动推导变量类型
11.8 线程本地变量
 

 
目录
与并发相关的错误类型
作者:Anthony Williams  译者:陈晓伟
40 次浏览
3次  

你可以在并发代码中发现各式各样的错误,这些错误不会集中于某个方面。不过,有一些错误与使用并发直接相关,本章重点关注这些错误。通常,并发相关的错误通常有两大类:

  • 不必要阻塞

  • 条件竞争

这两大类的颗粒度很大,让我们将其分成颗粒度较小的问题。

10.1.1 不必要阻塞

“不必要阻塞”是什么意思?一个线程被阻塞的时候,不能处理任何任务,因为它在等待其他“条件”的达成。通常这些“条件”就是一个互斥量、一个条件变量或一个future,也可能是一个I/O操作。这是多线程代码的先天特性,不过这也不是在任何时候都可取的——衍生成“不必要阻塞”。你会问:为什么不需要阻塞?通常,是因为其他线程在等待该阻塞线程上的某些操作完成,如果该线程阻塞了,那那些线程必然会被阻塞。

这个主题可以分成以下几个问题:

  • 死锁——如你在第3章所见,在死锁的情况下,两个线程会互相等待。当线程产生死锁,应该完成的任务就会持续搁置。举个例子来说,一些线程是负责对用户界面操作的线程,在死锁的情况下,用户界面就会无响应。在另一些例子中,界面接口会保持响应,不过有些任务就无法完成,比如:查询无结果返回,或文档未打印。

  • 活锁——与死锁的情况类似。不同的地方在于线程不是阻塞等待,而是在循环中持续检查,例如:自旋锁。一些比较严重的情况下,其表现和死锁一样(应用不会做任何处理,停止响应),CPU的使用率还居高不下;因为线程还在循环中被检查,而不是阻塞等待。在一些不太严重的情况下,因为使用随机调度,活锁的问题还是可以解决的。

  • I/O阻塞或外部输入——当线程被外部输入所阻塞,线程也就不能做其他事情了(即使,等待输入的情况永远不会发生)。因此,被外部输入所阻塞,就会让人不太高兴,因为可能有其他线程正在等待这个线程完成某些任务。

简单的介绍了一下“不必要阻塞”的组成。

那么,条件竞争呢?

10.1.2 条件竞争

条件竞争在多线程代码中很常见——很多条件竞争表现为死锁与活锁。而且,并非所有条件竞争都是恶性的——对独立线程相关操作的调度,决定了条件竞争发生的时间。很多条件竞争是良性的,比如:哪一个线程去处理任务队列中的下一个任务。不过,很多并发错误的引起也是因为条件竞争。

特别是,条件竞争经常会产生以下几种类型的错误:

  • 数据竞争——因为未同步访问一块共享内存,将会导致代码产生未定义行为。在第5章已经介绍了数据竞争,也了解了C++的内存模型。数据竞争通常发生在错误的使用原子操作,做同步线程的时候,或没使用互斥量所保护的共享数据的时候。

  • 破坏不变量——主要表现为悬空指针(因为其他线程已经将要访问的数据删除了),随机存储错误(因为局部更新,导致线程读取了不一样的数据),以及双重释放(比如:当两个线程对同一个队列同时执行pop操作,想要删除同一个关联数据),等等。不变量被破坏可以看作为“基于数据”的问题。当独立线程需要以一定顺序执行某些操作时,错误的同步会导致条件竞争,比如:顺序被破坏。

  • 生命周期问题——虽然这类问题也能归结为破坏了不变量,不过这里将其作为一个单独的类别给出。这里的问题是,线程会访问不存在的数据,这可能是因为数据被删除或销毁了,或者转移到其他对象中去了。生命周期问题,通常是在一个线程引用了局部变量,在线程还没有完成前,局部变量的“死期”就已经到了,不过这个问题并不止存在这种情况下。当你手动调用join()等待线程完成工作,你需要保证异常抛出的时候,join()还会等待其他未完成工作的线程。这是线程中基本异常安全的应用。

恶性条件竞争就如同一个杀手。死锁和活锁会表现为:应用挂起和反应迟钝,或超长时间完成任务。当一个线程产生死锁或活锁,可以用调试器附着到该线程上进行调试。条件竞争,破坏不变量,以及生命周期问题,其表现都是代码可见的(比如,随机崩溃或错误输出)——可能重写了系统部分的内存使用方式(不会改太多)。其中,可能是因为执行时间,导致问题无法定位到具体的位置。这是共享内存系统的诅咒——需要通过线程尝试限制可访问的数据,并且还要正确的使用同步,应用中的任何线程都可以复写(可被其他线程访问的)数据。

现在已经了解了这两大类中都有哪些具体问题了。

下面就让我们来了解,如何在你的代码中定位和修复这些问题。


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



40 次浏览
3次