1.13 心得与小结
学习多线程程序设计远远不是看看教程了解API 怎么用那么简单,这最多“主要是为了读懂别人的代码,如果自己要写这类代码,必须专门花时间严肃、认真、系统地学习,严禁半桶水上阵”(孟岩)。一般的多线程教程上都会提到要让加锁的区域足够小,这没错,问题是如何找出这样的区域并加锁,本章§1.9 举的安全读写shared_ptr 可算是一个例子。
据我所知,目前C++(www.cppentry.com) 没有特别好的多线程领域专著,但C 语言有,Java 语言也有。《Java Concurrency in Practice》[JCP] 是我读过的写得最好的书,内容足够新,可读性和可操作性俱佳。C++(www.cppentry.com) 程序员反过来要向Java 学习,多少有些讽刺。除了编程(www.cppentry.com)书,操作系统教材也是必读的,至少要完整地学习一本经典教材的相关章节,可从《操作系统设计与实现》、《现代操作系统》、《操作系统概念》任选一本,了解各种同步原语、临界区、竞态条件、死锁、典型的IPC 问题等等,防止闭门造车。
分析可能出现的race condition 不仅是多线程编程(www.cppentry.com)的基本功,也是设计分布式系统的基本功,需要反复历练,形成一定的思考范式,并积累一些经验教训,才能少犯错误。这是一个快速发展的领域,要不断吸收新知识,才不会落伍。单CPU 时代的多线程编程(www.cppentry.com)经验到了多CPU 时代不一定有效,因为多CPU 能做到真正的并行执行,每个CPU 看到的事件发生顺序不一定完全相同。正如狭义相对论所说的每个观察者都有自己的时钟,在不违反因果律的前提下,可能发生十分违反直觉的事情。
尽管本章通篇在讲如何安全地使用(包括析构)跨线程的对象,但我建议尽量减少使用跨线程的对象,我赞同水木网友ilovecpp 说的:“用流水线,生产者消费者,任务队列这些有规律的机制,最低限度地共享数据。这是我所知最好的多线程编程(www.cppentry.com)的建议了。”
不用跨线程的对象,自然不会遇到本章描述的各种险态。如果迫不得已要用,希望本章内容能对你有帮助。
小结
原始指针暴露给多个线程往往会造成race condition 或额外的簿记负担。
统一用shared_ptr/scoped_ptr 来管理对象的生命期,在多线程中尤其重要。
shared_ptr 是值语意,当心意外延长对象的生命期。例如boost::bind 和容器都可能拷贝shared_ptr。
weak_ptr 是shared_ptr 的好搭档,可以用作弱回调、对象池等。
认真阅读一遍boost::shared_ptr 的文档,能学到很多东西:
http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm
保持开放心态,留意更好的解决办法,比如C++(www.cppentry.com)11 引入的unique_ptr。忘掉已被废弃的auto_ptr。
shared_ptr 是TR1 的一部分,即C++(www.cppentry.com) 标准库的一部分,值得花一点时间去学习掌握19,对编写现代的C++(www.cppentry.com) 程序有莫大的帮助。我个人的经验是,一周左右就能基本掌握各种用法与常见陷阱,比学STL 还快。网络上有一些对shared_ptr 的批评,那可以算作故意误用的例子,就好比故意访问失效的迭代器来证明std::vector 不安全一样。
正确使用标准库(含shared_ptr)作为自动化的内存/资源管理器,解放大脑,从此告别内存错误。