进程,线程,协程与互斥锁基础,

#1:--------------什么是进程?
“”"
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,
是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。
程序是指令、数据及其组织形式的描述,进程是程序的实体。
狭义定义:进程是正在运行的程序的实例。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,包括文本区域、数据区域和堆栈。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
在这里插入图片描述
运行中的进程可能具有以下三种基本状态。1)就绪状态(Ready):
进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。
2)运行状态(Running):
进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
3)阻塞状态(Blocked):
由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行。
例如:创建一个进程下载网站上的图片

#下载网上图片或文本
from multiprocessing import Process#导入模块
from urllib import request
import os
def downloader(url):
    print("当前进程id:",os.getpid(),os.getppid())#获取当前进程和当前进程的父进程的id,
    file_name = url.split("/")[-1]#用/分隔开,得到列表,【-1】指获取列表最后一个元素,得到你想要下载的文件,,,
    responce = request.urlopen(url)#得到响应,request指请求
    content = responce.read()#获取相应的内容
    with    open(file_name,"wb")    as fp:
        fp.write(content)#写入文件
if __name__ == '__main__':
    p_list=[]
    
    for i in range(1,5,1):
        url="http://www.langlang2017.com/img/banner"+str(i)+".png"
        
        p=Process(target=downloader,args=(url,))#传参,target=后跟函数名,切记没有括号,args=(url,))后跟参数,是元祖形式,当参数只有一个的时候,一定要加逗号
        
        p.start()#运行模块
        p_list.append(p)
    for p in p_list:
        p.join()#阻塞

    print("主进程id:",os.getpid())
    print("程序结束")

#例2:创建多进程:

from  multiprocessing import Process
import time
def sing():
    for i in range(1,4):
        time.sleep(1)
        print("唱第%d首歌"%i)
def dance():
    for i in range(1,4):
        time.sleep(1)
        print("跳第%d段舞蹈"%i)
if __name__ == '__main__':
    p=Process(target=sing)
    p.start()
    dance()

#2:#----------------------什么是线程?
由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程
即线程,
进程是资源分配的最小单位,线程是CPU调度的最小单位(程序真正执行的时候调用的是线程).每一个进程中至少有一个线程。
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
在这里插入图片描述
在这里插入图片描述
●新线程态(New Thread)
产生一个Thread对象就生成一个新线程。当线程处于"新线程"状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。例如,一个线程调用了new方法之后,并在调用start方法之前的处于新线程状态,可以调用start和stop方法。
●可运行态(Runnable)
start()方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run()方法。在这时
线程的生命状态与周期
线程的生命状态与周期
线程处于可运行态。该状态不称为运行态是因为这时的线程并不总是一直占用处理机。特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可运行态的线程占用处理 机。注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。
●阻塞/非运行态(Not Runnable)
当以下事件发生时,线程进入非运行态。
①suspend()方法被调用;
②sleep()方法被调用;
③线程使用wait()来等待条件变量;
④线程处于I/O请求的等待。
●死亡态(Dead)
当run()方法返回,或别的线程调用stop()方法,线程进入死亡态。通常Applet使用它的stop()方法来终止它产生的所有线程。
例:创建线程:

from threading import Thread#导入线程模块
from urllib import request
def downloader(url):
    file_name =url.split("/")[-1]
    response=request.urlopen(url)
    content=response.read()
    with open(file_name,'wb') as fp:
        fp.write(content)


if __name__ == '__main__':
    #主进程下面有个主线程
    url_list=["https://123p3.sogoucdn.com/imgu/2018/04/20180404144123_595.png",
              "https://123p2.sogoucdn.com/imgu/2019/03/20190308152948_432.jpg"]
    for url in url_list:

#创建线程
        t=Thread(target=downloader,args=(url,))#传参
        t.start()#启动线程

#3:--------------什么是协程?
在这里插入图片描述
简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.

Python里最常见的yield就是协程的思想!
协程 是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序”。从技术的角度来说,“协程就是你可以暂停执行的函数”。如果你把它理解成“就像生成器一样”,那么你就想对了。
最常见的协程例子就是斐波那契数列:

#斐波那契

def fbnq(times):
    n=0
    a,b=0,1
    while   n<times:

        a,b=b,a+b
        yield a
        n+=1
F=fbnq(7)
print(next(F))

#4:---------------------------进程、线程、协程的区别?
进程:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序执行的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体
线程:
线程,是程序执行的最小单元。一个标注的线程由线程ID,当前指令指针,寄存器集合和堆栈组成。另外,线程也是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可以与同属一个进程的其他线程共享进程所拥有全部资源。一个线程创建和撤销另一个线程,同一个进程中的多个线程并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有运行、阻塞、和就绪三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机。运行状态是指线程战友处理剂正在运行。阻塞状态是指线程在等待一个事件,逻辑上不可执行。每个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
协程:
有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

在这里插入图片描述
在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入线程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
因此出现了能独立运行的基本单位——线程(Threads)。
在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。
另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,线程不是一个可执行的实体。
#进程和线程的区别也可以体现在对全局变量的修改上,例如:
创建一个多进程:

