进程与线程详解

【0】目录

【1】什么是进程?什么是线程?什么是并发?什么是并行?
【2】多进程和多线程的区别?
【3】.进程间通信方式以及优缺点?
【4】.进程、线程的基本状态及状态之间的关系?
【5】.多线程有哪些实现方式?
【6】.什么时候用多线程?什么时候用多进程?
【7】.线程同步和线程异步?
【8】.多线程同步和互斥有几种实现方法,分别适用什么情况?
【9】.条件变量与互斥锁、信号量的区别?
【10】Windows下线程同步方法:临界区,互斥量,信号量,事件

【1】什么是进程?什么是线程?什么是并发?什么是并行?

进程是资源分配的最小单位,有独立的地址空间和系统资源。
线程是cpu调度,程序执行的最小单位,一个线程只属于一个进程,而一个进程可以有多个线程,多个线程共享同一个进程的资源。在多核系统下允许几个线程各自独立的在处理器上运行,操作系统提供线程就是为了方便有效地实现这种并发性。
(多进程:同时运行QQ、微信、浏览器 多线程: 用浏览器同时进行浏览网页、播放视频、下载资源)
并发是把cpu运行时间划分成若干个时间段,每个时间段再分配给各个线程执行,当一个线程在运行时,其他线程处于挂起状态。
并行是同一时刻当一个cpu执行一个线程时,另一个cpu可以执行另一个线程,两个线程互不抢占cpu资源,是真正意义上的不同线程在同一时刻同时执行。
形象地解释:
进程是拥有一系列资源的集合,这些资源包括内存空间、内核对象、资源文件等等。我们将进程理解为一个工厂,工厂本身不能运作,需要有人来操作。那么这些工人就是线程,每一个工人操作自己的一台设备,这个设备就可以看成是线程的栈,他由这个工人自己使用。一个工厂里有多台设备时,如果只有一个人那么他就需要去一个个的去操作工厂里的设备,如果这些设备需要同时运行,那么这样操作效率太低。因此,工厂会多聘用几个工人,他们每个人操作自己的设备,这样效率就会大大提高。工人在操作设备时,可能两个人需要使用同一个工具,这个工具是全局的变量,因此他们可以共同访问,但是一个工人要去使用这个工具时,他会等在那里,等另一个人使用完,然后他就可以接过工具,继续干活了,这就是线程的同步。创建多个工厂就是多进程程序。工人操作的每台设备还是属于该工厂,因此线程是依附于进程的,占用进程的地址空间,线程之间也可以相互访问对方的地址,需要通过传址能实现,但是一般不会出现这样的情况,试想能有多大的机会在一个函数中访问另一个函数的的局部变量。在代码的实现中,我们可以将线程仅仅看成一函数去分析,只不过他是并发进行的。
在上述的论述中,工厂就是进程,工人就是线程,工厂所占的位置就是进程空间,工厂里的设备和工具就是数据和资源,多个工人同时工作就是多线程,几个工人要用同时使用一个工具就是线程同步。

【2】多进程和多线程的区别?

1)进程是资源分配的最小单位,线程是cpu调度,程序执行的最小单位。
2)进程有独立的地址空间,而同一进程中的线程共享该进程的地址空间。比如在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种非常昂贵的多任务工作方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,因此启动一个线程,切换一个线程远比进程操作要快,花费也要小得多。当然,线程是拥有自己的寄存器和堆栈(线程栈),比如在windows中用_beginthreadex创建一个新进程就会在调用CreateThread的同时申请一个专属于线程的数据块(_tiddata)。虽然,线程有自己线程栈,线程可以直接访问全局变量,甚至可以访问进程地址空间中的每一个内存,所以一个线程可以读写甚至清楚另一个线程的堆栈。
3)线程之间的通信比较方便。同一进程下的线程共享数据(比如全局变量,静态变量,打开的文件,子进程),通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问的同步与互斥正是编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。在一个线程中分配的堆在各个线程中均可以使用,在一个线程中打开的文件各个线程均可用,当然指同一进程中的线程。
4)多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。
5)线程的执行与进程是有区别的。每个独立的进程有自己的一个程序入口,顺序执行序列和程序出口,但是线程不能独立执行,必须依附于程序之中,由应用程序提供多个线程的并发控制。
6) linux中进程具有父子关系,形成进程树,但是线程是平等的没有父子关系

