进程、线程和协程

操作系统

分时系统
      由于CPU速度不断提高和采用分时技术,一台计算机可同时连接多个用户终端,而每个用户可在自己的终端上联机使用计算机,好象自己独占机器一样。
      分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。
      特点:
  (1)多路性。若干个用户同时使用一台计算机。微观上看是各用户轮流使用计算机;宏观上看是各用户并行工作。
  (2)交互性。用户可根据系统对请求的响应结果,进一步向系统提出新的请求。这种能使用户与系统进行人机对话的工作方式,明显地有别于批处理系统,因而,分时系统又被称为交互式系统。
  (3)独立性。用户之间可以相互独立操作,互不干扰。系统保证各用户程序运行的完整性,不会发生相互混淆或破坏现象。
  (4)及时性。系统可对用户的输入及时作出响应。分时系统性能的主要指标之一是响应时间,它是指:从终端发出命令到系统予以应答所需的时间。
实时系统
 实时操作系统的主要特点:
  (1)及时响应。每一个信息接收、分析处理和发送的过程必须在严格的时间限制内完成。
  (2)高可靠性。需采取冗余措施,双机系统前后台工作,也包括必要的保密措施等。


分时——现在流行的PC,服务器都是采用这种运行模式,即把CPU的运行分成若干时间片分别处理不同的运算请求 linux系统
实时——一般用于单片机上、PLC等,比如电梯的上下控制中,对于按键等动作要求进行实时处理

UNIX操作系统是一个通用的多用户分时交互型的操作系统
操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。
操作系统的作用:
    1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
    2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

进程

进程的定义

在讲进程前首先要弄明白什么是程序:程序(Program)是为实现特定目标或解决特定问题而用计算机语言编写的命令序列的集合,必须python就是个程序

  • 进程是程序的一次执行过程。
  • 进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
  • 进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

 

并行和并发
并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )
并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。

 

 

进程同步和异步
       所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,   两个任务的状态可以保持一致。
  所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。

进程的特征

进程是由多程序的并发执行而引出的,它和程序是两个截然不同的概念。进程的基本特征是对比单个程序的顺序执行提出的,也是对进程管理提出的基本要求。

 

  1. 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
  2. 并发性:任何进程都可以同其他进程一起并发执行
  3. 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
  4. 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
  5. 结构特征:进程由程序、数据和进程控制块三部分组成。

多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变

进程状态的转换

进程在其生命周期内,由于系统中各进程之间的相互制约关系及系统的运行环境的变化,使得进程的状态也在不断地发生变化(一个进程会经历若干种不同状态)。通常进程有以下五种状态,前三种是进程的基本状态。

1) 运行状态:进程正在处理机上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。

2) 就绪状态:进程已处于准备运行的状态,即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。

3) 阻塞状态,又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

4) 创建状态:进程正在被创建,尚未转到就绪状态。创建进程通常需要多个步骤:首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息;然后由系统为该进程分配运行时所必需的资源;最后把该进程转入到就绪状态。

5) 结束状态:进程正从系统中消失,这可能是进程正常结束或其他原因中断退出运行。当进程需要结束运行时,系统首先必须置该进程为结束状态,然后再进一步处理资源释放和回收等工作。

注意区别就绪状态和等待状态:就绪状态是指进程仅缺少处理机,只要获得处理机资源就立即执行;而等待状态是指进程需要其他资源(除了处理机)或等待某一事件。之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪状态的;而其他资源(如外设)的使用和分配或者某一事件的发生(如I/O操作的完成)对应的时间相对来说很长,进程转换到等待状态的次数也相对较少。这样来看,就绪状态和等待状态是进程生命周期中两个完全不同的状态,很显然需要加以区分。



五种状态的转换


就绪状态----->>>运行状态:处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。

运行状态----->>>就绪状态:处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。

运行状态----->>>阻塞状态:当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。

阻塞状态----->>>就绪状态:当进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。

 

python中的进程

进程的创建与结束


from
multiprocess import Process process(target= 子进行的函数名 ,args=参数) def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号 参数介绍: 1 group参数未使用,值始终为None 2 target表示调用对象,即子进程要执行的任务 3 args表示调用对象的位置参数元组,args=(1,2,'egon',) 4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} 5 name为子进程的名称
6 p.is_alive() 表示进程是否活着 True代表进程还在 False代表进程不在了
7 p.terminate() 表示结束一个进程,但是这个进程不会立刻被杀死 (异步实现)

 守护进程:deamon=True     

  守护进程就是在主进程结束时,子进程跟着也结束

  子进程.join则是主进程等待子进程结束才结束

