操作系统为了程序的并发执行引入了进程的概念,提高了资源的利用率以及吞吐量。
在20世纪 60年代人们提出了进程的概念后,在OS中一直都是以进程作为能拥有资源和独立运行的基本单位的。
直到 20 世纪 80 年代中期,人们又提出了比进程更小的能独立运行的基本单位——线程(Threads)
试图用它来提高系统内程序并发执行的程度,从而可进一步提高系统的吞吐量。
简言之,进程的概念,使之能够并发执行多道程序,线程的概念让你更好地并发执行程序,一个是能不能的问题,一个是更好的问题。

线程与进程对比

线程概念的发展

线程概念简介 什么是线程 多线程上篇(七)-LMLPHP
进程概念提出的目的就是为了多道程序并发执行,并发过程中必然意味着不断地进程调度任务切换,但是他又是资源分配的独立单位,也就是说他要背着资源来回跑。
举个例子:
办公室内,每个人都有一台电脑,电脑就是资源
然后大家经常需要不断地变换座位位置(比如大家都是哪里需要去哪里,客服缺人了,销售就顶一个过去)
每个人都抱着自己的电脑来回的换位置方便?还是大家只是人员走动,电脑就使用那个位置的电脑方便?
进程也是有些类似的道理你带着这么多资源来回切换调度,必然会带来更多的时&&空开销
所以创建了线程的概念程序运行时所需的资源和程序的调度进行解耦
进程仍旧负责资源的独立分配,但是线程作为调度运行的独立单位,仅仅携带自身运行的必备的一丁点资源。

对比

线程具有许多传统进程所具有的特征,所以又称为轻型进程(Light-Weight Process)或进程元
相应地把传统进程称为重型进程(Heavy-Weight Process),传统进程相当于只有一个线程的任务。
在引入了线程的操作系统中,通常一个进程都拥有若干个线程,至少也有一个线程。
线程概念简介 什么是线程 多线程上篇(七)-LMLPHP
并发性
传统的OS系统,进程之间可以并发执行,引入线程概念的OS,不仅仅进程间可以并发执行,一个进程中的线程也可以并发执行,不同进程中的线程也可以并发执行
独立性
同一进程中的多个线程独立性比不同进程间的独立性差很多。
每个进程都是独立的地址空间和资源,同一进程下多线程他们共享进程下的资源,而且通常他们往往是用来相互合作的,每个线程都可以访问所在进程的所有地址空间,比如一个线程打开的文件,可以被其他线程读写。
调度性
传统OS,进程作为资源分配和调度分派的基本单位,进程是可以独立运行的基本单位,不过进程调度切换时空开销大
引入线程的OS,线程是运行调度和分派的基本单位,线程才是独立运行的基本单位,线程切换时,仅仅需要保存和设置少量寄存器内容,代价远远小于进程切换,不过需要注意是同一个进程内线程切换不会进程切换,但是不同进程中的线程进程切换,仍旧会导致进程切换。
拥有资源
进程拥有资源,并且作为系统中拥有资源的独立基本单位。
线程自身不拥有系统资源,仅仅拥有一点必不可少的,独立运行需要的资源,比如线程中的TCB。
除了自身的丁点儿资源外,共享所属进程的资源,同一个进程下所有线程,拥有相同的地址空间。
多处理器支持
传统进程(或者说单线程进程)只能运行于一个处理机上,不管有多少个处理机;
但是对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,并行运行
简言之,多线程可以让多核CPU充分发挥性能并行运行。
系统开销
进程和线程的创建撤销,系统都要为止分配和回收资源,比如内存空间、IO设备等,进程和线程的上下文切换,系统也都需要付出一定的时空开销。
但是,线程相关的开销明显小于进程。

线程简介

各线程之间也是存在资源共享和相互合作的,线程在运行时也是间断的,轮转切换的。
线程也是有运行状态的,这一点与进程并没有本质区别,最主要的状态也是就绪、执行、阻塞
进程的控制核心信息保存在PCB中,线程也有对应的组成---TCB,所有用于控制和管理线程的信息都保存在TCB中
 
线程概念简介 什么是线程 多线程上篇(七)-LMLPHP
 
线程尽管是另外一种完全不同的事物,但是毕竟是从进程的概念演化而来,也是操作系统对程序运行抽象的一部分,所以,线程必然与进程有着很多的相似点

线程实现

线程的实现主要有三种形式
  • 内核支持
  • 用户级线程
  • 另外就是二者的组合
