python 进程&线程

PYTHON 进程

进程的使用

一 : 进程概述

进程由三部分组成 : 代码段,数据段,PCB(进程控制块)

Python中的进程编程依赖于multiprocessing这个包,其中Process类就是提供最基础的功能的类

二 : 使用Process类创建进程

使用Process类可以创建新的进程,有两种使用方法:

1.实例化Process类,参数如下:

group参数未使用,值始终为None;

target表示调用对象,即子进程要执行的任务,也就是方法名,不是字符串形式,也不带括号;

args表示调用对象的位置参数元组,args=(1,2,‘egon’,);

kwargs表示调用对象的字典,kwargs={‘name’:‘egon’,‘age’:18};

name为子进程的名称.

这种方法需要先写好一个方法,作为参数传入Process实例化时候调用的__init__()方法

def func(i):
    time.sleep(1)
    print('这里是子进程')


if __name__ == '__main__':
    p = Process(target=func, args=(1,))  ## 实例化一个进程对象
    p.start()  ## 开启一个子进程
    print('这里是父进程')

## 结果:
## 这里是父进程
## 这里是子进程

2.新写一个Process类的子类,初始化直接调用父类的初始化方法,自己实现一个名为run()的方法,这个方法提供的作用相当于方法1中提前写好的那个方法

class MyProcess(Process):
    def __init__(self):
        super(MyProcess, self).__init__()

    def run(self):
        print('这是以继承类的方式开启的子进程')


if __name__ == '__main__':
    p1 = MyProcess()
    p1.start()

## 结果:
## 这是以继承类的方式开启的子进程

三 : 进程对象的使用

创建进程(进程类实例化)之后,需要调用start()方法启动它,我们知道,一个进程启动之后到销毁之前,有三种状态:就绪/阻塞/运行,调用该方法之后,进程就会进入就绪状态,等待操作系统给它分配时间片.

  • 其他方法介绍:
  • run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法;
  • terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
  • is_alive():如果p仍然运行,返回True
  • join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
  • run()和start()的纠葛:
  • start()方法调用的就是run()方法,但是start()只是让进程处于就绪状态,run()则是让进程进入运行状态,大约相当于start()+join()的效果,所以run()之后也不会再调用join().

常用属性:

name : 给进程一个名字;
    pid : 进程的pid
    daemon : 默认为False,当为True时,该进程为守护进程

四 : 守护进程

  • 特点 :
    • 1.守护进程随着父进程的结束而结束,普通子进程运行完父进程才会结束,即使父进程代码块已经运行完毕.
    • 2.守护进程不能再创建子进程.
    • 创建 : 将实例化出的进程对象的daemon属性设定为True即可,该语句需要在start()语句之前.

进程的锁

一 : 概述

当多个进程同时访问一个变量或者一个文件的时候,可能会发生数据紊乱的问题,为了解决这个问题,便有了锁这个工具.

二 : 锁的创建和使用

使用到的锁也是在multiprocessing包中的Lock类,要使用锁将它实例化即可.

锁可以使得一个变量或文件在同时只能有一个进程访问,相当于给变量(或文件)加了一把锁,当该进程使用完毕,将锁释放之后,其他进程才可以访问.

from multiprocessing import Process,Lock
import time

def check(i):
    with open(r'.\check.txt','r') as f:
        con = f.read()
    print('第%s个人查到%s'%(i,con))

def buy_ticket(i,l):
    l.acquire()## 拿钥匙,锁门
    with open(r'.\buy.txt','r') as f:
        con = int(f.read())
        time.sleep(0.1)
    if con > 0:
        print(' 第%s个人买到票了'%i)
        con -= 1
    else:
        print(' 第%s个人没有买到票'%i)
    time.sleep(0.1)## 是指 买完票后,把余票数量重写写入数据库的时间延迟
    with open(r'.\buy.txt','w') as f:
        f.write(str(con))
    l.release()## 还钥匙,开门

if __name__ == '__main__':
    l = Lock()
    for i in range(10):
        p_ch = Process(target=check,args=(i+1,))
        p_ch.start()
    for i in range(10):
        p_buy = Process(target=buy_ticket,args=(i+1,l))
        p_buy.start()

对象的acquire()方法可以加锁,release()可以释放锁,将需要加锁的代码块放在这两句中间即可.

三 : 信号量的创建和使用