from multiprocessing import  Process
import time
def func():
    while True:
        print('*'*10)
        time.sleep(1)
if __name__=='__main__':
    p=Process(target=func)
    p.daemon=True    #守护进程 要在start之前设置,守护进程中 不能再开启子进程
    p.start()
    for i in range(5):
        time.sleep(1)
        print('#'*i)
**********

**********
#
**********
##
**********
###
**********
#### #当主程序结束时,子程序的死循环也跟着结束

锁:

   资源总是有限的,程序运行如果对同一个对象进行操作,则有可能造成资源的争用,甚至导致死锁,在并发进程中就可以利用锁进行操作来避免访问的冲突。

我们可以模拟一个火车抢票的过程,当过个客户同时对一个程序发起访问时,假设此时有5张票,有10个人抢


进程锁
import random
import time
from multiprocessing import Process
from multiprocessing import Lock
import json
def search(i):
with open('info')as f:
print(i,json.load(f)["count"])

def get(i):
with open('info')as f:
ret_num=json.load(f)['count']
time.sleep(random.random())
if ret_num>0:
with open('info','w') as f:
json.dump({'count':ret_num-1},f)
print('客户%s抢到票了'%i)
else:
print('没票了')
def task(i,lock):
search(i)
lock.acquire()
get(i)
lock.release()
if __name__=='__main__':
lock=Lock()
for i in range(10):
p=Process(target=task,args=(i,lock))
p.start()



0
5 1 5 3 5 2 5 8 5 客户0抢到票了 客户1抢到票了 5 3 9 3 7 3 4 3 6 3 客户3抢到票了 客户2抢到票了 客户8抢到票了 没票了 没票了 没票了 没票了 没票了

线程锁


递归锁RLock 和互斥锁 Lock


我们使用线程对数据进行操作的时候,如果多个线程同时修改某个数据,可能会出现不可预料的结果,为了保证数据的准确性,引入了锁的概念。


import threading
import time
 
num = 0
 
lock = threading.RLock()    # 实例化锁类
 
def work():
    lock.acquire()  # 加锁
    global num
    num += 1
    time.sleep(1)
    print(num)
    lock.release()  # 解锁
 
for i in range(10):
    t = threading.Thread(target=work)
    t.start()

 


threading.RLock和threading.Lock 的区别


RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况,一个线程只能acquire一次,release后才可重新acquire。 如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的锁


import threading
 
lock = threading.Lock()
lock.acquire()
lock.acquire()  # 产生死锁
print(123) lock.release() lock.release()

如果将互斥锁换成递归锁
import threading
rlock = threading.Lock()
rlock.acquire()
rlock.acquire() # 这里不会出现死锁
print(123)
rlock.release()
rlock.release()

123
 

信号量Semaphore:  

信号量Semaphore用来控制对共享资源的访问数量Semaphore管理一个内置的计数器, 每当调用acquire()时内置计数器-1; 调用release() 时内置计数器+1; 计数器不

能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release(),如果继续用锁的概念来说的话,就相当于最多可以同时获得锁的数量(一个把锁配多把钥匙),

例如

 

from multiprocessing import Semaphore
sem = Semaphore(4) #这里表示最多可以最多有三个线程可以使用这个锁

sem.aquire()   #表示获得锁,每sem.acquire() 就相当于减少一把锁
sem.release() #表示释放锁,每sem.release() 就相当于减少一把锁

 

假设一个房间,做多可以容纳2个人,有10个人要从里边进出

# from multiprocessing import Semaphore # from multiprocessing import Process # import time # def room(i,sem): # print('%s进入房间' % i) # sem.acquire() # time.sleep(2) # print('%s离开房间' % i) # sem.release() # if __name__ == '__main__': # sem = Semaphore(2) # for i in range(10): # time.sleep(1) # p = Process(target=room, args=(i, sem)) # p.start()


0进入房间
1进入房间
0离开房间
2进入房间
1离开房间
3进入房间
2离开房间
4进入房间
3离开房间
5进入房间
4离开房间
6进入房间
5离开房间
7进入房间
6离开房间
8进入房间
9进入房间
7离开房间
8离开房间
9离开房间

事件Event

Event用来实现进程间同步通信。

举一个红绿灯的例子吧,当20辆车进入路口,当车号可以被3整除时,遇到红灯则等2秒,遇到绿灯则行驶2秒后变为红灯,依次循环

 

from multiprocessing import Process
from multiprocessing import Event
import time
def light(e):
    while True:
        if e.is_set():                 #表示是否阻塞,True表示非阻塞,False表示阻塞
            time.sleep(2)
            print('红灯亮')
            e.clear()                  #表示将标志变为阻塞
        else:
            time.sleep(2)
            print('绿灯亮')
            e.set()                    #表示将标志变为非阻塞