线程概念简介 什么是线程 多线程上篇(七)-LMLPHP
从上面的分析中可以看得出来,内核支持和用户级都有各自明显的缺点和优点。
有些操作系统把用户级线程和内核支持线程两种方式进行组合,提供了组合方式ULT/KST 线程。
在组合方式线程系统中, 内核支持多KST线程的建立、调度和管理,同时,也允许用户应用程序建立、调度和管理用户级线程。
一些内核支持线程对应多个用户级线程,程序员可按应用需要和机器配置对内核支持线程数目进行调整,以达到较好的效果。
组合方式线程中,同一个进程内的多个线程可以同时在多处理器上并行执行,而且在阻塞一个线程时,并不需要将整个进程阻塞。
所以,组合方式多线程机制能够结合 KST和 ULT两者的优点,并克服了其各自的不足。 

线程的同步与通信

关于进程的同步与通信的相关逻辑原理,对于进程的同步与通信绝大多数都是适用的。
针对于这些原理,多线程OS也提供了多种同步机制,如互斥锁、条件变量、计数信号量以及多读、单写锁等。

信号量机制

进程中的信号量机制完全适合多线程同步
根据用法分为两种
  • 私用信号量(private samephore)
  • 公用信号量(public semaphort)
系统运行中,有多个进程,进程中又有多个线程。
如果是为了同一进程中多个线程同步设置的信号量,量属于特定的进程所有,这就叫做私用,OS并不知道私用信号量的存在。
如果是为了不同进程或者不同进程中的线程之间而设置的,就叫做公用。其数据结构是存放在受保护的系统存储区中,由OS为它分配空间并进行管理,故也称为系统信号量。

互斥锁(mutex)

互斥锁是一种比较简单的、用于实现线程间对资源互斥访问的机制
互斥锁可以有两种状态
  • 开锁(unlock)
  • 关锁(lock)
当一个线程需要读/写一个共享数据段时,需要对mutex进行上锁,离开时需要解锁。
上锁时,首先校验 mutex 的状态,如果它已处于关锁状态,则试图访问该数据段的线程将被阻塞;如果 mutex处于开锁状态,则将 mutex 上锁后便去读/写该数据段。
线程完成操作后,必须将 mutex 解锁,同时还需要将阻塞在该互斥锁上的一个线程唤醒,其它的线程仍被阻塞在等待mutex打开的队列上。
另外,为了减少线程被阻塞的机会,在有的系统中还提供了一种用于 mutex 上的操作命令 Trylock。
顾名思义,并不会因为无法进入而阻塞,若 mutex 处于上锁状态,则 Trylock 并不会阻塞该线程,而只是返回一个指示操作失败的状态码。

条件变量

在许多情况下,只利用 mutex 来实现互斥访问可能会引起死锁,比如A线程请求资源顺序为R1,R2,B线程请求资源顺序为R2,R1
如果A对mutex 1上锁成功进入临界区后,需要获取R2的锁mutex 2,可是此时B获得了资源R2,对mutex 2已经上锁,此时,A等待mutex 2 B等待mutex 1,形成了死锁
所以说,锁,应该是仅仅用于在条件成立时进行操作时的一个同步保障,而不能在整个过程中都依靠锁
可以借助于条件变量,就是条件
每一个条件变量通常都与一个互斥锁一起使用,单纯的互斥锁用于短期锁定,主要是用来保证对临界区的互斥进入。
而条件变量则用于线程的长期等待,直至所等待的资源成为可用的资源。

申请

Lock mutex                  
while (条件状态不满足) {
        wait(condition variable);//释放锁,线程挂起等待,直到条件满足通知;
}
临界区其他操作
unlock mutex; 

释放

Lock mutex
一些操作
unlock mutex;
wakeup(condition variable);
简言之,借助于条件变量用于控制长时间的等待,锁用于控制对资源的同步。

总结

本文对线程进行了非常简单的介绍,线程之于进程在很多的方面有着极其类似的逻辑,尤其是从调度的视角看。
毕竟线程就是对进程中关于调度部分的独立抽象。
只要能够理解进程和线程的目的就能够很好地理解他们相似的原因,因为都是操作系统对于程序运行的抽象描述,线程是进程的更加细粒度的掌控。
在换句话说就是操作系统的角度对程序的执行抽象为:“资源的分配”“调度”
最初这两个概念都是加诸于进程这个概念上,后续为了更加高效将两个概念进行了拆分,就是这样
所以说,对于原先介绍的进程的相关概念中关于调度部分的绝大多数理论,都是适用于线程概念的
02-07 21:41