PYTHON多进程多线程

2018-08-18  本文已影响58人  _宁采臣
目录
  1.进程和线程
      1.1系统多任务机制
      1.2进程、线程和协程
          1.2.1进程(process)
          1.2.2线程(thread)
          1.2.3串行、并行、并发
  2.多线程编程
      2.1PYTHON中的多线程
          2.1.1threading模块属性和方法
          2.1.2thread类型属性和方法
      2.2函数式开发实现
          2.2.1 线程状态——join
          2.2.2  线程状态——daemon
          2.2.3  线程管理——锁【Lock/Rlock】
          2.2.4 线程管理——死锁【Dead Lock】
          2.2.5  线程管理——实践【Event】
          2.2.6   油条的故事——Event实现
          2.2.7 线程管理——条件【Condition】
          2.2.8  生产者消费者问题——Condition实现
          2.2.9  线程管理——队列【Queue】
          2.2.10  生产者消费者问题——Queue队列实现
          2.2.11 线程管理——其他
       2.3  面向对象开发实现
          2.3.1 面向对象那个多线程基本语法
  3.多进程编程
      3.1多进程概述
      3.2python多进程开发
          3.2.1multiprocessing
          3.2.2多进程的基础操作
          3.2.3 多进程面向对象实现
          3.2.4 带参数的多进程:共享?独占?
          3.2.5 多进程的简化:内置进程池
          3.2.6 多个进程通信:multiprocessing.Manager
          3.2.7多个进程通信:multiprocessing.Queue
          3.2.8 多个进程通信:multiprocessing.Pipe

1.进程和线程

1.1系统多任务机制

多任务操作机制的引入主要是在相同的硬件资源下怎么提高任务处理效率的!多任务的处理机制可以在提升任务处理效率的基础上,快速提升用户体验!

我们现实生活中无时无刻不在上演着多任务处理操作方式,听着音乐瞧着代码,看着小说吃着大餐等等,都是多任务的体现,计算机在还原生活中功能处理流程的同事,也在还原生活中的多任务操作机制,计算机上的各种编程语言在多任务处理机制上都有非常友好的技术支持和实现方式

PYTHON本身也支持多任务处理,并且提供了如下的操作方式
多线程多任务处理机制
多进程多任务处理机制
协程多任务处理机制

1.2进程、线程和协程
1.2.1 进程(process)

进程:计算机中一个程序在一个数据集上一次动态执行过程,主要包含三部分内容
程序:描述进程的功能以及处理流程
数据集:功能处理过程中需要的资源数据
进程控制:严格控制进程执行过程中的各种状态
通俗来说,一个进程就是计算机上正在运行的一个程序

一个软件程序要运行,需要将软件依赖的数据加载到内存中,通过CPU进行运算并按照程序定义的逻辑结构进行流程控制,直到数据处理完成后程序退出!
在程序实际执行过程中,进程只是分配需要的数据资源,是程序的主题,在程序运行时真正运行的是线程,每个进程至少会有一个线程

1.2.2 线程(Thread)

计算机中程序运行的实际执行者就是线程,线程又称为轻量级进程,是一个CPU的执行单元,每个进程至少会有一个主线程用于执行程序

线程和进程对比如下:
一个进程可以有多个进程,但是至少有一个主线程
一个线程只能属于一个进程
一个进程中多个线程,可以共享进程中提供的数据
CPU运算分配给线程,CPU上执行运算的是线程
线程是最小的运行单元,进程是最小的资源管理单元

1.2.3串行、并行、并发

串行:就是传统意义上的同步、顺序的意思,按照一定的执行步骤顺序执行每个环节

并行:就是传统意义上的异步,同时的意思,同时执行接收到的多个任务

并发:同时接收到多个任务,同时执行多个任务,但是具体到某个时刻~只是在执行一个任务,只是在很短时间内在多个任务之间切换,模拟形成了多个任务同时执行的现象