【3】.进程间通信方式以及优缺点?

通信方式6种:管道、消息队列、共享内存、信号量、套接字、信号
根据信息量大小的不同可以分为低级通信和高级通信,在选择上,如果用户传递的信息较少.或是需要通过信号来触发某些行为的,一般用信号机制就能解决,如果进程间要求传递的信息量比较大或者有交换数据的要求,那么就要使用共享内存和套接字这些通信方式。
各种通信方式优缺点:
1)管道:分为有名管道和无名管道,都是半双工,只能承载无格式字节流,有名管道允许无亲缘关系进程间的通信,而无名管道只能在具有亲缘关系的进程间使用。进程的亲缘关系一般指的是父子关系。当一个进程创建了一个管道,并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。
无名管道:优点:简单方便;缺点:只能在具有亲缘关系的进程间使用,缓冲区有限
有名管道:优点:可实现任意关系的进程间通信;缺点:长期存于系统中,使用不当容易出错,缓冲区有限
2)消息队列:是消息的链表,存放在内核中,是Linux系统下不同进程间共享资源的一种机制,允许不同进程将格式化的数据流以消息队列形式发送给任意进程。
优点:任意进程间通信,通过系统调用函数实现消息发送和接收同步,不用考虑同步问题,方便;缺点:信息复制需要额外消耗CPU时间,不适宜于信息量大或操作频繁的场合
3)共享内存:通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,它是利用内存缓冲区直接交换信息,不需要复制,是最快的IPC(进程间通信)方式,往往和信号量配合使用,来实现进程间的同步通信。
优点:不用复制,快,信息量大;缺点:进程间读写同步问题,只能在同一个计算机系统的进程间共享,不方便网络通信
4)信号量:是一个计数器,用来控制多个线程对共享资源的访问,不是用于交换大批数据,而用于多线程间的同步,常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源,因此,主要作为进程间以及同一个进程内不同线程之间的同步手段。
优点:可以同步进程;缺点:信号量有限
5)套接字
优点:传输数据为字节级,传输数据可自定义,数据量小效率高;传输数据时间短,性能高;适合于客户端和服务器端之间信息实时交互;可以加密,数据安全性强。
缺点:需对传输的数据进行解析,转化成应用级的数据。

【4】.进程、线程的基本状态及状态之间的关系?

线程与进程相同,都具有三个基本状态:运行、阻塞、就绪。
状态之间有四种转换关系:运行–> 阻塞;阻塞–>就绪;就绪–>运行;运行–>就绪;只有运行与就绪可以相互转换,而阻塞一定要经过就绪态。我的理解是,如果没有就绪态,那么操作系统在调度的时候就不知道哪些是已经阻塞完成哪些还在阻塞,那等待IO来说,如果没有就绪态,操作系统就不知道哪些IO上已经有了输入,哪些还需要继续等待。

【5】.多线程有哪些实现方式?

1)调用系统的API,windows下是CreateThread,_beginThread(不要用),_beginThreadex(推荐使用),Linux使用POSIX线程(需要扩展)
2)使用第三方库的多线程函数,比如Boost库,实现用户级线程,MFC库中的afxBeginThread实际是调用系统API
3)使用C/C++库里的函数,包含thread头文件中,thread函数创建线程(用户级线程)

【6】.什么时候用多线程?什么时候用多进程?

