1.进程是操作系统资源分配和调度的最小单位,实现操作系统内部的并发;线程是进程的子任务,cpu可以识别、执行的最小单位,实现程序内部的并发。
2.一个进程最少有一个线程或有多个,一个线程只能属于一个进程,线程依赖于进程存在
3.进程有独立的地址空间,线程有自己的堆栈和局部变量,多个线程可以共享同一地址空间。
4.进程的创建、切换、销毁的系统开销都远大于线程。创建或销毁进程时,系统要分配或回收相应的地址空间和io资源。切换时要保存整个cpu环境,还要配置新环境。而线程切换只保留少量寄存器内容。
5.线程间的通信方式很简单,因为它们共享了地址空间。但是要保证互斥、同步的操作保证数据的一致性。
6.一般一个进程挂掉并不会影响别的进程,但是一个线程挂掉,它所在的进程也会挂掉。
?1. 线程是操作系统的资源,线程的创建、切换、停止等都非常消耗资源,而创建协程不需要调用操作系统的功能,编程语言自身就能完成,所以协程也被称为用户态线程,协程比线程轻量很多;
?2. 线程在多核环境下是能做到真正意义上的并行,而协程是为并发而产生的;
?3. 一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行;
?4. 线程进程都是同步机制,而协程则是异步;
?5. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力;
?6. 操作系统对于线程开辟数量限制在千的级别,而协程可以达到上万的级别。
因为多线程共享了同一片地址空间,所以线程间通信很简单,只需要将数据复制到共享(全局或堆)变量中即可。不过要考虑同步和互斥,保证数据的一致性。用到的技术有:
1.信号:Linux 中使用 pthread_kill() 函数对线程发信号
2.互斥锁、读写锁、自旋锁。
互斥锁mutex——保证同一时间只能有一个线程访问共享资源,当锁被占用时别的线程试图加锁都会进入阻塞状态(释放cpu资源使其从运行进入阻塞状态,当锁被释放时,调度哪个线程取决于内核的调度)。
读写锁rwlock——读共享,写互斥。写模式加锁时,其他不论读写试图加锁都阻塞。读加锁时,读进程不阻塞,写进程阻塞。
自旋锁spinlock——自旋锁上锁受阻时它不会阻塞,而是循环中轮询是否能获得锁,所以它不涉及线程切换所以没有切换开销,但是它霸占了cpu会浪费cpu资源。所以自旋锁适合并行结构(多处理器),或者锁被短时间持有又不希望有线程切换开销的情况。
3.条件变量:条件变量始终与互斥锁一起使用。条件变量以原子的方式阻塞进程直到达到某一特定条件,对条件的判断也是在互斥锁的保护下进行的。
4.信号量:信号量实际是一个非负的整数计算器,用于对公共资源的控制。公共资源增加信号量加1,减少减1。并且只有信号量的值大于0的时候才可访问。
p是对信号量的值进行原子减一,代表获取资源,当信号量的值为0时,p操作会阻塞,意味着资源不可用。
v操作是对信号量的值进行原子加一,代表释放资源,v操作从不阻塞。
fork :在进行 fork 复制进程时,并不马上进行父进程的地址空间的完全拷贝。而是使用了写时拷贝 (Copy-On-Write )技术,即就是让父进程和子进程共享父进程的页面,当父进程或子进程中任意一个 进程试图修改某个页面时,再将其拷贝一份给子进程。这样可以延迟页面拷贝,提高 fork 复制的效 率。另外,通常 Linux 中的新进程都是通过 fork+exec 实现的,如果 fork 后需要执行 exec 那么直接就不需要拷贝了。这里有一个小细节: fork 之后内核会将子进程排在队列的前面,以让子进程先执行,以免父进程执行导 致写时复制,而后子进程执行 exec 系统调用,因无意义的复制而造成效率的下降。vfork :除了子进程必须要立刻执行一次对 exec 的系统调用,或者调用 _exit( ) 退出,对 vfork( ) 的成功调 用所产生的结果和fork( ) 是一样的。 vfork( ) 会挂起父进程直到子进程终止或者运行了一个新的可执行文 件的映像。通过这样的方式,vfork( ) 避免了地址空间的按页复制。在这个过程中,父进程和子进程共享 相同的地址空间和页表项。实际上vfork( ) 只完成了一件事:复制内部的内核数据结构。因此,子进程也 就不能修改地址空间中的任何内存。
1、段主要为了内存独立/隔离,有利于共享;
2、页与物理内存映射,解决碎片问题,提升效率;
3、找到段,找到页表起始地址,找到页帧号,得到物理地址
io复用使得一个程序能监听多个文件描述符,提高程序的性能。
Linux系统实现io复用的技术有select,poll,epoll
1. 用户态将文件描述符传入内核的方式2.select :创建 3 个文件描述符集并拷贝到内核中,分别监听读、写、异常动作。这里受到单个进程可以打开的fd 数量限制,默认是 1024 。poll :将传入的 struct pollfd 结构体数组拷贝到内核中进行监听。epoll :执行 epoll_create 会在内核的高速 cache 区中建立一颗红黑树以及就绪链表 ( 该链表存储已经就绪 的文件描述符) 。接着用户执行的 epoll_ctl 函数添加文件描述符会在红黑树上增加相应的结点。内核态检测文件描述符读写状态的方式select :采用轮询方式,遍历所有 fd ,最后返回一个描述符读写操作是否就绪的 mask 掩码,根据这个掩码给fd_set 赋值。 poll :同样采用轮询方式,查询每个 fd 的状态,如果就绪则在等待队列中加入一项并继续遍历。epoll :采用回调机制。在执行 epoll_ctl 的 add 操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/ 可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。3. 找到就绪的文件描述符并传递给用户态的方式select :将之前传入的 fd_set 拷贝传出到用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。poll :将之前传入的 fd 数组拷贝传出用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。epoll : epoll_wait 只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量。内核将就绪的文件描述符放在传入的数组中,所以只用遍历依次处理即可。这里返回的文件描述符是通过mmap 让内核和用户空间共享同一块内存实现传递的,减少了不必要的拷贝。4. 重复监听的处理方式select :将新的监听文件描述符集合拷贝传入内核中,继续以上步骤。poll :将新的 struct pollfd 结构体数组拷贝传入内核中,继续以上步骤。epoll :无需重新构建红黑树,直接沿用已存在的即可。epoll 更高效的原因1. select 和 poll 的动作基本一致,只是 poll 采用链表来进行文件描述符的存储,而 select 采用 fd 标注位来存放,所以select 会受到最大连接数的限制,而 poll 不会。2. select 、 poll 、 epoll 虽然都会返回就绪的文件描述符数量。但是 select 和 poll 并不会明确指出是哪些文件描述符就绪,而epoll 会。造成的区别就是,系统调用返回后,调用 select 和 poll 的程序需要遍历监听的整个文件描述符找到是谁处于就绪,而epoll 则直接处理即可。3. select 、 poll 都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而 epoll 创建的有关文件描述符的数据结构本身就存于内核态中,系统调用返回时利用mmap() 文件映射内存加速与内核空间的消息传递:即epoll 使用 mmap 减少复制开销。4. select 、 poll 采用轮询的方式来检查文件描述符是否处于就绪态,而 epoll 采用回调机制。造成的结果就是,随着fd 的增加, select 和 poll 的效率会线性降低,而 epoll 不会受到太大影响,除非活跃的socket很多。5. epoll 的边缘触发模式效率高,系统不会充斥大量不关心的就绪文件描述符虽然 epoll 的性能最好,但是在连接数少并且连接都十分活跃的情况下,select 和 poll 的性能可能比 epoll 好,毕竟 epoll 的通知机制需要很多函数回调。
1. 物理内存 以前,还没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围是有限的,这取决于 CPU 的地址线条数。比如在 32 位平台下,寻址的范围是 2^32 也就是 4G。并且这是固定的,如果没有虚拟内存,且每次开启一个进程都给 4G 物理内存,就可能会出现很多问题: - 因为物理内存是有限的,当有多个进程要执行的时候,都要给 4G 内存,很显然内存不够,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完了以后,再将等待的进程装入内存。这种频繁的装入内存的操作效率很低 - 由于指令都是直接访问物理内存的,那么任何进程都可以修改其他进程的数据,甚至会修改内核地址空间的数据,这是不安全的
2. 虚拟内存 由于物理内存有很多问题,所以出现了虚拟内存。虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
?死锁:两个或几个进程在运行时由于资源争夺处于互相等待的过程,如无外力干涉,它们都不会有改变。
产生的原因:系统资源不足、资源分配不合理、进程运行推进方式不合理
必要条件:
互斥条件——一个资源每次只能给一个进程使用
请求与保持条件——一个进程在请求资源时,对已获得的资源保持不变
不可剥夺条件——对于已获取资源的进程,不可强行剥夺其资源
循环等待条件——若干进程处于一种头尾相接的循环等待的过程
条件变量是线程同步的一种方式,这种方式下有两种状态的线程:等待条件变量成立而挂起的线程和条件变量成立。为了避免竞争,所以条件变量总是和互斥锁一起使用。
条件变量可以以原子的方式阻塞线程,直到满足条件变量为真为止。
条件变量为假时线程阻塞并以原子方式释放等待条件变化的互斥锁。当另一个线程条件改变时,该线程可能会向相关原子变量发出信号,于是几个处于等待的线程将会唤醒,获取互斥锁,然后评估条件。