问题
虽然现在我对多线程编程已经具有比较深入的概念和应用了,但关于在实际应用中使用多线程相关的知识和能力基本都是从2016年开始,在工作中有需求后,才陆续由自己学习和掌握的。鉴于多线程的重要性,我一直觉得在大学中掌握相关技能是十分重要的。这些年来一直有个问题困扰着我,为什么计算机科学与技术专业毕业的我,尽管在学校已经有意识的去理论结合实际展开实践了,但直到在真正工作后才掌握了这部分的技能?
思考
多线程编程学习历程
先回顾一下我自己接触多线程编程的历程吧。
最开始是在2016年左右,公司希望使用C++做跨平台原生音视频SDK,当时新入职的同事建议使用C++11来做。当时作为毕业近两年,使用ActionScript做基于Flash的音视频客户端已有一年多经验,成功使用nodejs和ffmpeg和C语言构建了生产用RTMP转HLS的服务端推流系统,同时具备客户端和服务端音视频业务经验的我来说,C++11完全是个新东西,我真的是没有一点概念。也是吃了不懂C++多线程编程这方面的亏,后续也就没能为做客户端音视频C++ SDK直接贡献代码,也没能实现当时将原生功能支持层与基于脚本的业务层分开的想法(脚本+脚本运行时+原生运行环境,这个想法到现在也没有能够完全实现,一定程度上也是因为必要性不足,同时也是体系力量不足以做这件事情)。
后来C++音视频SDK逐渐深入应用,出现了不计其数的多线程相关的问题。基于我之前在大学储备的操作系统相关理论,也结合当时阅读更深入的材料,还有不断的反复尝试排查,终于一次又一次的解决了各种疑难杂症。
2016~2018年,大多数掌握C++11的工程师都是初学者,都仅有很少的经验
而在解决了问题之后,我逐步深刻意识到了掌握多线程编程的必要性,也开始系统性的阅读了C++11关于多线程处理的部分(主要是《C++并发编程实战》),还有多线程编程相关的其他书籍。另外在理论储备上也增加了并行计算相关的内容。另外针对音视频的特点,也涉猎了异构计算相关的内容。
当读到《C++并发编程实战》的时候,仿佛打开了一扇新的大门。之前对于线程的概念仅限于可以为单个进程增加里用到多核CPU的能力这一点,而面对多线程下的编程模型和实践方法,其实并没有了解。之前困扰自己的很多问题,一夜之间都解开了。
接下来的问题就是,给我带来了这么多教训的多线程编程,为何在之前学习中缺失了呢?
隐身的多线程编程
说起多线程编程,C和C++应该是与之相关很紧密的语言。
我接触C与C++的时间不是很早,但也不是很晚。严格讲,我在高中期间使用谭浩强的书已经很仔细的学习了C语言,但是到上机的时候,我仍然是一脸懵逼,不知道该怎么办。因为谭浩强的书虽然讲了语法和概念,但完全没告诉你怎么实操!在没有专业指导老师,身边也没有从事相关行业的家长和亲戚的情况下,是很难自己从零开始的!
所以我在高中阶段的这段经历,给了我一个很深刻的教训,就是学习计算机知识一定要找可以实操的书籍。进入大学后,学校讲C/C++的时候(2011年),我就发现学校提供的教材是不行的。因为它完全没有实操性。所以我就到当地最大的新华书店,在计算机书籍区域,找了一本有实操性的自学书籍,Ivor Horton的《Visual C++ 2005入门经典》。然后我趁着大一寒假,把这本书里的代码从头到尾对着书全部敲了一遍。在那个学校老师只讲理论,家长也帮不上忙的时候,这本书真可谓是我的大救星。它让我真正通过带着我从Visual C++ 2005这个IDE上创建项目开始,从运行与调试教起,从简单到难,一步一步真正掌握了C++语言(当时的想法是这样)。该书虽然帮助我成功入门了面向过程编程与面向对象编程,但是对于多线程方面的内容只字未提。。。
这里面的原因我想有两点,一点是说这是面向初学者的书籍,讲多线程编程确实不太合适。另外就是这本书是2005年出的,我看的时候是2011年,当时C++11严格讲才刚标准化,而直到C++11后,多线程编程才作为一个特性内置到一门语言中,所以这部分内容就更不可能写入到C++的书籍中了。
最近我又看了这本书的后续版本,直到《Visual C++ 2012入门经典》才涉猎了一点多核编程的内容,而且是以Windows API编程的方式引入的。《Visual C++ 2013入门经典》又删去了这部分。另外一点,VC系列直到2015才正式支持C++11,看来我指望通过这部经典的书来学多线程编程是没戏的了 🙁
当时大家传的很火的学语言的书籍,并且直到现在,像什么《C Primer Plus》,还有《C++ Primer》(第四版,第五版),这些涉及到语言学习的书籍,都完全没有涉及到关于多线程的内容,其中部分还是在C11和C++11之后出版的。这些书基本都是偏重于语言本身的特性,并且一定程度上仍然认为多线程是系统特性,不提相关内容。这让自学者真的是太难接触到多线程编程的内容了。
而当时在大学的时候(2010~2014),虽然操作系统课程有讲到线程,PV操作,死锁,互斥量,信号量等,但这些完全是概念上做理解,虽然我当时理解了,但是因为没有经过实操,也不知道怎么接触实操,所以完全不知道这些东西和实际编程有什么关系,如何去使用。
殊不知,在2011年左右,差不多正好是我学C++的时候,C++11成为正式标准,C++也成为了正式支持多线程编程的语言。如果当时我能接触到多线程编程,在工作的前三年,不出意外我会做的更好。
最近我搜索了多线程书籍,除了专门讲多线程的之外,只有面向Linux的多线程编程,或是面向Windows的多线程编程书籍,可以说和语言真的是无缘了。
多线程编程像是隐身了一般,消失在语言相关的书籍中。
多线程的历史
最近慢慢的对一些东西有了概念。就是哪些是语言内置的API,哪些是操作系统API,分得越来越清楚了。这让我对于多线程的历史产生了兴趣。
多线程的起源
多线程并不是一开始就有的。经过搜索,操作系统的多线程基本上可以确定起源于1995年
- Windows 95
- Linux kernel 2.4
- Unix(support POSIX.1c)
这是有原因的。因为那一年开始,随着IEEE Std 1003.1c-1995的发布,线程正式标准化。
之前如果想做单进程内的多线程编程,基本上只能用主动放弃时间片的协程模型。
这个时间比我预想的要晚。看来如果一个工程师说自己多线程编程经验,那最多有26年 🙂
多线程的发展
从POSIX.1c之后,pthread API就成为了通用多线程编程的唯一指定API,加上Win32 Thread,覆盖了几乎所有平台。而多线程也成为了操作系统提供API的一部分。
然后不管是C语言还是C++语言,都可以调用多线程API来进行多线程编程。
有了操作系统API的支持,好像多线程编程的问题就解决了。然而事实真的如此么?
虽然有了操作系统的多线程API,我们确实可以进行多线程编程了,但这不代表仅仅依靠操作系统的API+语言就能把多线程编程做好。当引入了多线程,就出现了许多奇奇怪怪不是问题的问题,比如变量的可见性问题,代码自然顺序失效的问题,方法是否可重入的问题,方法是否线程安全的问题。另外还有就是开线程只是手段,编程人员真正想要的是实现「异步执行任务」。「能够进行多线程编程」和「以合理的成本做好多线程编程」是完全不同的两个概念。这两者的差异,几乎只能通过「编程经验」来解决。而编程经验的累积,成本是非常高的。
语言内置多线程
这些年来,多核处理器成为主流,应用也越做越深,多线程编程越来越普遍,应用范围越来越广。对多线程编程人员的大量需求和多线程编程人才的稀缺完全不相适应,这使得降低多线程编程的门槛势在必行。通过语言内置多线程编程,便可以大大降低多线程编程与应用的成本,让更多人能够更容易的掌握多线程编程。
值得高兴的是,近些年来多线程编程已经纳入语言的考虑范围了,越来越多的语言内置了多线程编程。这些语言通过内置多线程功能,通过各种限制和范式,规范了多线程编程的方法,降低了多线程编程出错的可能性。这是一个革命性的进步。
C++11,不用考虑任何平台问题,直接调用其API进行编程即可。它已经成为了事实上跨平台应用开发的标准。
为什么C11不能成为跨平台应用的开发标准语言? 因为在C11中多线程并不是必须要实现的功能,并且大多数编译器并没有实现C11的线程API。毕竟C语言作为更底层的语言,不会考虑将开发「大型应用」作为主要目标。
Golang(2009年正式发布)实现了自己的m:n的线程模型,通过语言规范了多线程开发的范式,大大降低了多线程开发的门槛。Rust(2010年正式发布)也支持多线程,并且增强了所有权和生命周期的管理,使之可以更安全的进行多线程编程。
总结
相信以后,越来越多的语言书籍会开始介绍多线程编程,因为它已经成为了语言的一部分。自然在学校学习编程语言时,即使是自学,也会更容易的接触到这部分内容。
作为一个多线程编程时代变迁的夹缝中人,在校期间错过自学多线程编程,毕业后却因行业需求被迫痛苦上手多线程编程,而后多线程编程也逐渐标准化与语言内置化让自学成为可能,这真是一段特殊而有趣的经历呢 🙂