python之多并发

目录

  • 一、进程/线程是什么?
  • 二、进程与线程的区别?
  • 三、进程的基本使用
    • 3.1 进程(multiprocessing)
      • 3.1.1 进程创建(multiprocessing.Process)
      • 3.1.2 获取进程名称和PID
    • 3.2 进程池(Pool)
      • 3.2.1 Pool方法
      • 3.2.2 Pool常用函数
      • 3.2.3 进程池实例
    • 3.3 消息队列的基本操作(Queue)
  • 四、线程
    • 4.1 线程(threading)
      • 4.1.1 线程创建(threading.Thread)
      • 4.1.2 自定义线程类
    • 4.2 线程其他方法
      • 4.2.1 线程锁(threading.RLock/threading.Lock)
  • 五、协程
    • 5.1 协程 - yield
    • 5.2 协程 - greenlet
    • 5.3 协程 - gevent


一、进程/线程是什么?

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;是资源分配的最小单位。

特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。是程序执行的最小单位。

二、进程与线程的区别?

1、线程共享内存空间,进程的内存是独立的
2、同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现
3、一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程

三、进程的基本使用

3.1 进程(multiprocessing)

3.1.1 进程创建(multiprocessing.Process)

方法:
multiprocessing
模块时跨平台版本的多进程模块,用Process来创建一个进程实例,start()启动。

import multiprocessing
 
def test1():
    print("in the test 1")
 
if __name__ == '__main__':
    process_obj = multiprocessing.Process(target=test1)  # 创建子进程对象
    process_obj.start()  # 启动进程
    process_obj.join()  # 阻塞进程

子进程的语法结构、常用方法和常用属性:
Process方法:
multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

参数:
group: 默认为None(目前未使用)
target: 是函数名字,需要调用的函数
name: 为进程名称
args: 调用对象的位置参数元组,args=(value1, value2, …)
kwargs: 调用对象的字典,kwargs={key1:value1, key2:value2, …}
daemon: 表示进程是否为守护进程,布尔值

方法介绍:
Process.start() 启动进程,并调用子进程中的run()方法
Process.run() 进程启动时运行的方法,在自定义时必须要实现该方法
Process.terminate() 强制终止进程,不进行清理操作,如果Process创建了子进程,会导致该进程变成僵尸进程
Process.join() 阻塞进程使主进程等待该进程终止
Process.kill() 与terminate()相同
Process.is_alive() 判断进程是否还存活,如果存活,返回True
Process.close() 关闭进程对象,并清理资源,如果进程仍在运行则返回错误

3.1.2 获取进程名称和PID

获取进程名称:multiprocessing.current_process()
获取进程PID:multiprocessing.current_process().pid()

3.2 进程池(Pool)

3.2.1 Pool方法

Pool模块控制着一个进程池,池中是可以执行很多任务的进程。

multiprocessing.Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None, context=None)
参数介绍:
processes: 设置要使用的进程数量,如果 processes 为 None,则使用 os.cpu_count() 返回的值
initializer: 是每个工作进程启动时要执行的可调用对象,默认为None
maxtasksperchild: 工作进程退出之前可以完成的任务数,完成后用一个新的工作进程来替代原进程,为了释放闲置资源
context: 可被用于指定启动的工作进程的上下文

3.2.2 Pool常用函数

1、.apply() 进程池中进程以同步方式执行任务;
2、.apply_async() 进程池中进程以异步方式执行任务;
如果使用apply_async方式,需要做以下两点:
(1)pool.close() 表示不再接收新的任务
(2)pool.join() 让主进程等待进程池执行结束后再退出

3.2.3 进程池实例


import multiprocessing
 
def copy_work(a,b):
    """用于模拟文件拷贝的函数"""
    print("正在拷贝文件...",multiprocessing.current_process(),a,b)
 
if __name__ == '__main__':
    # 创建进程池
    pool = multiprocessing.Pool(3)  # 最大允许创建3个进程
    for i in range(10):
        # 让进程池以同步方式执行copy_work
        pool.apply(copy_work,(10,20))

3.3 消息队列的基本操作(Queue)

使用multprocessing模块的Queue实现多进程之间的是数据传递

import multiprocessing
 
# 创建队列
queue = multiprocessing.Queue(5)  # 5表示队列长度为5

Queue常用函数:
放值:
queue.put() 放入队列值

