os提供了很多底层的重要函数,我们的程序调用这些底层的函数,被称为系统调用,举例:
为了保证安全性,我们并不能随意访问系统的资源,系统函数就是为用户提供的一种访问系统资源的安全的方式。
os通过划分内核态和用户态来达到这一隔离的目的,内核态可以访问任何数据,用户态不能访问内核数据。如Linux输入一个whoami命令,其实这个whoami就是一个系统函数,我们只是通过用户态去系统调用内核态的函数。
内核态可以访问任何数据,用户态不能访问内核数据,这一分层都是依靠硬件实现的,虽然都是在内存中但是把内存划分成了核心段和用户段,不能随意访问。
用户发起系统调用内核的方法的唯一方式就是中断指令int,达到安全性的目的。
其中用户态的whoami方法中包含了中断指令int的代码,将进入内核态调用内核态的whoami方法
为了提高cpu的利用率,不至于一直死等io操作(IO操作比计算操作要耗费的时间多得多),一个cpu应该交替的执行多个程序,称为并发。这些执行的程序被称为进程。cpu并发的执行不同的进程,需要进行上下文切换,记录进程的状态。操作系统依赖PCB(进程控制块)感知线程。
如打开cmd,初始化只有一个shell进程,当我们输入ifconfig命令时,系统就会执行fork打开一个新进程去执行该命令。
操作系统就是以这样的形式对多进程进行管理控制的。
进程间的切换是使用os中schedule()这个函数,schedule()中通过查询就绪态选择进程进行调度,具体依赖调度算法选择合适的一个,然后进行上下文切换。
每个进程都有它的内存映射表,使得进程之间的数据隔离。当切换进程时也需要切换对应的内存映射表。
在一个进程之中分出多个指令执行序列、但这些指令序列又共享一个进程中的资源,这些指令序列称为线程,线程间切换代价比较小,不需要切换映射表,只是指令间的切换即可,比较轻量级。
线程(thread):保留了并发的优点,避免了进程切换的代价。实质就是映射表不变而PC指针变
线程间的切换是依靠TCB(线程控制块)实现的
如:网页浏览器访问一个网站时,一开始在浏览器这个进程里create多个线程,一个线程用来接收服务器的数据,一个线程用于显示文本,一个线程处理图片,一个线程用来显示图片,这几个线程互相切换的执行。当接收数据的线程接收到数据后可以调用yield函数释放cpu的使用权给到其他的线程并发的执行多个线程……
这种方式主要是使用yield实现了多线程的并发运行,create和yield都是用户程序(yield的核心代码就是在多线程间跳转),执行的时候不需要访问内核,所以使用这种方法实现的多线程叫做用户级线程。
用户级线程内核是感受不到的,这就导致了如果一个进程中的其中一个线程阻塞了,该进程也会被阻塞(包括进程中的其他线程)。
如上图:当进程一中的接收数据的线程执行时需要进行读写网卡的io操作了,相应的进程一就会进入内核态(因为要访问硬件了),此时io操作花费的时间会比较长,所以进程一进入到阻塞态等待io操作完成,此时cpu(cpu感知不到进程一还有其他线程要执行)就会去执行进程二,所以进程一中的其他线程本来是可以继续执行的,但是因为一个线程而导致了进程被阻塞而得不到执行。所以一些浏览器已经从多线程改为了多进程的方式,每打开一个标签就是一个进程。
计算密集型的,用户级线程就可以,因为不需要IO操作
核心级线程的线程创建(thread_create)是一个系统调用,创建线程会进如到内核,TCB在内核中。当线程进行切换的时候,由用户态转化为内核态,切换完毕要从内核态返回用户态。
如上图:当进程一的某个线程阻塞了,cpu知道进程一还有其他线程,并不会阻塞进程,其他线程也会有机会得到运行,也会被调度,这种内核级线程的调度叫做schedule而不叫yield。
内核级线程的并发性要更好一些。
多核cpu不像多处理cpu一样有多个内存映射表(MMU),它只有一个,多个执行序列用共同一个映射表,这其实就是线程。我们再看看核心级线程图,如果想让多个线程分配到多个cpu核心上,就需要使用核心级线程,才能充分的使用到多核心cpu,因为用户级线程cpu没法感知到,没法分配硬件给它,用户级线程只能以进程的方式得到利用。
区别:
内核线程的优点:
缺点:
用户线程的优点:
缺点:
轻量级进程(LWP)是建立在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。
可以看到图中:轻量级线程可以由用户管理,但是每一个线程又需要一个内核支持,所以轻量级线程就是对内核级线程的一种优化。
由于每个LWP都与一个特定的内核线程关联,因此每个LWP都是一个独立的线程调度单元。即使有一个LWP在系统调用中阻塞,也不会影响整个进程的执行。
轻量级进程具有局限性。
首先,大多数LWP的操作,如建立、析构以及同步,都需要进行系统调用。系统调用的代价相对较高:需要在user mode和kernel mode中切换。
其次,每个LWP都需要有一个内核线程支持,因此LWP要消耗内核资源(内核线程的栈空间)。因此一个系统不能支持大量的LWP。
- LWP的术语是借自于SVR4/MP和Solaris 2.x。
- 有些系统将LWP称为虚拟处理器。
- 将之称为轻量级进程的原因可能是:在内核线程的支持下,LWP是独立的调度单元,就像普通的进程一样。所以LWP的最大特点还是每个LWP都有一个内核线程支持。
协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换代价很小,从广义上分的话可以说携程就是用户级线程,不用内核支持。
携程就是程序员自己写的,自己在内存中维护一个堆栈,它能够在自己的程序中实现多线程并发的运行,即使你的os不支持多线程,你也可以使用携程模拟多线程。
在有大量IO操作业务的情况下,我们采用协程替换线程,可以到达很好的效果,一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。
在协程中尽量不要调用阻塞IO的方法,比如打印,读取文件,Socket接口等,除非改为异步调用的方式,并且协程只有在IO密集型的任务中才会发挥作用。
协程只有和异步IO结合起来才能发挥出最大的威力。
现如今主流的多线程闻名的语言如golang,erlang都是采用的携程。