有时候,我们需要多个进程同时访问一个方法,但是又要对他们加以控制,换个说法,也就是需要一个有多把钥匙的锁,这时候我们需要信号量(Semaphore)

使用方法和Lock一致,不同的是实例化的时候需要传入一个int类型的参数,也就是创建几把钥匙,加锁和释放的方法也和lock一致,非常简单.

from multiprocessing import Process,Semaphore
import time

def func(i,sem):
    sem.acquire()
    print('第%s个人进入小黑屋,拿了钥匙锁上门' % i)
    time.sleep(1)
    print('第%s个人出去小黑屋,还了钥匙打开门' % i)
    sem.release()

if __name__ == '__main__':
    sem = Semaphore(5)## 初始化了一把锁5把钥匙,也就是说允许5个人同时进入小黑屋
    ## 之后其他人必须等待,等有人从小黑屋出来,还了钥匙,才能允许后边的人进入
    for i in range(20):
        p = Process(target=func,args=(i,sem,))
        p.start()

四 : 事件的创建和使用

为了使用主进程控制子进程,创建了事件机制.要使用事件,需要导入multiprocessing包中的Event,通过几个方法控制来事件.

事件处理的机制:全局定义一个flag(event.is_set()),如果为False,那么程序执行event.wait()的时候就会阻塞,为True,则不阻塞.

事件的方法:

set() : 将flag设为True

clear() : 将flag设为False

is_set() : 返回flag的值

下面用一个红绿灯函数来演示事件的使用

def tra(e):
    '''信号灯函数'''
    while 1:## 红绿灯得一直亮着,要么是红灯要么是绿灯
        if e.is_set():## True,代表绿灯亮,那么此时代表可以过车
            time.sleep(5)## 所以在这让灯等5秒钟,这段时间让车过
            print(' 红灯亮! ')## 绿灯亮了5秒后应该提示到红灯亮
            e.clear()## 把is_set设置为False
        else:
            time.sleep(5)## 此时代表红灯亮了,此时应该红灯亮5秒,在此等5秒
            print(' 绿灯亮! ')## 红的亮够5秒后,该绿灯亮了
            e.set()## 将is_set设置为True

def Car(i,e):
    e.wait()## 车等在红绿灯,此时要看是红灯还是绿灯,如果is_set为True就是绿灯,此时可以过车
    print('第%s辆车过去了'%i)

if __name__ == '__main__':
    e = Event()
    triff_light = Process(target=tra,args=(e,))## 信号灯的进程
    triff_light.start()
    for i in range(50):## 描述50辆车的进程
        if i % 3 == 0:
            time.sleep(2)
        car = Process(target=Car,args=(i+1,e,))
        car.start()

进程的通信

一 : 概述

进程间通信(IPC)的方式有N种,这里我们学习FIFO队列和管道

二 : 队列的创建和使用

  • 队列可以由multiprocessing.Queue创建,它是多线程安全的,可以实现多进程之间的数据传递.

  • 创建 : Queue([maxsize]) , maxsize是队列中允许的最大元素数.默认为0,意为无限制.

  • 将Queue类实例化得到对象q,q具有如下方法:
  • q.get( [ block [ , timeout ] ] ), 这个方法用于返回q中的第一个元素,如果为空,则阻塞,直到有元素可用为止,参数block用于控制阻塞行为,默认为True,若设置为False,则不会阻塞,引发Queue.Empty异常,参数timeout为超时时间,用在阻塞模式中,若在指定的时间内没有元素可用,引发Queue.Empty异常.
  • q.get_nowait()相当于block设定为False的get()方法.
  • q.put( item, [ , block [ , timeout ] ] ) ,将item放入队列,如果队列已满,则阻塞,直到有空间可用为止,参数block用于控制阻塞行为,默认为True,若设置为False,则不会阻塞,引发Queue.Full异常,参数timeout为超时时间,用在阻塞模式中,若在指定的时间内没有元素可用,引发Queue.Full异常.
  • q.put_nowait(item),相当于block设定为False的put()方法.
  • q.qsize(),返回队列中目前项目的数量,不过该函数的结果并不可靠.
  • q.empty(),调用此方法时,如果队列为空,则返回True,不过结果不可靠,因为在返回结果和使用队列中间,可能有其他进程对队列进行了修改.
  • q.full(),与empt()方法相反,如果队列已经满了,则返回True,也是不可靠的,原因相同.
  • q.close(),关闭队列,无法再put()和get()