取值:
queue.get()

判断队列的消息数量:
queue.qsize()

判断队列是否为空:
queue.empty()

注意:
1、队列取值空,此时队列会处于阻塞状态,直到有新的值进入队列。
2、有一定的概率会打印相反的结果,因此在调用**empty()**方法前,通常可以sleep 0.00001秒

四、线程

4.1 线程(threading)

4.1.1 线程创建(threading.Thread)

Thread()方法:
threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

参数介绍:
group: 官方的解释是,为了日后扩展ThreadGroup类实现而保留。
target: 是要于多线程的函数
name: 是线程的名字
args: 函数的参数,类型是元组()
kwargs: 函数的参数,类型是字典{}

使用函数:
.start() : 激活线程
.getName() : 获取线程的名称
.setName() : 设置线程的名称
.name : 获取或设置线程的名称
.is_alive() : 判断线程是否为激活状态
.isAlive() : 判断线程是否为激活状态
.setDaemon() 通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之后才可以使用。如果不是守护线程,主线程执行过程中,守护线程也在进行,主线程执行完毕后,守护线程不论成功与否,均停止
.isDaemon() : 判断是否为守护线程
.ident : 获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。
.join() : 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
.run() : 线程被cpu调度后自动执行线程对象的run方法

4.1.2 自定义线程类

通过threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要以下三步:
1.让自定义类继承 threading.Thread
2.让自定义类重写run方法
3.通过实例化自定义类对象.start()方法启动自定义线程

import threading,time
 
 
# 自定义线程类
class MyThread(threading.Thread):
    def __init__(self, num):
        super().__init__()  # 要先调用父类的init方法否则会报错
        self.num = num
 
    # 重写 父类run方法
    def run(self):
        for i in range(self.num):
            print("正在执行子线程的run方法...",i)
            time.sleep(0.5)
 
if __name__ == '__main__':
    mythread = MyThread(5)
    mythread.start()

4.2 线程其他方法

4.2.1 线程锁(threading.RLock/threading.Lock)

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

lock = threading.RLock()
#或
lock = threading.Lock()

lock.acquire()  # 获得锁 
# +锁定中间代码
lock.release()  # 释放锁 

注意: 在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁

五、协程

在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。

5.1 协程 - yield

import time

def test1():
    while True:
        print("---in front of the test1 ---")
        yield
        print("---in behind of the test1 ---")
        time.sleep(0.5)
        
        
def test2():
    while True:
        print("---in front of the test2 ---")
        yield
        print("---in behind of the test2 ---")
        time.sleep(0.5)
         
def main():
    test_01 = test1()
    test_02 = test2()
    while True:
        next(test_01)
        next(test_02)
        
if __name__ == '__main__':
    main()

运行结果:

>>>---in front of the test1 ---
>>>---in front of the test2 ---
>>>---in behind of the test1 ---
>>>---in front of the test1 ---
>>>---in behind of the test2 ---
>>>---in front of the test2 ---
>>>---in behind of the test1 ---
>>>---in front of the test1 ---
>>>---in behind of the test2 ---
>>>---in front of the test2 ---

5.2 协程 - greenlet

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单

import time
from greenlet import greenlet


def test1():
    while True:
        print("---in front of the test1 ---")
        gr1.switch()  # 切换到gr2中运行
        print("---in behind of the test1 ---")
        time.sleep(0.5)
        
        
def test2():
    while True:
        print("---in front of the test2 ---")
        gr2.switch()  # 切换到gr2中运行
        print("---in behind of the test2 ---")
        time.sleep(0.5)

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


gr1.switch()

greenlet 已经实现了协程,但是这个还的人工切换,太麻烦了。

5.3 协程 - gevent

gevent原理是当一个 greenlet 遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,就会产生堵塞,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

import gevent

def test(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        #用来模拟一个耗时操作,注意不是time模块中的sleep,注意和time.sleep(1)的结果区别
        gevent.sleep(1)

# 方式一:
g1 = gevent.spawn(test,5)
g2 = gevent.spawn(test,5)
g3 = gevent.spawn(test,5)
g1.join()
g2.join()
g3.join()
# 方式二:
gevent.joinall([
        gevent.spawn(test),
        gevent.spawn(test)
])

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

轻松一刻:
python之多并发_第1张图片

你可能感兴趣的:(java,开发语言)