线程(
Thread
);与进程类似,线程是允许应用程序并发执行多个任务的一种机制,线程参与系统调度,事实上,系统调度的最小单元是线程、而并非进程。
一、线程概念
线程是参与系统调度的最小单位。它被包含在进程之中,是进程中的实际运行单位。一个线程指的是进程中一个单一顺序的控制流(或者说是执行路线、执行流),一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务。譬如某应用程序设计了两个需要并发运行的任务 task1
和
task2
,可将两个不同的任务分别放置在两个线程中。
线程是如何创建起来的?
当一个程序启动时,就有一个进程被操作系统(
OS
)创建,与此同时一个线程也立刻运行,该线程通 常叫做程序的主线程(Main Thread
),因为它是程序一开始时就运行的线程。应用程序都是以
main()
做为 入口开始运行的,所以 main()
函数就是主线程的入口函数,
main()
函数所执行的任务就是主线程需要执行的任务。
所以由此可知,任何一个进程都包含一个主线程,只有主线程的进程称为单线程进程。所谓多线程指的是除了主线程以外,还包含其它的线程,其它线程通常由主线程来创建(调用pthread_create 创建一个新的线程),那么创建的新线程就是主线程的子线程。
主线程的重要性体现在两方面:
⚫
其它新的线程(也就是子线程)是由主线程创建的;
⚫
主线程通常会在最后结束运行,执行各种清理工作,譬如回收各个子线程。
线程的特点:
线程是程序最基本的运行单位,而进程不能运行,真正运行的是进程中的线程。当启动应用程序后,系 统就创建了一个进程,可以认为进程仅仅是一个容器,它包含了线程运行所需的数据结构、环境变量等信息。
线程具有以下一些特点:
⚫
线程不单独存在、而是包含在进程中;
⚫
线程是参与系统调度的基本单位;
⚫
可并发执行。同一进程的多个线程之间可并发执行,在宏观上实现同时运行的效果;
⚫
共享进程资源。同一进程中的各个线程,可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址; 此外,还可以访问进程所拥有的已打开文件、定时器、信号量等等
选择多线程还是多进程?
进程创建多个子进程可以实现并发处理多任务(本质上便是多个单线程进程),多线程同样也可以实现 (一个多线程进程)并发处理多任务的需求,那我们究竟选择哪种处理方式呢?首先我们就需要来分析下多进程和多线程两种编程模型的优势和劣势。
多进程编程的劣势:
⚫
进程间切换开销大。多个进程同时运行(指宏观上同时运行,无特别说明,均指宏观上),微观上依然是轮流切换运行,进程间切换开销远大于同一进程的多个线程间切换的开销,通常对于一些中小型应用程序来说不划算。
⚫
进程间通信较为麻烦。每个进程都在各自的地址空间中、相互独立、隔离,处在于不同的地址空间中(因为每个进程逻辑上都有4G的虚拟内存空间,而虚拟内存空间对应的物理地址是随机的),因此相互通信较为麻烦
解决方案便是使用多线程编程,多线程能够弥补上面的问题:
⚫
同一进程的多个线程间切换开销比较小。
⚫
同一进程的多个线程间通信容易。它们共享了进程的地址空间,所以它们都是在同一个地址空间中,通信容易。
⚫
线程创建的速度远大于进程创建的速度。
⚫
多线程在多核处理器上更有优势!
综上所述,多线程编程相比于多进程编程的优势是比较明显的,在实际的应用当中多线程远比多进程应 用更为广泛。那既然如此,为何还存在多进程编程模型呢?难道多线程编程就不存在缺点吗?当然不是,多线程也有它的缺点、劣势,譬如多线程编程难度高,对程序员的编程功底要求比较高,因为在多线程环境下 需要考虑很多的问题,例如线程安全问题、信号处理的问题等,编写与调试一个多线程程序比单线程程序困难得多。
二、并发与并行
并行指的是可以并排
/并列执行多个任务,这样的系统,它通常有多个执行单元,所以可以实现并行运行。并行运行并不一定要同时开始运行、同时结束运行,只需满足在某一个时间段上存在多个任务被多个执行单元同时在运行着。
相比于串行和并行,并发强调的是一种时分复用,与串行的区别在于,它不必等待上一个任务完成之后在做下一个任务,可以打断当前执行的任务切换执行下一个任何,这就是时分复用。在同一个执行单元上, 将时间分解成不同的片段(时间片),每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样 轮训(交叉/
交替执行),这就是并发运行。如下图所示:
总结:
⚫
串行:一件事、一件事接着做
⚫
并发:交替做不同的事;
⚫
并行:同时做不同的事。
三、线程ID
就像每个进程都有一个进程
ID
一样,每个线程也有其对应的标识,称为线程
ID
。进程
ID
在整个系统中是唯一的,但线程 ID
不同,线程
ID
只有在它所属的进程上下文中才有意义。
线程
ID
在应用程序中非常有用,原因如下:
⚫
很多线程相关函数,譬如
pthread_cancel()
、
pthread_detach()
、
pthread_join()
等,它们都是利用线程 ID
来标识要操作的目标线程;
⚫
在一些应用程序中,以特定线程的线程
ID
作为动态数据结构的标签,这某些应用场合颇为有用, 既可以用来标识整个数据结构的创建者或属主线程,又可以确定随后对该数据结构执行操作的具体线程。