def car(i,e):
    e.wait()                           #表示刚实例化出来的一个事件对象,默认的信号是阻塞信号
    print('%s车通过'%i)

if __name__=='__main__':
    e=Event()                          #创建一个事例对象
    p=Process(target=light,args=(e,))
    p.start()
    for i in range(20):
        if i%3==0:
            time.sleep(2)
            p1=Process(target=car,args=(i,e))
            p1.start()

绿灯亮
0车通过
3车通过
红灯亮
绿灯亮
6车通过
9车通过
红灯亮
绿灯亮
12车通过
15车通过
红灯亮
绿灯亮
18车通过
红灯亮

队列Queue

  适用于多线程编程的先进先出数据结构,可以用来安全的传递多线程信息。

   通过队列实现了 主进程与子进程的通信   子进程与子进程之间的通信
q=Queue(10)     #实例化一个对象,允许队列对多10个元素
q.put() #放入队列
q.get() #从队列中取出

 假设现在有一个队伍,队伍里最多只能站5个人,但是有15个人想要进去

from multiprocessing import Process
from multiprocessing import Queue
def getin(q):     #进入队伍的子进程
    for i in range(15):
        q.put(i)
        # print(q)
def getout(q):    #离开队伍的子进程
    for i in range(6):
        print(q.get())
if __name__=='__main__':
    q=Queue(5)      #队伍内最多可以容纳的人数
    p=Process(target=getin,args=(q,))     #进入队伍的进程
    p.start()
    p2=Process(target=getout,args=(q,))   #离开队伍的进程
    p2.start()

0
1
2
3
4
5

 

 

JoinableQueue

JoinableQueue相比于Queue多了join()和task_done()
q.task_done(),每次从queue中get一个数据之后,当处理好相关问题,最后调用该方法,以提示q.join()是否停止阻塞,让线程向前执行或者退出;
q.join(),阻塞,直到queue中的数据均被删除或者处理。常用于生产者消费者模型

import random
from multiprocessing import Process
from multiprocessing import Queue
def producer(q,food):
    for i in range(5):
        q.put('%s-%s'%(food,i))
        print('生产了%s-%s'%(food,i))
  q.join() #当接收到task_done()发送数据,解除阻塞 def consumer(q,name):
while True: food=q.get() print('%s吃了%s'%(name,food)) q.task_done() #每次get()完,task_done()内置的计数器减1,知道减到0,发送给join() if __name__=='__main__': q=Queue p1=Process(target=producer,args=(q,'面包')) p1.start() p2=Process(target=producer,args=(q,'牛奶')) p2.start() c1=Process(target=consumer,args=(q,'老张'))
c1.daemon()==True #当主进程结束,消费者也结束(守护进程) c1.start() c2
=Process(target=consumer,args=(q,'老李'))
c1.daemon()==True c2.start()

   p1.join() #当生产结束,主进程也跟着结束
   p2.join()
 
管道 Pipes
  管道的特点:双向通信、数据不安全,与队列相比没有锁的机制,与socket很相似

from multiprocessing import Process, Pipe

def f(p):
foo,son=p
son.close()
while True:
try:

  son.recv()
except EOFError:
      break
if __name__ == '__main__': foo, son = Pipe()         # 管道实例化,可以实例化得到它的父端和子端 p = Process(target=f, args=(foo,son),) p.start() son.close()
foo.send('hello')
foo.send('hello')
foo.close()

数据共享 Managers

  上面两种方式只是实现了数据的传递,还没有实现数据的共享,如实现数据共享,就要用到Managers

from multiprocessing import Process,Manager
import os

def f(dict1,list1):
    dict1[os.getpid()] = os.getpid()            # 往字典里放当前PID
    list1.append(os.getpid())                   # 往列表里放当前PID
    print(list1)

if __name__ == "__main__":
    with Manager() as manager:
        d = manager.dict()                       #生成一个字典,可在多个进程间共享和传递
        l = manager.list(range(5))               #生成一个列表,可在多个进程间共享和传递
        p_list = []                              
        for i in range(10):
            p = Process(target=f,args=(d,l))
            p.start()
            p_list.append(p)                     # 存进程列表       
        for res in p_list:                    
            res.join()
        print('\n%s' %d)                                        #若要保证数据安全,需要加锁lock=Lock()

 

进程池

