多线程与多进程
1.进程和线程的定义
进程线程的定义:进程可以理解为在操作系统中一个运行起来的程序(程序是指令、数据及其组织形式,进程是程序的实体),是操作系统进行资源分配的最小单位。线程是进程中的一个执行流,是操作系统进行系统调度的最小单位,一个进程由一个或多个线程组成。
2.多进程和多线程
多进程指的是操作系统同时执行多个进程,例如同时运行多个软件,多进程的引入也是为了更好的利用系统的资源,以此来提高系统的并发数量,在单位时间内处理更多的任务。而多线程的引入主要是为了降低系统的资源开销。
两者的区别:
- 每个进程都拥有独立的系统分配的资源,一个进程的崩溃通常对其他进程不会产生影响,具有较好的隔离性。因此在多进程通信中进程间的同步较为困难,常见的进程间同步的方式有(共享内存、管道、消息队列、套接字等)
- 线程共享进程的地址空间,只有自己的堆栈和局部变量,通常线程的崩溃会导致整个进程的崩溃。多线程共享容易,但是同步困难。
3.进程线程间的通信:
- 对于进程间的通信方式:
- 管道:数据只能单向流动,且需要有血缘关系。
- 命名管道:也只能单向流动,但是可以在没有血缘关系的进程间通信
- 消息队列:
- 共享存储区:映射一段能被其他进程所访问的内存,但是要注意数据的一致性问题。
- 信号量:通常作为一种锁机制来防止进程对共享资源的访问。
- 套接字: 既可以用于本机进程间的通信,也可用于不同主机间进程之间的通信。
- 信号:通知某一事件发生。
- 线程间的通信:主要用于在同一进程中的不同线程之间传递数据和协调工作。
- 线程互斥:互斥锁、读写锁、自旋锁、乐观锁、悲观锁。
- 互斥锁:对于临界区代码的访问需要进行加锁和解锁,使用lock和unlock,lock_guard(对象出了作用域会自动调用析构函数)等相关操作。互斥锁加锁失败后会释放占用的CPU资源,从用户态陷入内核态,由内核来进行线程的切换。线程的状态会经过由运行态-->阻塞态-->就绪态的一个转换。这样虽然让出了CPU的时间避免了CPU的长时间等待,但是内核态到用户态的消耗也是不可忽略的。
- 读写锁:分为读锁和写锁,写锁是互斥的,当写锁没有被线程持有时,多个线程都可以同时持有读锁,当读写被持有,则其余的读锁和写锁都将被阻塞。读锁是共享的,当一个线程持有读锁后,其他持有读锁的线程不会被阻塞。
- 自旋锁:与互斥锁不同的是,自旋锁加锁失败后不会放弃CPU的资源,它会一直循环等待,知道拿到锁,这样可以减少内核切换带来的开销。自旋锁通过操作系统提供的CAS函数完成加锁解锁的操作。
- 悲观锁:悲观锁认为多线程对同一资源的同时访问率较高,所以当进行资源访问时会先加锁。例如上面的互斥锁、读写锁、自旋锁。
- 乐观锁:乐观锁则认为冲突的概率是很低的,先进行访问操作,通过版本号来查看是否发生了冲突。
- 线程通信:条件变量、信号量。
- 条件变量:唤醒-等待的模式,条件变量通常用来阻塞一个线程,当满足某些条件时才会被唤醒,通常是与互斥锁一起使用。常见的操作就是wait()和notify()。
- 信号量:一种具有原子性的特殊变量,是一个引用计数,用来限制共享资源的并发连接数量,当值大于0时,资源计数减一,程序执行,信号量的值为0时,程序陷入等待。主要用来控制对公共资源的访问。
4.多线程的选择
- 对于IO密集型任务:这种任务下,CPU大部分时间都是在等在系统的读写操作(从磁盘、内存读取写入数据等),CPU一直处理闲置状,这时候可以用多线程来大大的提高系统处理的效率,当进行系统IO的时候,CPU切换到其他线程处理,当IO完成后,CPU再切换回来处理剩下的数据处理的任务。可以大大加速程序的运行,避免了CPU长时间的闲等。一般来说线程数与CPU核心数的关系为(线程数=CPU核心数/(1-阻塞系数)),这里的阻塞系数通常是一个泛化的值,一般在0.8~0.9之间。
- 对于CPU密集型任务:也指计算密集型任务,这种任务几乎占满了整个cpu的运行事件,对于单核的情况,没必要使用多线程,因为多线程的切换会带来额外的时间和资源消耗。对于多核心的,线程数=核心数为最佳,这样每个线程都有一个CPU核心来运行,同时也没有线程切换的开销,最大程度上使用了CPU。

浙公网安备 33010602011771号