进程:二进制程序的抽象,包括:加载的二进制程序,虚拟内存,内核资源(如打开的文件),关联用户等
线程:进程内的执行单元,包括:虚拟处理器,堆栈,程序状态。
进程是运行的二进制程序,线程是操作系统调度器可以调度的最小单元
现代os包括两种用户空间基础抽象:虚拟内存 虚拟处理器
虚拟内存是进程相关的,与线程无关。所以每个进程有独立的内存空间,进程内所有线程共享这份空间。
虚拟处理器是线程相关的,与进程无关,每个线程都是独立调度的实体,支持单个进程并行处理多个工作。
总之,虽然线程效果可以使用其他技术实现,但是多核处理器的发展让线程无可替代。
进程间切换 开销大于 进程内切换,
在非linux系统上非常明显,在linux系统上,进程切换代价不高,但线程切换成本接近0.
计算机体系结构对进程切换有影响,但线程不存在此问题,因为进程切换涉及把一个虚拟地址空间切换到另一个虚拟地址空间。如x86系统上,当切换时,TLB必须清空。在某些场景下,TLB丢失对系统有极大影响,有些ARM机器上,必须把整个cpu缓存清空。但对于线程,不存在这些代价。因为线程到线程切换不会交出虚拟地址空间。
多线程的代价:bug。多线程程序的设计,编写,理解,调试 的复杂度远高于单线程。
多线程可以使用其他技术代替,这取决于使用多线程目的。
希望低延迟高IO吞吐,可以用IO多路复用,非阻塞IO,异步IO实现。这些技术支持进程并发IO操作,不阻塞进程。
希望实现并发,使用N个进程也可以同样利用处理器资源,除了增加资源消耗代价和上下文切换开销。
希望共享内存,linux提供了一些IPC,比起多线程,他们有更严格的方式共享内存。
虽然有很多技术能代替线程,但是上下文切换的优势是无法替代的。因此多线程很常见。如底层内核,高层GUI。
总之,若技术强则使用多线程,否则使用多进程。
比如ngx使用多进程(主进程做管理,子进程负责工作) envoy使用多线程(主线程做管理,其他线程做工作),结果envoy性能强于ngx。
当然apache也使用多线程,但他的线程模型是一个连接对应一个线程(同步IO),所以连接数多了,内存消耗大(一个线程增加8M内存消耗),而envoy使用多线程和异步IO,所以一个工作线程服务多个连接。
内核线程数和用户线程数 1:1.
即线程库通过 clone() 创建新线程,返回给进程,直接作为用户空间线程。
也叫用户级线程,即 N个线程映射到一个内核进程。
该模型由用户空间进行管理调度。
优点:上下文切换成本几乎为0
缺点:内核线程只有一个,无法利用多个处理器
太多复杂,已放弃
和用户线程模型类型,属于用户空间,但是不存在用户空间对协同程序的调度。
他们是协作式调度,支持显示放弃一个程序而去执行另一个(这里的程序指普通c/c++函数)。
协同程序更侧重于程序流控制,而不是并发。
linux本身不支持协同程序,可能是其上下文切换已经非常快,不需要比内核线程性能更好的结构。
Go为linux提供类似协同程序的支持。
核心多线程编程模式:
该模式又有两种工作方式:
该模式下使用同步IO
该模式通常需要设置线程数上限,若正在执行的线程数到达上限,新的连接可能被加入队列,也可能被拒绝服务,
直到正在执行的连接数下降到上限值以下。
该模式本身不需要线程,可用 进程 替换 线程。如 apache就这么做的。
增加线程是有固有成本的,主要是内核和用户空间栈。所以线程数必须有上限(尤其是32位系统)。
并且 线程数 超出系统处理器个数,并不会带来任何好处。
所以使用 IO多路复用和回调函数。
该模式本身也不需要线程。
事件驱动模式是设计多线程服务器的最佳选择,例如当前除了apache外,其他服务器都是事件驱动模式。
设计多线程系统时首先考虑事件驱动:异步IO,回调,事件循环,一个很小的线程池(每个处理器一个线程)
x++;
的汇编代码可能如下:
若两个线程并发执行x++,x=5,以下是可能的输出
正确的情况
脏数据的情况
所以即使对变量加一操作——一行代码,一旦有多个线程并发执行,都会充满竞争条件。
解决竞争的方法是 实现临界区原子性访问。
常见的方法是 锁。
锁的要领是 锁数据,而不是代码。 不要说锁保护这个函数,而是将数据和锁关联起来。
原因是 锁和代码关联时,锁的语义就很难理解,随着时间的变化,锁和数据之间的关系就不清晰了,程序员会引入新数据,而忘记相关锁。
把锁和数据关联,这个映射就会很清晰。
当两个线程互相持有对方需要的锁,就导致死锁。
要避免死锁,就要在 设计时 定义好锁,比如常见死锁为 ABBA ,即 一个线程获得A,一个线程获得B,然后死锁了。
解决方式是,必须总是先获得A,然后获得B.
linux内核只为线程提供底层原语clone,多线程库在用户空间。 posix实现为 Pthreads
pthreads标准是一堆文字描述,标准实现是glibc提供,glibc提供了两种phthreads实现:linuxThreads 和 NPTL
NPTL是优越的线程库,是linux2.6和glibc2.3中引入。
phtread API 定义了构建一个多线程程序的方方面面,有100多个接口,由于过于庞大和丑陋,有不少骂声,但是它依然是核心线程库,并且很多线程库都是构建在phtread上的。
原文:https://www.cnblogs.com/yangxinrui/p/15183501.html