对于需要使用几个甚至十几个进程时,我们使用Process还是比较方便的,但是如果要成百上千个进程,用Process显然太笨了,multiprocessing提供了Pool类,即现在要讲的进程池,能够将众多进程放在一起,设置一个运行进程上限,每次只运行设置的进程数,等有进程结束,再添加新的进程

  • Pool(processes =num):设置运行进程数,当一个进程运行完,会添加新的进程进去
  • apply_async:异步,串行
  • apply:同步,并行
  • close():关闭pool,不能再添加新的任务
import os
import time
import random
from multiprocessing import Pool
from multiprocessing import Process
def func(i):
    i += 1

if __name__ == '__main__':
    p = Pool(5)          # 创建了5个进程
    start = time.time()
    p.map(func,range(1000))  
    p.close()                        # 是不允许再向进程池中添加任务
    p.join()                        #阻塞等待 执行进程池中的所有任务直到执行结束
    print(time.time() - start)
    start = time.time()
    l = []
    for i in range(1000):
        p = Process(target=func,args=(i,))  # 创建了一百个进程
        p.start()
        l.append(p)
    [i.join() for i in l]
    print(time.time() - start)

回调函数:

import os
import time
from multiprocessing import Pool
# 参数 概念 回调函数
def func(i):    # 多进程中的io多,分出去一部分
    print('子进程%s:%s'%(i,os.getpid()))
    return i*'*'

def call(arg):   # 回调函数是在主进程中完成的,不能传参数,只能接受多进程中函数的返回值
    print('回调 :',os.getpid())
    print(arg)

if __name__ == '__main__':
    print('主进程',os.getpid())
    p = Pool(5)
    for i in range(10):
        p.apply_async(func,args=(i,),callback=call)  #callback 回调函数 :主进程执行 参数是子进程执行的函数的返回值
    p.close()
    p.join()

主进程 15748
子进程0:17264
子进程1:17264
子进程2:17264
子进程3:17264
子进程4:17264
子进程5:17264
子进程6:17264
子进程7:17264
子进程8:17264
子进程9:17264
回调 : 15748

回调 : 15748
*
回调 : 15748
**
回调 : 15748
***
回调 : 15748
****
回调 : 15748
*****
回调 : 15748
******
回调 : 15748
*******
回调 : 15748
********
回调 : 15748
*********
 

 

 

 线程

   进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
     同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。

   举个形象点的例子,进程就好比人吃饭,在吃饭的过程中比如吃菜和吃主食就是线程。即线程是进程的一部分。

import os
import time
import threading

def func():                         
    time.sleep(1)
    print('hello world',os.getpid(),threading.currentThread().name,threading.currentThread().ident)
#threading.currentThread().name 查看当前线程名
#threading.currentThread().ident 查看当前线程ID   thread_lst
= []     for i in range(10):   t = threading.Thread(target=func) # 主线程   t.start() # 子线程   thread_lst.append(t)   [t.join() for t in thread_lst]   print(os.getpid()) #主线程的进程ID

hello world 12868 Thread-1 12756
hello world 12868 Thread-3 12864
hello world 12868 Thread-2 12872
hello world 12868 Thread-4 21644
hello world 12868 Thread-6 16820
hello world 12868 Thread-5 9272
hello world 12868 Thread-7 13332
hello world 12868 Thread-9 18160
hello world 12868 Thread-8 13060
hello world 12868 Thread-10 12480
12868

守护线程

  setDaemon()用法和在多进程中一样,必须放在start之前。

 

gil锁

全局解释器锁的存在让多线程只有一个线程在执行,所以python里的多线程是假的多线程,不管多少核,同一时间只能在一个核上运行。

那什么时候用多线程呢?

  • io操作不占用cpu,计算占用cpu
  • 大量计算,耗cpu的用单线程
  • python多线程,不适合cpu密集操作型的任务,适合io密集型的任务
  • IO密集开多线程,计算密集开多进程

 

 

 

协程

协程,又称微线程,纤程。英文名Coroutine。协程是一种用户态的轻量级线程。协程的本质是个单线程,遇到IO就切换

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

线程的切换,会保存到CPU的寄存器里。
CPU感觉不到协程的存在,协程是用户自己控制的。

greenlet

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch()
    print(78)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

 

gevent

Greenlet 手动切换;Gevent 自动切换,封装了Greenlet
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet
它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

from gevent import monkey;monkey.patch_all()        
import time                              
import gevent                              
def func1():
    print(123)
    time.sleep(1)
    print(789)

def func2():
    print('456')
    time.sleep(1)
    print('10jq')

g1 = gevent.spawn(func1)  # 遇见他认识的io会自动切换的模块
g2 = gevent.spawn(func2)
gevent.joinall([g1,g2])



123
456
789
10jq

 

posted @ 2018-02-01 18:32  排骨南  阅读(172)  评论(0编辑  收藏  举报