而大部分计算机中的因公用程序的执行,一般都是并发执行机制的多任务处理机制

因为CPU的核心数目限制,以前的CPU每个核心只有一个独立的线程,也就是江湖传言的单核单线程CPU,只能执行一个运行线程,为了能让计算机实现多任务同时处理的机制,计算机就给CPU定义了时间片轮询机制,让CPU在很短的一个时间片执行任务A,下一个时间片执行任务B、下一个时间片执行任务C。这样短时间的切换就会造成多个任务同时运行的表象!

现在所说的CPU一般都是四核八线程,或者或者八核十六线程之类的,其实就是表示了CPU在同一时间可以同时执行多少个线程程序,也就是CPU级别的多线程运行机制。

但是PYTHON为了保证多任务机制下的共享数据的安全性和完整性,Cpython官方解释器内置了一个GIL(Global Interceptor Lock:全局解释器锁),只允许在同一时间内CPU只能执行一个线程,所以在PYTHON的官方解释器下,所谓多线程是多线程并发机制并不是多线程并行机制!

2.多线程编程

在python2中提供了标准模块thread和threading支持多线程的并发编程,但是随着并发编程的实际使用操作过程,thread模块国语底层的控制方式对于并发编程的新手来说不是很友好,要求多线程的程序开发逻辑思维清晰同时又具备大量开发经验的情况下,可以控制的非常精细
python3中将thread模块进行了规范内置,更名为_thread,友好的提醒如果你不是并发编程的骨灰级爱好者,请不要轻易阐释使用_thread进行操作,而胡思推荐使用操作更加灵活使用更加简洁的threading模块进行并发编程的处理。

_thread模块多线程并发任务的简单实现如下:

import  _thread,time

#定义函数,函数中执行循环遍历指定的循环
def test(num):
    for i in range(num):
        print(_thread.get_ident(),":",i)
        
#通过_thread.start_new_thread()启动两个线程,分别执行test函数
_thread_start_new_thread(test,(2,))
_thread_start_new_thread(test,(3,))

#让主线程休眠3s,等待子线程执行结束
time.sleep(3)

2.1 PYHTON中的多线程

官方推荐的_threading模块的多线程并发编程机制,结合是下流行的面向过程、面向独享的编程处理模式,主要有两种操作方式
(1)函数式的线程创建方式,适合面向过程程序的并发编程实现
(2)面向对象的创建方式,适合面向对象程序的并发编程实现

2.1.1threading模块的属性和方法
Thread                                   线程类,用于创建和管理线程
Event                                      事件类,用于线程同步
Condition                                条件类,用于线程同步
Lock/Rlock                               锁类,用于线程同步
Timer                                 延时线程,一批能够与在一定事件后执行一个函数
Semaphore/BoundedSemaphore      信号量类,用于线程同步
active_count()/activeCount()       获取当前alive状态的所有线程数量
current_thread()/currentThread()      获取当期正在执行的线程对象
get_ident()                获取运行中程序当前线程的唯一编号
enumerate()                   获取所有alive状态线程列表
local                                  线程局部数据类
stack_size([size])                    获取线程占用内存栈的大小
main_thread                         获取主线程
2.1.2 Thread 类型属性和方法
__init__(group,target,name,args,kwargs)       构造方法,创建线程类型
is_alive()/isAlive()               判断当前线程是否alive状态
run()                          线程执行方法,自定义线程必须重写该函数
start()                       线程启动方法
join(【timeout = None】)    线程独占,等待当前线程运行结束或者超时
ident                     标识当前线程的唯一编号
name                       当前线程名称
daemon                   布尔值,判断当前线程是否守护线程
2.2 函数式开发实现

通过threading 模块的Thread函数们可以实现多线程程序的运行
案例需求:火车站窗口售票
(1)单线程实现模式:相当于只有一个窗口售票

import time
#总票数
count = 10

