1.进程与线程
1.1 基本概念
- 进程:对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发
- 线程:进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
1.2 进程与线程的区别
- 一个线程只属于一个进程,而一个进程中可以有多个线程,但至少有一个线程,线程依赖于进程而存在.
- 进程执行过程中拥有独立的内存单元,而多个线程共享进程的内存.(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量.)
- 进程是资源分配的最小单位,线程是CPU调度的最小单位.
- 由于在创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、IO设备等.因此,操作系统所付出的开销将显著地大于创建或撤销线程时的开销.类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置.而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作.可见,进程切换的开销远大于线程切换的开销.
- 由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现也变得容易.进程间通信IPC,线程间可以直接读写进程数据段(如 全局变量)来进行通信——需要同步或互斥手段的辅助,以保证数据的一致性.在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预.
- 进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂.
- 进程间不会相互影响,线程中一个线程挂掉可能导致整个进程挂掉.
- 进程适应于多核、多机分布,线程适用于多核.
1.3 进程间的通信方式
进程间的通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)以及套接字socket.
1.3.1 管道
管道主要包括匿名管道和命名管道.管道可用于具有亲缘关系的父子进程间的通信.命名管道除了具有管道所具有的所有功能外,它还允许无亲缘关系的进程间的通信.
- 匿名管道PIPE:
- 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端.
- 它只能用于具有亲缘关系的进程间通信(父子进程或兄弟进程).
- 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数.但它不是普通的文件,并不属于其他任何的文件系统,并且只存在于内存中.
- 命名管道FIFO:
- FIFO可以在无关的进程中交换数据.
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中.
1.3.2 系统IPC
-
消息队列:
消息队列是消息的链接表,存放在内核中.一个消息队列由一个标识符(即队列ID)来标记(消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限的进程可以按照一定的规则向消息队列中添加新的信息.对消息队列有读权限的进程可以从中读取信息.
特点:
- 消息队列是面向记录的.其中的消息具有特定的格式和特定的优先级.
- 消息队列独立于发送和接受进程.进程终止时,消息队列中的内容并不会被删除.
- 消息队列可以实现消息的随机查询.消息不一定要以先进先出的次序读取,也可以按消息的类型读取.
-
信号量:
信号量与已经介绍过的IPC结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问.信号量用于实现进程间的互斥同步,而不是用于存储进程间的通信数据.
特点:
- 信号量用于进程间同步,若要进程间传递数据需要结合共享内存使用.
- 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作.
- 每次对信号量的PV操作不仅限于对信号量值加1或减1,而且可以加减任意正整数.
- 支持信号量组
-
信号:
信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生.
-
共享内存:
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新.这种方式需要依靠某种同步操作,如互斥锁和信号量等.
特点:
- 共享内存是最快的一种IPC,因为进程是直接对内存进行读取.
- 因为多个进程可以同时操作,所以需要进程同步
- 信号量+共享内存通常要结合在一起使用,信号量用来同步对共享内存的访问.
1.3.3 套接字
socket也是一种进程间通信机制.与其他通信机制不同的是,它可以用于不通主机之间的通信
1.4 线程间通信方式
- 临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问.
- 互斥量Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问.
- 信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目.
- 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作.
二、并发与并行
- 并发:多个进程在一个CPU下采用时间片轮转方式,在一段时间内,让多个进程得以推进,类似“雨露均沾”思想,一个CPU在短时间内会自动调度不同任务.(同一时间间隔内,多个事件交替发生)
- 并行:多个任务在多个CPU分别同时运行,这称之为并行.(同一时刻,多个事件发生)
三、线程的风险
- 线程安全性问题:多线程环境下,程序的运行结果与预期不符.(伴生现象:不容易复现)
- 线程的活跃度问题:
- 死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
- 饥饿:如果线程优先级“不均”,并且CPU繁忙的情况下,优先级低的线程得到执行的机会很小,就可能发生线程“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“饥饿”问题。饥饿嘛,线程一直得不到CPU时间,一直被饿着.
- 活锁:线程拿到锁,却又相互释放锁,不执行功能.
- 线程的性能问题:
- 消耗时间:线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失.
- 消耗CPU和内存:如果发生大量的线程被创建、执行和销毁,这可是非常耗CPU和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM.
- 容易导致GC频繁的执行:大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿.
- 线程的上下文切换:在线程调度过程中需要访问由操作系统和JVM共享的数据结构。应用程序、操作系统以及JVM都使用一组相同的CPU,在JVM和操作系统的代码中消耗越多的CPU时钟周期,应用程序的可用CPU时钟周期就越来越少。当一个新的线程被切换进来时,它所需要的数据可能不在当前处理器的本地缓存中,因此上下文切换将导致一些缓存缺失,因而线程在首次调度运行时会更加缓慢。
参考文章:
标签:Java,多个,队列,编程,基础知识,信号量,线程,进程,CPU From: https://www.cnblogs.com/cxz0214/p/16879515.html