from multiprocessing import Process#创建进程模块
g_num=0
def work1():
    global g_num
    for i in range(1000000):
        g_num+=1
    print("-----in work1,g_um is %d"%(g_num))#输出1000000
def work2():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("-----in work2,g_um is %d" % (g_num))#输出2000000
if __name__ == '__main__':
    work1()
    work2()
    print("g_num",g_num)#输出2000000

创建一个多线程:

from threading import Thread,Lock
g_num=0
def work1():
    global g_num
    for i in range(1000000):
        g_num+=1
    print("-----in work1,g_um is %d"%(g_num))#由于线程之间共享全局变量,所以这里的输出随机
def work2():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("-----in work2,g_um is %d" % (g_num))#由于线程之间共享全局变量,所以这里的输出随机
if __name__ == '__main__':
    t1=Thread(target=work1)
    t2 = Thread(target=work2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("g_num",g_num)#输出随机

由于线程之间共享全局变量,造成了线程之间对资源的竞争,解决的办法就是 “加锁”:
互斥锁:
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能:

都需要访问/使用同一种资源;
多个任务之间有依赖关系,某个任务的运行依赖于另一个任务。
【同步】:

是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

【互斥】:

是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
 在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的。
  在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

【互斥锁的特点】:

  1. 原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;

  2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;

  3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

【互斥锁的操作流程如下】:

  1. 在访问共享资源后临界区域前,对互斥锁进行加锁;

  2. 在访问完成后释放互斥锁导上的锁。在访问完成后释放互斥锁导上的锁;

  3. 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放
    在这里插入图片描述
    把上面的多线程加上互斥锁就不会造成数据丢失的问题:

from threading import Thread,Lock
g_num=0
def work1():
    global g_num
    for i in range(1000000):
        mutex.acquire()#加锁
        g_num+=1
        mutex.release()#解锁
    print("-----in work1,g_um is %d"%(g_num))
def work2():
    global g_num
    for i in range(1000000):
        mutex.acquire()#加锁
        g_num += 1
        mutex.release()#解锁
    print("-----in work2,g_um is %d" % (g_num))
mutex=Lock()#创建锁
if __name__ == '__main__':
    t1=Thread(target=work1)
    t2 = Thread(target=work2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("g_num",g_num)

加上互斥锁:1,确保了某段关键代码只能由一个线程在某段时间来占有它;2,阻止了多线程并发执行,包含所得某段代码实际上只能以单线程模式执行,;3,由于只是在确保了某段关键代码加锁,所以其他的语句还可以并行操作,所以既保证了安全有提高了效率。
4,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,举个通俗的例子:
两个柜子,两个锁,两把钥匙,把两把钥匙分别放进另外一个柜子,然后锁上,结果呢,两个都打不开了。在程序内部,这样就会导致两个进程死掉。
产生死锁的条件
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。

1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
#产生原因
1.竞争资源引起进程死锁

当系统中供多个进程共享的资源如打印机、公用队列的等,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。
2.进程推进顺序不当引起死锁
#预防方法
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源,在系统运行过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配。因此,对资源的分配要给予合理的规划。

下面几种方法可用以避免重装死锁的发生:

①允许目的节点将不完整的报文递交给目的端系统;

②一个不能完整重装的报文能被检测出来,并要求发送该报文的源端系统重新传送;

③为每个节点配备一个后备缓冲空间,用以暂存不完整的报文。

①、②两种方法不能很满意地解决重装死锁,因为它们使端系统中的协议复杂化了。一般的设计中,网络层应该对端系统透明,也即端系统不该考虑诸如报文拆、装之类的事。③方法虽然不涉及端系统,但使每个节点增加了开销。

有序资源分配法
这种算法资源按某种规则系统中的所有资源统一编号(例如打印机为1、磁带机为2、磁盘为3、等等),申请时必须以上升的次序。系统要求申请进程:

1、对它所必须使用的而且属于同一类的所有资源,必须一次申请完;

2、在申请不同类资源时,必须按各类设备的编号依次申请。例如:进程PA,使用资源的顺序是R1,R2;进程PB,使用资源的顺序是R2,R1;若采用动态分配有可能形成环路条件,造成死锁。

采用有序资源分配法:R1的编号为1,R2的编号为2;

PA:申请次序应是:R1,R2

PB:申请次序应是:R1,R2

这样就破坏了环路条件,避免了死锁的发生

银行算法银行家算法 - 搜狗百科 https://baike.sogou.com/v696313.htm?fromTitle=银行家算法
避免死锁算法中最有代表性的算法是DijkstraE.W于1968年提出的银行家算法:

该算法需要检查申请者对资源的最大需求量,如果系统现存的各类资源可以满足申请者的请求,就满足申请者的请求。

这样申请者就可很快完成其计算,然后释放它占用的资源,从而保证了系统中的所有进程都能完成,所以可避免死锁的发生。

“”"

猜你喜欢

转载自blog.csdn.net/chengchuanji/article/details/88594210