def sale_tickle():
    global count
    while count >0:
        print("售出一张票”,count)
        count -= 1
        time.sleep(0.5)
     else:
         print("售票结束,没有票了”)
if __name__ == "__main__"
    sale_tickle()

多线程售票:相当于多个窗口同时售票

import time
#总票数
count = 10

def sale_tickle():
    global count
    while count >0:
        print("售出一张票”,count)
        count -= 1
        time.sleep(0.5)
     else:
         print("售票结束,没有票了”)
if __name__ == "__main__"
    #定义多个线程(窗口)
    t1 = threading.Thread(name = "窗口1",target = sale_tickle)
     t2 = threading.Thread(name = "窗口2",target = sale_tickle)
     t3 = threading.Thread(name = "窗口3,target = sale_tickle)
     t4= threading.Thread(name = "窗口4",target = sale_tickle)
     t5 = threading.Thread(name = "窗口5",target = sale_tickle)
#启动五个串口同时售票
t1.start()
t2.start()
t3.start()
t4.start()
t5.start()
#程序运行要比单线程运行节省好几倍的时间,这就是多线程的魅力!
2.2.1线程状态_join

join:线程的join状态是独占模式,当前线程独占CPU运行单元,必须等待当前线程执行完成或者超时之后,才能运行其他线程

import time
#总票数
count = 10

def sale_tickle():
    global count
    while count >0:
        print("售出一张票”,count)
        count -= 1
        time.sleep(0.5)
     else:
         print("售票结束,没有票了”)
if __name__ == "__main__"
    #定义多个线程(窗口)
    t1 = threading.Thread(name = "窗口1",target = sale_tickle)
     t2 = threading.Thread(name = "窗口2",target = sale_tickle)
     t3 = threading.Thread(name = "窗口3,target = sale_tickle)
     t4= threading.Thread(name = "窗口4",target = sale_tickle)
     t5 = threading.Thread(name = "窗口5",target = sale_tickle)
#启动五个串口同时售票
t1.start()

#线程t1调用join,独占模式运行,等待t1线程运行结束或者超时,才能继续运行其他线程
t1.join()

t2.start()
t3.start()
t4.start()
t5.start()
#程序运行要比单线程运行节省好几倍的时间,这就是多线程的魅力!
2.2.2线程状态-daemon

线程对象的daemon属性用于标识某个线程是否守护线程
守护线程的意义在于~跟主线程之间山无陵天地合,同生共死,一旦主线程执行退出,无论守护线程是否执行完成,都会直接退出!

2.2.3线程管理—锁【Lock/Rlock】

多线程程序在运行过程中,由于多个线程访问的是同一部分数据,很容易会造成共享数据访问冲突的现象,如果一旦出现冲突程序就会出现执行结果不符合期望的结果

此时共享数据的修改操作,在多线程的情况下,是需要通过锁定的方式进行独占修改的!就如同入厕一样,当多个人[线程】在执行程序,修改数据【入厕】时,每个线程在操作过程中都需要锁定这一部分数据【厕所】,直到数据处理完成之后解锁,下一个线程才能进行操作
python中提供了两种线程锁的操作
(1)同步锁/互斥锁:lock
(2)可重用锁 :Rlock
锁的操作主要是获取锁和释放锁两种
(1)acquire()获取锁,上锁,锁定
(2)release()释放锁,开锁,解锁

2.2.4 线程管理-死锁【Dead Lock】
线程锁固然功能强大,可以管理多个线程之间的共享数据问题, 但是同时它的强大也带了了比较纠结的问题,需要开发人员对于锁定的数据有一个良好的人认知,否则特别容易造成死锁的现象,比较著名的哲学家吃饭问题就是死锁的典型代表

由于计算机运算速度较快,所有有两种方案可以将问题放大
(1)给执行函数添加休眠时间
(2)添加线程数量
死锁并不是每次都会出现的,而是程序在执行过程中,根据系统CPU时间片的切换机制恰好遇到了重复上锁的情况,就会死锁。

实际项目开发过程中,一定要注意死锁情况的影响
这样的情况可以通过可重用锁Rlock进行锁定处理

pyhton中的互斥锁,只有两种状态,locked和unlocked,如果一旦重复上锁就会死锁,但是可重用所Rlock ,在锁定的基础上提供了一个计数器counter,可以计算上锁的次数然后通过release()解锁时就会重新运算计数器,等待计数器清零时所有锁全都释放了!

2.2.5 线程管理—时间【Event】

线程锁解决了多个线程访问共享数据时冲突的问题,如果多个线程之间需要通信应该怎么解决呢?此时就需要用到多个线程之间的可以用于互相通信的处理对象了,该处理对象必须满足如下基本条件:
(1)该对象能同时被多个线程访问
(2)该对象可以被标记不同的状态
(3)该对象可以用于控制线程等待|运行之间的切换
pyhton提供了一个事件对象Event,可以基本满足上述条件,完成线程之间的通信

set()              添加一个标记状态
isSet()/is_set()       检查事件对象是否被标记             
clear()               清除标记状态
wait()              事件对象操作的当前线程等待,直到该对象被标记状态
2.2.6 油条的故事- Event实现

需求:一个冬天的早晨,顾客去小摊贩那里买油条,由于油条都是现炸,所以顾客需要等待小摊贩生产油条,小摊贩生产好油条后顾客可以进餐,结束后打招呼离开

分析:这里有两个线程,小摊贩线程和顾客线程,顾客线程运行开始必须等待,小摊贩线程工作生产油条,当油条生产之后唤醒顾客线程,此时小摊贩线程等待,顾客就餐完毕之后准备离开,唤醒小摊贩结账走人!

两个线程的通信:事件对象:threading.Event
set()添加标记
wait()线程等待-如果当前事件对象被标记~继续运行
clear()清除标记
import threading,time
#定义事件对象
event = threading.Event()

def xiao_fan():
    print("XF,炸油条.......")
    time.sleep(2)
    #添加标记
    event.set()
    event.clear()
    
    print("XF, 卖油条“)
    
    event.wait()
    print("XF:结账完毕,谢谢光临”)
def gu_ke():
    #线程等待~等待事件对象被标记
    event.wait()
    print("GK:买油条“)
    print("GK:吃油条”)
    time.sleep(2)
if __name__ == "__main__":
    xf = threading.Thread(target = xiao_fan)
    gk = threading.Thread(target = gu_ke)

    xf.start()
    gk.start()
2.2.7 线程管理-条件【Condition】

线程条件Condition对象,也是多
线程并发模式下一种线程之间通信的友好支持
在某些情况下,我们需要多个线程在运行过程中根据实际操作情况,不同功能的线程在满足对应的条件时等待、启动两种状态之间进行切换

如经典的线程间通信问题:生产者消费者问题
生产者负责上生产食物,将食物存储在列表中;消费者负责消费,也就是从列表中删除数据;这里的存储食物的列表,我们限制了长度,最多容纳20个食物数据
此时就会出现这样的问题,如果列表中的食物已经达到20;那么所有的生产者线程不能继续生产食物了,必须处于等待状态,等待消费者消费了食物之后再次生产
同理如果列表中的食物为空了,所有的消费者也就不能吃食物了,必须处于等待状态,等待生产者生产了食物之后才能消费

Conditon()对象的属性和方法
acquire()                                        锁定
release()                                        解锁
wait()                                        释放锁,同时阻塞当前线程,等待被唤醒
wait_for()                            释放锁,同时阻塞当前线程,等待被唤醒
notify()                                      唤醒
notify_all()                            唤醒所有等待condition条件的线程
2.2.8 生产者消费者问题-Condition 实现

需求:生产者消费者问题,描述的是多个线程之间的通信处理方式和手段。多个生产者线程生产食品放到指定的食品容器中,并唤醒所有的消费者线程开始就餐如果食品容器容量饱和,则所有生产者线程等待
多个消费者线程在指定的视屏容器中获取食物就餐,并唤醒所有的生产者线程开始生产,如果食品容器中没有任何视屏了,则所有消费者线程等待

import threading,time,random
#定义食物列表
foods = list()
#创建一个线程条件对象
con = threading.Conditon()
def product():
    while True:
        time.sleep(0.5)
        con.acquire()
        if  len(foods)<20:
            _no = random.randint(0,20)
            print("生产者{}生产了:format(threading.current_thread().getName(),_no)
            foods.append(_no)
            print("PRO--",len(foods))
            con.notify()
        else:
            con.wait()
            print("生产者{}----等待”。format(threading.current_thread().getName()))
con.release()

def  consumer():
    while True:
        time.sleep(0.5)
        con.acquire()
        if len(foods) >0:
            _no = foods.pop()
            print("消费者{}消费了".format(threading.current_thread().getName()), _no)
            print("CUS--", len(foods))       
            con.notify()
        else:
            con.wait()
            print("消费者{}-----等待”.format(threading.current_thread().getName()))
         con.release()
if __name__ == "__main__":
    #创建多个生产者线程
    for i in range(5):
        p = threading.Thread(name = "_p" + str(i),target = product)
        p.start
    #创建多个消费者线程
    for j in range(2):
        c = threading.Thread(name = "_c" + str(j),target = consumer)
        c.start()
2.2.9 线程管理-队列【Queue】

多线程并发编程的重点,是线程之间共享数据的访问问题和线程之间的通信问题
为了解决线程之间数据共享问题,python提供了一个数据类型【队列】可以用于在多线程并发模式下,安全的访问数据而不会造成数据共享冲突
pyhton中queue模块提供的队列类型Queue的操作模式如下

put(【timeout = None】) 想队列中添加数据,队列如果满了,一直阻塞知道超市或者队列中有数据被删除之后添加成功
get(【timeout = None】)从队列中获取数据,如果队列为空,一直阻塞知道超时或者队列中添加数据之后获取成功

2.3面向对象开发实现

pyhton人性化的提供了使用与更加强大场合的多线程并发编程
面向对象的操作模式,让python的多线程并发更加优秀

2.3.1面向对象多线程基本语法

面向对象的多线程,主要是让自定义类型派生自threading.Thread类
重写Thread类型的run()方法,然后创建自定义线程类的对象之后,调用start()方法启动

3.多进程编程

3.1多进程概述

进程是正在执行中国的应用程序,一个进程包含了该应用程序的所有信息,如加载数据内存空间、代码、程序数据、对象句柄、执行单元等等,一个应用程序根据其功能的多样性,可以通过多个进程并发的形式来实现

计算机中多线程的操作已经可以实现多任务的处理机制了,但是如果实际到多核CPU或者多个CPU的硬件主机,多进程并发编程的实现能比多线程并发机制更加有效的利用和发挥硬件资源优势

3.2pyhton多进程开发

3.2.1multiprocessing

python内建标准模块multiprocessing 对多进程并发编程提供了良好的支持,通过该模块的Process进程类型,可以很方便的创建和管理多个进程,通过该模块提供的Lock|Rlock进程锁类型、Event事件类型。Condition条件类型等等也可以很方便的完成进程间同步操作

和多线程的操作方式类似,多进程的实现方式也提供了面向过程的实现和面向对象的实现,同时多进程的本地数据共享和通信模式也非常的类似多线程编程

multiprocessing常见属性和方法

Process                进程类型,用于创建和管理进程
Lock/Rlock            进程互斥锁/重用锁,用于进程同步
Event                     进程事件类型,用于进程同步
Condition               进程条件类型,用于进程同步
Queue                   进程队列类型,用于多进程数据共享
Manager                 进程管理类型,用于多进程数据共享
listener|Client         进程监听|客户端,基于网络多进程之间的数据共享
3.2.2多进程的基础操作

main方法运行的是主进程,通过multiprocessing创建的子进程是由主进程产生的!

3.2.3对进程面向对象实现

多进程的面向对象的实现方式类似多线程的操作模式
自定义进程类型,继承系统进程标准类型multiprocessing.Process
重写父类的run()方法,在方法中定义执行代码
在使用时创建该自定义进程类型的对象,调用对象的start()方法启动一个新的进程

3.2.4带参数的多进程:共享?独占?

多线程的操作模式下我们的全局变量是多个线程共享的,所以多线程并发模式下对于数据的修改非常危险,那么多进程模式下数据的处理应该是什么样的呢?
通过两种方式来观察多进程模式下数据的处理
(1)全局变量的数据
(2)参数数据

3.2.5 多进程的简化:内置进程池

多进程的操作在实际应用中也是非常多的,但是纯底层的代码开发控制并发也是一件非常繁琐的事情,所以就出现了面向过程多进程并发的优化操作方式:进程池pool
通过进程池pool可以快速创建多个进程执行指定函数,完成高并发处理操作
(1)Pool对象的属性和方法
apply(func,args) 传递参数args并执行函数func,同时阻塞当前进程知道该函数执行完成,函数func智慧在进程池中的一个进程中运行
apply_async(func,args,callback,error_callback)
传递参数args并执行函数func,该方法不会形成阻塞,函数执行完成之后可以通过结果对象的get(方法获取结果,如果结果对象可用是会自动调用callback指定的函数,如果结果对象调用失败是会自动调用error_callback指定的函数
close() Pool进程池的底层工作机制是向进程池提交任务产生工作进程执行该方法是主动停止给进程池提交任务,并等待所有提交任务执行完成退出
terminate()立即结束该进程,当进程池对象被回收时自动调用该方法
join()等待工作进程退出,再次之间必须调用close()或者terminate

3.2.6 多个进程通信:multiprocessing.Manager

不同进程之间的数据通信,涉及到核心的数据共享问题,主要由python中提供的内建模块multiprocessing.Manager类型实现,该类型内置了大量的用于数据共享的操作

multiprocessing.Manager 常见属性和方法

Array             内置进程间共享数组类型
Queue           内置进程间共享队列类型
list()          内置进程间共享列表类型
dict()         内置进程间共享字典类型
Value             内置进程间共享值类型
Barrier           进程同步类型
BoundedSemaphore|Semaphore 进程信号量类型
Lock|Rlock               进程互斥锁/重用锁
Event                      进程同步时间类型
Conditon                  进程同步条件类型
3.2.7 多个进程通信:multiprocessing.Queue

多个进程之间的痛惜操作,数据的床底在pyhotn中的multiprocesisng模块中提供了一个专门用于多进程之间进行数据传递的队列:Queue

multiprocessing.Queue常见属性和方法

put(data【,timeout= None】) 添加一个数据到队列中
put_nowait(data)                 添加一个数据到队列中,非阻塞模式
get(【timeout = None】) 从队列中获取一个数据
get_nowait()        从队列中获取一个数据,非阻塞模式
full()               判断队列是否已满
empty()            判断队列是否已空
close()                     关闭队列
qsize()                        获取队列中的元素数量
3.2.8 多个进程通信:multiprocessing.pipe

python为了更加友好的多个进程之间的数据通信操作,提供了一个管道类型专门用于进程之间的协作:multipricessing.pipe

multiprocessing.pipe 常见属性和方法

__init__(duplex = True)         初始化方法,返回两个数据conn1,conn2分别表示管道的两端,默认是双向通信,如果duplex = False,  conn1只能接受消息,  conn2只能发送消息
send
上一篇下一篇

猜你喜欢

热点阅读