1)需要频繁创建销毁的优先用线程
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是巨大的。
2)需要进行大量计算的优先使用线程
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。这种原则最常见的是图像处理、算法处理。
3)强相关的处理用线程,弱相关的处理用进程
什么叫强相关、弱相关?简单的例子:
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
4)可能要扩展到多机分布的用进程,多核分布的用线程

【7】.线程同步和线程异步?

同步是指一个线程要等待另一个线程执行完之后才开始执行当前的线程。
异步是指一个线程去执行,它的下一个线程不必等待它执行完就开始执行。
一般一个进程启动的多个不相干线程,它们之间的相互关系就为异步,比如游戏有图像和背景音乐,图像是由玩家操作的 而背景音乐是系统循环播放,它们两个线程之间没什么关系各干各的,这就是线程异步。至于同步的话指的是多线程同时操作一个数据,这个时候需要对数据添加保护,这个保护就是线程的同步。
同步使用场景:对于多个线程同时访问一块数据的时候,必须使用同步,否则可能会出现不安全的情况,有一种情况不需要同步技术,那就是原子操作,也就是说操作系统在底层保证了操作要么全部做完,要么不做。
异步的使用场景:当只有一个线程访问当前数据的时候。比如观察者模式,它没有共享区,主题发生变化后通知观察者更新,主题继续做自己的事情,不需要等待观察者更新完成后再工作。

【8】.多线程同步和互斥有几种实现方法,分别适用什么情况?

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源,如“第一类读写者模型”。
火车票售票是互斥还是同步? 互斥
生产者消费者模式是主要是同步问题,也有互斥的问题。
Linux下线程同步方法:atomic(原子操作),mutex(互斥锁),读写锁,条件变量,信号量,屏障(并发完成同一项任务时,屏障的作用特别好使)
1)atomic(原子操作):就是该操作绝不会在执行完毕前被任何其他任务或事件打断,是最小的执行单位。API定义在内核源码树的include/asm/atomic.h文件中。
原子操作通常用于实现资源的引用计数。
2)mutex(互斥锁):锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到, 而必须等待解锁。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。pthread_mutex_trylock() 语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回 EBUSY而不是挂起等待。
对于使用了多线程的程序,编译方法如下:g++ reader_writer.c -l pthread -o a.out
3)读写锁:适合于对数据结构的读次数比写次数多得多的情况。因为,以读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁又叫共享-独占锁。
4)条件变量:与互斥锁不同,条件变量是用来等待而不是用来上锁的,通常条件变量和互斥锁同时使用。如果一个条件不满足,一个线程会自动阻塞-挂起,并释放等待状态下的互斥锁。如果条件满足,就会唤醒一个或多个被这个条件变量阻塞的线程,这些唤醒的线程重新上锁,测试条件是否满足。
5)信号量:对应一个down和up操作,down使信号量减1,up使信号量加1,如果信号量大于0,则down后继续执行,如果down等于0,则down后睡眠,但是并不会将信号量减到负数。down和up都是原子操作。

【9】.条件变量与互斥锁、信号量的区别?

1)互斥锁必须总是由给它上锁的线程解锁,信号量的挂出不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,而另一个线程可以挂出该信号灯。
2)互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)。
3)由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而对于条件变量,当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。
4)互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。

【10】Windows下线程同步方法:临界区,互斥量,信号量,事件

临界区:适合一个进程内多线程访问公共资源时使用。
互斥量:适合不同进程内多线程访问公共资源时使用。
注意:如果是在进程内部使用的话,用临界区会带来速度上的优势并且能够减少资源占用量。
信号量:与临界区和互斥量不同,它是允许多个线程同时访问公共资源的,它相当于操作系统的PV操作,它会事先设定一个最大线程数,如果线程占用数达到最大,那么其它线程就不能再进来,如果有部分线程释放资源了,那么其它线程才能进来访问资源。
事件:通过通知操作的方式来保持线程同步。
注意:互斥量,事件,信号量都是内核对象,可以跨进程使用。

猜你喜欢

转载自blog.csdn.net/dai_wen/article/details/81433783