'''
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''

from multiprocessing import Queue
q=Queue(3)#设置容量

#put ,get ,put_nowait,get_nowait,full,empty
q.put(3)#加入数据
q.put(3)
q.put(3)
## q.put(3)   ## 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。
           ## 如果队列中的数据一直不被取走,程序就会永远停在这里。
try:
    q.put_nowait(3) ## 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错;没有满就会进入数据。
except: ## 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。
    print('队列已经满了')

## 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。
print(q.full()) #满了位True

print(q.get())#取出数据
print(q.get())
print(q.get())
## print(q.get()) ## 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。
try:
    q.get_nowait(3) ## 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。
except: ## 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。
    print('队列已经空了')

print(q.empty()) #空了
  • 加入进程通信中
import time
from multiprocessing import Process, Queue

def f(q):
    q.put([time.asctime(), 'from Eva', 'hello'])  #调用主函数中p进程传递过来的进程参数 put函数为向队列中添加一条数据。

if __name__ == '__main__':
    q = Queue() #创建一个Queue对象
    p = Process(target=f, args=(q,)) #创建一个进程
    p.start()
    print(q.get())
    p.join()

三 : 使用队列实现生产者消费者模型

    1. 什么是生产者消费者模型
    • 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
    1. 为什么使用这个模型
    • 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
    1. 基于队列实现该模型
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()#q,没有数据一直等待
        time.sleep(random.randint(1,5))
        print('%s 吃 %s' %(os.getpid(),res))

def producer(q):
    for i in range(10):
        time.sleep(random.randint(1,10))
        res='包子%s' %i
        q.put(res)
        print('%s 生产了 %s' %(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    p1=Process(target=producer,args=(q,))#生产者们
    c1=Process(target=consumer,args=(q,))#消费者们

    #开始
    p1.start()
    c1.start()
    print('主进程')
  • 但是这样子写有一个问题,因为消费者函数一直在运行,所以主程序也不会关闭,这时候,我们需要在生产完毕之后再向队列总加入一个None作为结束信号,即可中断程序.
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('%s 吃 %s' %(os.getpid(),res))

def producer(q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('%s 生产了 %s' %(os.getpid(),res))
    q.put(None) #发送结束信号
if __name__ == '__main__':
    q=Queue()
    #生产者们
    p1=Process(target=producer,args=(q,))

    #消费者们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()
    print('主')
  • 这个结束信号也可以放在主程序中发送,这就需要先运行完毕生产者,确保结束信号加在了队列最后
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('%s 吃 %s' %(os.getpid(),res))

def producer(q):
    for i in range(2):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('%s 生产了 %s' %(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    #生产者们
    p1=Process(target=producer,args=(q,))

    #消费者们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()

    p1.join()
    q.put(None) #发送结束信号
    print('主')

  • 主进程在生产者生产完毕后发送结束信号None

  • 当存在多个生产者和消费者的时候,代码是酱紫的

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('%s 吃 %s' %(os.getpid(),res))

def producer(name,q):
    for i in range(2):
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('%s 生产了 %s' %(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    #生产者们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))

    #消费者们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    p2.start()
    p3.start()
    c1.start()

    p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号
    p2.join()
    p3.join()
    q.put(None) #有几个消费者就应该发送几次结束信号None
    q.put(None) #发送结束信号
    print('主')

  • 多个消费者的例子:有几个消费者就需要发送几次结束信号

  • 需要注意的是当存在两个消费者的时候,我们在最后放入了两个中断信号,这是因为中断信号就是给消费者用的,当前一个消费者进程get了一个None之后,这个None就被取出了,这种方式很low,当消费者多的时候,需要put多个None,所以python中实现了更cool的方法.

    • JoinableQueue([maxsize]) ,可以创建一个可连接的队列,它的特别之处在于消费者消费数据之后会反馈给生产者一个信号,通知是使用共享的信号和条件来实现的.
    • 这个类是Queue的子类,其中单独定义了两个方法:task_done() 和join()
    • task_done() : 消费者使用此方法发出信号,表示get()取出的项目已经被处理,如果调用该方法的次数大于从队列删除的项目的数量,将引发ValueError异常.
    • join() : 生产者使用该方法阻塞,知道队列中的所有项目都被处理,阻塞将持续到task_done()返回信号的次数等于队列中的项目数.

from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('%s 吃 %s' %(os.getpid(),res))
        q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了

def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('%s 生产了 %s' %(os.getpid(),res))
    q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。


if __name__ == '__main__':
    q=JoinableQueue()
    #生产者们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))

    #消费者们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))
    c1.daemon=True
    c2.daemon=True

    #开始
    p_l=[p1,p2,p3,c1,c2]
    for p in p_l:
        p.start()

    p1.join()
    p2.join()
    p3.join()
    print('主') 
    
    #主进程等--->p1,p2,p3等---->c1,c2
    #p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
    #因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。

  • JoinableQueue队列实现消费之生产者模型

四 : 使用管道进行通信

  • 创建 : 1 . from multiprocessing import Pipe 2. con1,con2 = Pipe(duplex) ,Pipe()返回的创建的对象是一个包含两个元素的元组, 这两个元素表示管道两端的连接对象,需要注意:必须在创建Process对象之前创建管道.

  • 参数 : 默认为True,表示全双工,意为两端既可以收,也可以发,如果该参数设置为False,con1只能用于接收,con2只能用于发送(不能调换,这个方向是固定的).

  • 主要方法 :

    • send(obj) 通过管道发送对象
    • recv() 接收另一端发送的内容,如果没有可以接收的,则会阻塞,如果另一端关闭,则会报EOFError.
    • close() 关闭连接,如果被垃圾回收,则会自动执行该方法.
from multiprocessing import Process, Pipe


def f(conn):
    conn.send("Hello The_Third_Wave")
    conn.close()


if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    p.join()
  • pipe初使用
from multiprocessing import Process, Pipe

def f(parent_conn,child_conn):
    parent_conn.close() #不写close将不会引发EOFError
    while True:
        try:
            print(child_conn.recv())
        except EOFError:
            child_conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(parent_conn,child_conn,))
    p.start()
    child_conn.close()
    parent_conn.send('hello')
    p.join()
    parent_conn.close()
from multiprocessing import Process,Pipe

def consumer(p,name):
    produce, consume=p
    produce.close()
    while True:
        try:
            baozi=consume.recv()
            print('%s 收到包子:%s' %(name,baozi))
        except EOFError:
            break

def producer(seq,p):
    produce, consume=p
    consume.close()
    for i in seq:
        produce.send(i)

if __name__ == '__main__':
    produce,consume=Pipe()

    c1=Process(target=consumer,args=((produce,consume),'c1'))
    c1.start()


    seq=(i for i in range(10))
    producer(seq,(produce,consume))

    produce.close()
    consume.close()

    c1.join()
    print('主进程')
  • pipe实现生产者消费者模型

五 : 使用Manager模块进行进程之间的数据共享

进程之间数据是独立的,可以借助队列和管道实现通信,二者都是基于消息传递的,虽然进程间数据独立,但是可以通过Manager模块实现数据共享,事实上这个模块的功能远不止于此.

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.

from multiprocessing import Manager,Process,Lock
def work(d,lock):
    with lock: #不加锁而操作共享的数据,肯定会出现数据错乱
        d['count']-=1

if __name__ == '__main__':
    lock=Lock()
    with Manager() as m:
        dic=m.dict({'count':100})
        p_l=[]
        for i in range(100):
            p=Process(target=work,args=(dic,lock))
            p_l.append(p)
            p.start()
        for p in p_l:
            p.join()
        print(dic)
  • Manager例子

六: 进程池

当有n多个进程的时候 ,我们可以用进程池来管理这些进程.

创建 : 1. from multiprocessing import Pool 2. p = Pool([numprocess [,initializer [, initargs]]])

参数:
numprocess : 传入的是一个int类型的数字,表示池中的最大进程数量,如果不给出则会通过os.cpu_count()获取,如果获取不到,则会赋值1,我们自定义这个数字一般是cpu数目+1.
initializer : 是每个工作进程启动时要执行的可调用对象,默认为None.
initargs : 传给initializer的参数组

  • 主要方法:

    • 1. map(func, iterable[, chunksize=None])

      • Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到结果返,返回值是个list.
    • 2. apply(func [, args [, kwargs]])

      • 同步得执行任务,并返回结果.主进程会被阻塞直到函数执行结束
    • 3. apply_async(func[, args=()[, kwds={}[, callback=None]]])

      • 与apply用法一致,但它是非阻塞的且支持结果返回后进行回调,异步得执行任务.
    • 4. close()

      • 关闭进程池,使其不再接受新的任务,但是不妨碍工作进程继续执行现有的任务.
    • 5. terminal()

      • 结束工作进程,不再处理未处理的任务.
    • 6. join()

      • 主进程阻塞等待子进程的退出,要在close或者terminal之后使用.
map进程池和多进程做任务的效率对比 :
from multiprocessing import Pool,Process
import time
def func(i):
    i += 1
    print(i)

if __name__ == '__main__':
    p = Pool(4)## 创建进程池,池中有4个进程待命
    start = time.time()
    t = [i for i in range(100)]
    p.map(func,t)#
    p.close()
    p.join()## 等待进程池中所有进程都执行完所有任务。
    print(time.time() - start)## 打印进程池做任务的时间
    start = time.time()
    l = []
    for i in range(100):
        p = Process(target=func,args=(i,))
        p.start()
        l.append(p)
    [i.join() for i in l]## 等待所有进程工作结束
    print(time.time() - start)## 打印开启100个进程做任务的时间
    print('--'*20+'>')
  • map进程池和多进程做任务的效率对比
同步调用和异步调用的区别 :
  • 同步调用
import os,time
from multiprocessing import Pool

def work(n):
    print('%s run %d' %(os.getpid(),n))
    time.sleep(1)
    return n**2

if __name__ == '__main__':
    p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
    for i in range(10):
        res=p.apply(work,args=(i,)) ## 同步调用,直到本次任务执行完毕拿到res,等待任务work执行的过程中可能有阻塞也可能没有阻塞
    print(res)                            ## 但不管该任务是否存在阻塞,同步调用都会在原地等着

## 进程池的同步调用
  • 异步调用
import os
import time
import random
from multiprocessing import Pool

def work(n):
    print('%s run' %os.getpid())
    time.sleep(random.random())
    return n**2

if __name__ == '__main__':
    p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
    res_l=[]
    for i in range(10):
        res=p.apply_async(work,args=(i,)) ## 异步运行,根据进程池中有的进程数,每次最多3个子进程在异步执行
                                          ## 返回结果之后,将结果放入列表,归还进程,之后再执行新的任务
                                          ## 需要注意的是,进程池中的三个进程不会同时开启或者同时结束
                                          ## 而是执行完一个就释放一个进程,这个进程就去接收新的任务。
        res_l.append(res)

    ## 异步apply_async用法:如果使用异步提交的任务,主进程需要使用jion,等待进程池内任务都处理完,然后可以用get收集结果
    ## 否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
    p.close()
    p.join()
    for res in res_l:
        print(res.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get

## 进程池的异步调用
回调函数 :
  • 需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数.
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def pasrse_page(res):
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    p=Pool(3)
    res_l=[]
    for url in urls:
        res=p.apply_async(get_page,args=(url,),callback=pasrse_page)
        res_l.append(res)

    p.close()
    p.join()
    print([res.get() for res in res_l]) #拿到的是get_page的结果,其实完全没必要拿该结果,该结果已经传给回调函数处理了

'''
打印结果:
<进程3388> get https://www.baidu.com
<进程3389> get https://www.python.org
<进程3390> get https://www.openstack.org
<进程3388> get https://help.github.com/
<进程3387> parse https://www.baidu.com
<进程3389> get http://www.sina.com.cn/
<进程3387> parse https://www.python.org
<进程3387> parse https://help.github.com/
<进程3387> parse http://www.sina.com.cn/
<进程3387> parse https://www.openstack.org
[{'url': 'https://www.baidu.com', 'text': '<!DOCTYPE html>\r\n...',...}]
'''
  • 使用多进程请求多个url来减少网络等待浪费的时间

    • 如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数

PYTHON 线程

  • 概述

    • 进程是资源分配的最小单位,线程是CPU调度的最小单位.一个进程至少拥有一个线程.

    • 进程和线程的区别 :

      • 1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
      • 2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
      • 3)调度和切换:线程上下文切换比进程上下文切换要快得多。
      • 4)在多线程操作系统中,进程不是一个可执行的实体。

一.线程的创建和使用

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('egon',))
    t.start()
    print('主线程')

创建线程的方式 - threading.Thread 类继承

我们可以通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法:

from threading import Thread
import time
class Sayhi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        time.sleep(2)
        print('%s say hello' % self.name)


if __name__ == '__main__':
    t = Sayhi('egon')
    t.start()
    print('主线程')

创建线程的方式 - threading.Thread 直接使用

Thread([group [, target [, name [, args [, kwargs]]]]])

  • group: 线程组,目前只能使用None
  • target: 执行的目标任务名
  • args: 以元组的方式给执行任务传参
  • kwargs: 以字典方式给执行任务传参
  • name: 线程名,一般不用设置
from  threading import Thread
from multiprocessing import Process
import os
def work():
    global n
    n=0

if __name__ == '__main__':
    ## n=100
    ## p=Process(target=work)
    ## p.start()
    ## p.join()
    ## print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100


    n=1
    t=Thread(target=work)
    t.start()
    t.join()
    print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
同一进程内的线程共享该进程的数据?

内存数据的共享问题

创建线程的方式 - _thread 开启

_thread.start_new_thread ( function, args[, kwargs] )
参数说明:

  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs - 可选参数。
import _thread
import time

# 为线程定义一个函数
def print_time( threadName, delay):
   count = 0
   while count < 5:
      time.sleep(delay)
      count += 1
      print ("%s: %s" % ( threadName, time.ctime(time.time()) ))

# 创建两个线程
try:
   _thread.start_new_thread( print_time, ("Thread-1", 2, ) )
   _thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
   print ("Error: 无法启动线程")

while 1:
   pass

二.守护线程

区别 :

1.对主进程来说,运行完毕指的是主进程代码运行完毕
    2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

详细解释 :

1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
      2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

  • 守护线程例1
from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('egon',))
    t.setDaemon(True) #必须在t.start()之前设置
    t.start()

    print('主线程')
    print(t.is_alive())
    '''
    主线程
    True
    '''
  • 守护线程例2
from threading import Thread
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


t1=Thread(target=foo)
t2=Thread(target=bar)

t1.daemon=True
t1.start()
t2.start()
print("main-------")

三.同步锁

  • 多个线程抢占资源的情况
from threading import Thread
import os,time
def work():
    global n
    temp=n
    time.sleep(0.1)
    n=temp-1
if __name__ == '__main__':
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果可能为99
import threading
R=threading.Lock()
R.acquire()#加锁
'''
对公共数据的操作
'''
R.release()#解锁
  • 同步锁的引用
  • View Code
from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

四.死锁与递归锁

死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁.

from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
  • 死锁
    • 解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

    • 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

递归锁RLock

五.信号量

同进程的一样Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1;计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

from threading import Thread,Semaphore
import threading
import time
## def func():
##     if sm.acquire():
##         print (threading.currentThread().getName() + ' get semaphore')
##         time.sleep(2)
##         sm.release()
def func():
    sm.acquire()
    print('%s get sm' %threading.current_thread().getName())
    time.sleep(3)
    sm.release()
if __name__ == '__main__':
    sm=Semaphore(5)
    for i in range(23):
        t=Thread(target=func)
        t.start()

六.事件

同进程的一样,线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

  • event.isSet():返回event的状态值;
  • event.wait():如果 event.isSet()==False将阻塞线程;
  • event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
  • event.clear():恢复event的状态值为False。

七.条件

  • 使得线程等待,只有满足某条件时,才释放n个线程,Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

  • 实例

import threading

def run(n):
    con.acquire()
    con.wait()
    print("run the thread: %s" % n)
    con.release()

if __name__ == '__main__':

    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()

    while True:
        inp = input('>>>')
        if inp == 'q':
            break
        con.acquire()
        con.notify(int(inp))
        con.release()
        print('****')

八.定时器

定时器,指定n秒后执行某个操作

from threading import Timer
 
def hello():
    print("hello, world")
 
t = Timer(1, hello)
t.start()  ## after 1 seconds, "hello, world" will be printed

九.线程队列

queue队列 :使用import queue,用法与进程Queue一样

  • 先进先出
      class queue.Queue(maxsize=0) #先进先出

import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''
  • 后进先出
      class queue.LifoQueue(maxsize=0) #last in fisrt out
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''


  • 优先级队列
      class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列
import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值