在我刚开始参加工作的那年,公司安排我开发一款即时通讯软件(IM,类似于 QQ 聊天软件),在这之前我心里也知道如果多线程操作一个整型值是要加锁的,但是当时为了图代码简便,而且在实际调试的时候,没有加锁的代码也从来没出过问题。于是我就心存侥幸了,觉得对整型值加锁真是多此一举。
我们的软件有类似于 QQ 这种单人聊天功能,每个用户都有一个整型的 userid,问题就出在这里。当时公司的老板和他妻子也使用这款软件,问题来了:有一天早上老板在这个软件上给他妻子发了一段亲密的话。问题来了,由于多线程操作他妻子的 userid 没加锁,最终变成了另外一个人的 userid,而这个 userid 恰好是我的账户。于是老板发给他妻子的聊天内容就被发给我了。
我当时看到聊天内容很奇怪,还回复了他一句,并且还带上了我自己的姓名……事情的结果,可想而知了,老板非常尴尬也非常生气……从那以后,老板看我的眼神都是怪怪的。我自知理亏,再也不侥幸了,凡是多线程读写整型变量都养成加锁的好习惯。
这是我曾经犯过的错误,也就是所谓的线程安全问题。“年轻”时的我,当时不明白为什么一个整型变量在多线程操作时存在安全问题,进而引起业务上的错乱问题。
很多年以前的时候,技术面试的时候面试官经常会问“程序什么时候需要开启新的线程”这样的问题,那个时候多核 CPU 才刚开始普及,很多人也是才开始逐渐接触多线程技术。而如今多核 CPU 和多线程编程技术已经是下里巴人的技术了。
本专栏将从操作系统原理的角度介绍多线程技术的方方面面,从基础的知识到高级进阶,它们是笔者这些年工作的经验总结和踩坑之后的教训。
无论是哪种编程语言和库,其最终都是要运行在操作系统上的,应用程序本身的特性应该是决定采用何种编程工具的最主要因素,但是无论你采用什么样的编程语言,通过了解操作系统 API 从而深入理解操作系统的工作原理,这本身就有很重要的意义。
因此,针对多线程编程,本专栏将详细地介绍 Windows 和 Linux 操作系统层面上提供的各种多线程接口,理解并熟悉它们的使用后,读者在接触或者学习其他语言或者库提供的多线程功能时,可以快速地上手和掌握。
以协程这一技术为例,虽然协程是计算机操作系统原理之一,但是我们所接触的大多数操作系统并没有从系统层面上支持协程这一技术。
线程是操作系统的内核对象,当多线程编程时,如果线程数过多,就会导致频繁地上下文切换,这些对性能是一种额外的损耗。
协程,是在应用层模拟的线程,它避免了上下文切换的额外损耗,同时又兼顾了多线程的优点,简化了高并发程序的复杂度。
再例如,线程局部存储技术是我们常用的一项多线程技术,它的存在让每个线程可以有自己私有存放数据的空间。那线程局部存储技术是如何实现的呢?本专栏中会庖丁解牛地介绍线程局部存储是什么以及它的实现原理。
正因为存在多线程编程,所以有了线程池模型,据我了解,“线程池”的实现和原理应该是多线程编程新手问的、聊的最多的一个技术点了。
本专栏会带领读者利用各种操作系统提供的线程同步对象来实现一个线程池,进而引出生产者消费者理论模型,再进一步升华,引出所谓的消息中间件,如 Kafka、RabbitMQ。
对于技术方案,我们不推崇重复造轮子,但是一定要具有重复造轮子的能力和了解轮子的制造原理。有了这项能力之后,在使用一些开源的消息中间件时,我们因为“知其然、知其所以然”才会把这些软件在项目中用得更好。
本专栏中会详细地介绍多线程操作整型变量非线程安全的原因以及解决方案。掌握了这些,你在学习像 Java 语言时,就明白了为什么 JDK 在操作一个整型变量时提供 AtomicInteger 这样的类了。
进程的 CPU 使用率过高如何查找原因并解决?
如何让一个程序只允许使用者运行一个实例?
在实际开发中,避免死锁有哪些可以遵循的规则?
什么是条件变量的虚假唤醒?虚假唤醒会带来什么问题?如何解决?
如何设计高效的线程池和队列模型?
如何在线程函数中访问类的成员变量和函数?
诸如此类实际开发中经常遇到问题,实在太多了,这里就不一一列举了。本专栏在保持主干脉络介绍的同时,也会穿插介绍一些与多线程相关的开发技巧和经验。
当然,多线程问题本来就比较复杂,尤其是本专栏同时介绍 Windows 和 Linux 两个操作系统平台的接口,在实际编写程序时,由于操作系统提供的 API 不一样,为了跨平台,我们不得不写许多跨平台代码。好在,C++ 11/14 标准给 C++ 引入了大量的多线程类和库,本专栏也会详尽地介绍它们的用法。
解析操作系统 API 层的多线程编程原理
展示 Windows 和 Linux 操作系统的基本原理
讲解线程间各种同步原语的适用场景、优缺点
贯穿实际开发中的问题定位与排查
彻底掌握多线程编程原理和编码经验
Windows 和 Linux 操作系统的基本原理
线程之间各种同步原语的适用场景和优缺点
相关编程惯用法和手段技巧
了解 Java、Go 等上层语言运行时环境提供的功能是如何基于操作系统 API 实现的
实际开发中一些问题定位与排查
建议读者将专栏各个章节中的代码示例都实际在机器上运行一遍,认真、准确地理解每一个多线程同步原语的适用场景和性能优缺点。只要透彻地理解了这些操作系统提供的基础多线程同步原语,在面对它们的衍生物(如线程池、消息队列、协程技术等)时可以更快地学习和用好。
操作系统提供的 API 接口一般在相当长的时间内会保持不变的(至少保持向后兼容),一经学会,终生受用。在新技术新思想层出不穷、令人应接不暇时,掌握了操作系统层面的 API 和其设计思想原理,可以以不变应万变。
最后,多线程编程在现代软件开发中是如此的重要,以至于熟练使用多线程编程是一名合格的后台开发人员的基本功,它是如此的重要,希望本专栏能帮助你掌握它,愿它能让你彻底告别多线程编程烦恼。