书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)( 三 )


值得注意的是 , 有了线程这个概念后 , 我们只需要进程开启后创建多个线程就可以让所有CPU都忙起来 , 这就是所谓高性能、高并发的根本所在 。
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)
文章图片
很简单 , 只需要创建出数量合适的线程就可以了 。
另外值得注意的一点是 , 由于各个线程共享进程的内存地址空间 , 因此线程之间的通信无需借助操作系统 , 这给程序员带来极大方便的同时也带来了无尽的麻烦 , 多线程遇到的多数问题都出自于线程间通信简直太方便了以至于非常容易出错 。 出错的根源在于CPU执行指令时根本没有线程的概念 , 多线程编程面临的互斥与同步问题需要程序员自己解决 , 关于互斥与同步问题限于篇幅就不详细展开了 , 大部分的操作系统资料都有详细讲解 。
最后需要提醒的是 , 虽然前面关于线程讲解使用的图中用了多个CPU , 但不是说一定要有多核才能使用多线程 , 在单核的情况下一样可以创建出多个线程 , 原因在于线程是操作系统层面的实现 , 和有多少个核心是没有关系的 , CPU在执行机器指令时也意识不到执行的机器指令属于哪个线程 。 即使在只有一个CPU的情况下 , 操作系统也可以通过线程调度让各个线程“同时”向前推进 , 方法就是将CPU的时间片在各个线程之间来回分配 , 这样多个线程看起来就是“同时”运行了 , 但实际上任意时刻还是只有一个线程在运行 。
线程与内存
在前面的讨论中我们知道了线程和CPU的关系 , 也就是把CPU的PC寄存器指向线程的入口函数 , 这样线程就可以运行起来了 , 这就是为什么我们创建线程时必须指定一个入口函数的原因 。 无论使用任何编程语言 , 创建一个线程大体相同:
//设置线程入口函数DoSomethingthread=CreateThread(DoSomething);//让线程运行起来thread.Run;
那么线程和内存又有什么关联呢?
我们知道函数在被执行的时产生的数据包括函数参数、局部变量、返回地址等信息 , 这些信息是保存在栈中的 , 线程这个概念还没有出现时进程中只有一个执行流 , 因此只有一个栈 , 这个栈的栈底就是进程的入口函数 , 也就是main函数 , 假设main函数调用了funA , funcA又调用了funcB , 如图所示:
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)
文章图片
那么有了线程以后了呢?
有了线程以后一个进程中就存在多个执行入口 , 即同时存在多个执行流 , 那么只有一个执行流的进程需要一个栈来保存运行时信息 , 那么很显然有多个执行流时就需要有多个栈来保存各个执行流的信息 , 也就是说操作系统要为每个线程在进程的地址空间中分配一个栈 , 即每个线程都有独属于自己的栈 , 能意识到这一点是极其关键的 。
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)
文章图片
同时我们也可以看到 , 创建线程是要消耗进程内存空间的 , 这一点也值得注意 。
线程的使用
现在有了线程的概念 , 那么接下来作为程序员我们该如何使用线程呢?
从生命周期的角度讲 , 线程要处理的任务有两类:长任务和短任务 。
1 , 长任务 , long-livedtasks
顾名思义 , 就是任务存活的时间很长 , 比如以我们常用的word为例 , 我们在word中编辑的文字需要保存在磁盘上 , 往磁盘上写数据就是一个任务 , 那么这时一个比较好的方法就是专门创建一个写磁盘的线程 , 该写线程的生命周期和word进程是一样的 , 只要打开word就要创建出该写线程 , 当用户关闭word时该线程才会被销毁 , 这就是长任务 。
书圈|看完这篇还不懂高并发中的线程与线程池你来打我(内含20张图)


推荐阅读