python并发编程之多进程、多线程、异步、协程、通信队列Queue和池Pool的实现和应用

python并发编程之多进程、多线程、异步、协程、通信队列Queue和池Pool的实现和应用
什么是多任务?
简单地说,就是操作系统可以同时运行多个任务。实现多任务有多种方式,线程、进程、协程。

并行和并发的区别?
并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的

真的多任务叫并行,假的多任务叫并发。

我们来了解下python中的进程,线程以及协程!

从计算机硬件角度:

计算机的核心是CPU,承担了所有的计算任务。 一个CPU,在一个时间切片里只能运行一个程序。

从操作系统的角度:

进程和线程,都是一种CPU的执行单元。

进程:表示一个程序的上下文执行活动(打开、执行、保存…)

线程:进程执行程序时候的最小调度单位(执行a,执行b…),可以简单理解为同一进程中有多个计数器,每个线程的执行时间不确定,而每个进程的时间片相等,线程是操作系统调度执行的最小单位

一个程序至少有一个进程,一个进程至少有一个线程。

多进程/多线程:
表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成。

进程每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。 进程之间的通信有操作系统传递,导致通讯效率低,切换开销大。进程是系统进行资源分配的最小单位

线程一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。线程不能够独立执行,必须依存在进程中。共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁"。

一个线程在访问内存空间的时候,其他线程不允许访问,必须等待之前的线程访问结束,才能使用这个内存空间。

互斥锁:一种安全有序的让多个线程访问内存空间的机制。

多线程:

密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。 threading.Thread、multiprocessing.dummy 缺陷:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。

但是,使用多线程,必须考虑到GIL 全局解释器锁:线程的执行权限,在Python的进程里只有一个GIL。

一个线程需要执行任务,必须获取GIL。

好处:直接杜绝了多个线程访问内存空间的安全问题。 坏处:Python的多线程不是真正多线程,不能充分利用多核CPU的资源。

但是,在I/O阻塞的时候,解释器会释放GIL。

什么是GIL?
GIL并不是Python的特性,Python完全可以不依赖于GIL。GIL全称Global Interpreter Lock。它是在实现Python解析器(CPython)时所引入的一个概念。GIL无疑就是一把全局排他锁。

Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能,如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现

GIL在较长一段时间内将会继续存在,但是会不断对其进行改进。
线程创建步骤:

复制代码
import threading

创建一个线程对象

t1 = threading.Thread(target=func_name, args=(num,), name=”子线程名字”)

创建一个线程并启动

t1.start()

等待子线程执行完毕之后再继续向下执行主线程

t1.join()
备注:主线程会等待子线程结束之后才会结束,主线程一死,子线程也会死。线程的调度是随机的,并没有先后顺序。
复制代码

由于多线程之间共享全局变量就会导致出现资源竞争的问题,为了避免这种竞争出现,利用互斥锁可以实现线程同步。

实现方式:

复制代码

创建锁

mutex = threading.Lock()

加锁

mutex.acquire()

释放锁

mutex.release()
复制代码

多进程:

密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。 multiprocessing 缺陷:多个进程之间通信成本高,切换开销大。

进程创建步骤:

复制代码

导入进程模块

import multiprocessing

创建一个进程的实力对象

P = Multiprocessing.Process(target=func_name, [args=(元组), kwargs={字典}])

创建并启动进程

p.start()
p.join(5)
复制代码

通过Queue实现通信(注意进程和协程使用的队列不一样)
复制代码
from queue import Queue # 线程、协程使用的队列
from multiprocessing import JoinableQueue as Queue # 进程使用的队列

q = Queue(maxsize=100)
item = {}
q.put_nowait(item) # 不等待直接放,队列满的时候会报错
q.put(item) # 放入数据,队列满的时候回等待
q.get_nowait() # 不等待直接取,队列空的时候会报错
q.get() # 取出数据,队列为空的时候会等待
q.qsize() # 获取队列中现存数据的个数
q.join() # 队列中维持了一个计数,计数不为0时候让主线程阻塞等待,队列计数为0的时候才会继续往后执行
q.task_done()

put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1

复制代码

注意:(多进程队列的使用非常特殊)

多进程中使用普通的队列模块会发生阻塞,对应的需要使用multiprocessing提供的JoinableQueue模块,其使用过程和在线程中使用的queue方法相同

如果是通过进程池创建的进程,那么队列的使用要用

multiprocessing.Manager().Queue()的方式,否则会报错。

进程池的实现步骤
复制代码

导入进程池模块

From multiprocessing import Pool

定义进程池,最大进程池最大数

Po = Pool(3)

通过进程池调用目标 apply_async非阻塞,不会等待子进程结束;apply阻塞,会等待子进程结束才结束

po.apply_async(func,(传递给目标的参数元祖,))

关闭进程池

Po.close()

等待进程池执行完毕

Po.join()

注意:

线程如果要使用线程池:from multiprocessing.dummy import Pool

协程如果要使用协程池:from gevent.pool import Pool

协程:

又称微线程,在单线程上执行多个任务,用函数切换,开销极小。不通过操作系统调度,没有进程、线程的切换开销。genvent,monkey.patchall

多线程请求返回是无序的,那个线程有数据返回就处理那个线程,而协程返回的数据是有序的。

缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高.

协程创建实例:

from gevent import monkey
import gevent
import random
import time
# 有耗时操作时需要
monkey.patch_all()  # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块

def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())
gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])

进程、线程、协程的区别和选择?
进程是资源分配的单位,真正执行代码的是线程,操作系统真正调度的是线程。

进程没有线程效率高,进程占用资源多,线程占用资源少,比线程更少的是协程。

协程依赖于线程、线程依赖于进程,进程一死线程必挂,线程一挂协程必死。

一般不用多进程,可以考虑使用多线程,如果多线程里面有很多网络请求,网络可能会有堵塞,此时用协程比较合适。

第二篇

1、异步和多线程区别:二者不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。多线程的好处,比较容易的实现了 异步切换的思想, 因为异步的程序很难写的。多线程本身程还是以同步完成,但是应该说比效率是比不上异步的。 而且多线很容易写, 相对效率也高。

2、异步和同步的区别: 在io等待的时候,同步不会切走,浪费了时间。异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。

3、 多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。

4、python因为GIL 全局锁的原因,所以同一时刻只能有一个线程在运行,遇到IO操作才会释放切换。当我们需要编写并发爬虫等IO密集型的程序时,应该选用多线程或者协程;当我们需要科学计算,设计CPU密集型程序,应该选用多进程,更好的利用多个CPU。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值