Python学习九:使用进程和线程

文章目录

  • 一、什么是进程
  • 二、创建进程的常用方法
    • 2.1使用multiprocessing模块创建进程
    • 2.2 使用Process子类创建进程
    • 2.3 使用进程池Pool创建进程
  • 三、进程间通讯
    • 3.1 队列简介
    • 3.2 多进程队列的使用
  • 四、什么是线程
  • 五、创建线程
    • 5.1 使用threading模块创建线程
    • 5.2 使用Thread子类创建线程
  • 六、线程间通信
    • 6.1 什么是互斥锁
    • 6.2 使用互斥锁
    • 6.3 使用队列在线程间通信

一、什么是进程

就是指操作系统能执行多个任务

eg:使用Windows操作系统可以同时听音乐、学习python课程等多个任务。每个任务就是一个进程。

进程四计算机已运行城西的实体 进程和程序不同,程序本身只有指令、数据、及其组织形式的描述,进程才是程序(那些指令和数据)的运行实例。

二、创建进程的常用方法

在python中有多个模块可以创建进程,比较常用的有os.fork()函数multiprocessing模块和Pool进程池

2.1使用multiprocessing模块创建进程

语法:
Process([group[,target[,name[,args[,kwargs]]]]])

  1. group:参数未使用,值始终为None
  2. target:表示当前进程启动时执行的可调用对象
  3. name:为当前进程实例的别名
  4. args:表示传递给target函数的参数元组
  5. kwargs:表示传递给target函数的参数字典
from multiprocessing import Process

# 执行子进程代码

def test(interval):
    print('我是子进程')

def main():
    print('主进程开始')
    p=Process(target=test,args=(1,))
    p.start()
    print('主进程结束')

if __name__ == '__main__':
    main()
>>>
主进程开始
主进程结束
我是子进程

上述代码中p实例除了常用的方法start()外,还有如下方法:

  • is_alive(): 判断进程实例是否还在执行
  • join([timeout]):是否等待进程实例执行结束,或者等待多少秒
  • start():启动进程实例(创建子进程)
  • run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法;
  • terminate():不管任务是否完成,立即终止
  • name:当前实例别名,默认为Process-N,N为从1开始递增的整数
  • pid:当前进程实例的PID值

# -*- coding:utf-8 -*-
from multiprocessing import Process
import time
import os

#两个子进程将会调用的两个方法
def  child_1(interval):
    print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
    t_start = time.time()   # 计时开始
    time.sleep(interval)    # 程序将会被挂起interval秒
    t_end = time.time()     # 计时结束
    print("子进程(%s)执行时间为'%0.2f'秒"%(os.getpid(),t_end - t_start))

def  child_2(interval):
    print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
    t_start = time.time()   # 计时开始
    time.sleep(interval)    # 程序将会被挂起interval秒
    t_end = time.time()     # 计时结束
    print("子进程(%s)执行时间为'%0.2f'秒"%(os.getpid(),t_end - t_start))

if __name__ == '__main__':
    print("------父进程开始执行-------")
    print("父进程PID:%s" % os.getpid())                  # 输出当前程序的ID
    p1=Process(target=child_1,args=(1,))                   # 实例化进程p1
    p2=Process(target=child_2,name="mrsoft",args=(2,))     # 实例化进程p2
    p1.start()  # 启动进程p1
    p2.start()  # 启动进程p2
    #同时父进程仍然往下执行,如果p2进程还在执行,将会返回True
    print("p1.is_alive=%s"%p1.is_alive())
    print("p2.is_alive=%s"%p2.is_alive())
    #输出p1和p2进程的别名和pid
    print("p1.name=%s"%p1.name)
    print("p1.pid=%s"%p1.pid)
    print("p2.name=%s"%p2.name)
    print("p2.pid=%s"%p2.pid)
    print("------等待子进程-------")
    p1.join() # 等待p1进程结束
    p2.join() # 等待p2进程结束
    print("------父进程执行结束-------")

>>>第一次执行

------父进程开始执行-------
父进程PID:1980
p1.is_alive=True
p2.is_alive=True
p1.name=Process-1
p1.pid=3304
p2.name=mrsoft
p2.pid=12300
------等待子进程-------
子进程(3304)开始执行,父进程为(1980)
子进程(12300)开始执行,父进程为(1980)
子进程(3304)执行时间为'1.01'秒
子进程(12300)执行时间为'2.01'------父进程执行结束-------

Process finished with exit code 0

>>>第二次执行
------父进程开始执行-------
父进程PID:10104
p1.is_alive=True
p2.is_alive=True
p1.name=Process-1
p1.pid=6004
p2.name=mrsoft
p2.pid=2852
------等待子进程-------
子进程(6004)开始执行,父进程为(10104)
子进程(2852)开始执行,父进程为(10104)
子进程(6004)执行时间为'1.00'秒
子进程(2852)执行时间为'2.00'------父进程执行结束-------

Process finished with exit code 0

2.2 使用Process子类创建进程

对于一些简单的小任务,使用Process(target=test)方式实现多进程。但是使用复杂的进程,通常定义成一个类,使其继承Process类每次实例化这个类的时候,就等同实例化一个进程对象。


# -*- coding:utf-8 -*-
from multiprocessing import Process
import time
import os

#继承Process类
class SubProcess(Process):
    # 由于Process类本身也有__init__初识化方法,这个子类相当于重写了父类的这个方法
    def __init__(self,interval,name=''):
        Process.__init__(self)      # 调用Process父类的初始化方法
        self.interval = interval    # 接收参数interval
        if name:                    # 判断传递的参数name是否存在
            self.name = name        # 如果传递参数name,则为子进程创建name属性,否则使用默认属性
    #重写了Process类的run()方法
    def run(self):
        print("子进程(%s) 开始执行,父进程为(%s)"%(os.getpid(),os.getppid()))
        t_start = time.time()
        time.sleep(self.interval)
        t_stop = time.time()
        print("子进程(%s)执行结束,耗时%0.2f秒"%(os.getpid(),t_stop-t_start))

if __name__=="__main__":
    print("------父进程开始执行-------")
    print("父进程PID:%s" % os.getpid())                  # 输出当前程序的ID
    p1 = SubProcess(interval=1,name='mrsoft')
    p2 = SubProcess(interval=2)
    #对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法,所以这里会执行p1.run()
    p1.start()  # 启动进程p1
    p2.start()  # 启动进程p2
    # 输出p1和p2进程的执行状态,如果真正进行,返回True,否则返回False
    print("p1.is_alive=%s"%p1.is_alive())
    print("p2.is_alive=%s"%p2.is_alive())
    #输出p1和p2进程的别名和pid
    print("p1.name=%s"%p1.name)
    print("p1.pid=%s"%p1.pid)
    print("p2.name=%s"%p2.name)
    print("p2.pid=%s"%p2.pid)
    print("------等待子进程-------")
    p1.join() # 等待p1进程结束
    p2.join() # 等待p2进程结束
    print("------父进程执行结束-------")


>>>
------父进程开始执行-------
父进程PID:19576
p1.is_alive=True
p2.is_alive=True
p1.name=mrsoft
p1.pid=20424
p2.name=SubProcess-2
p2.pid=19304
------等待子进程-------
子进程(20424) 开始执行,父进程为(19576)
子进程(19304) 开始执行,父进程为(19576)
子进程(20424)执行结束,耗时1.01秒
子进程(19304)执行结束,耗时2.00------父进程执行结束-------

2.3 使用进程池Pool创建进程

当需要创建的⼦进程数量不多时, 可以直接利⽤multiprocessing.Process动态生成多个进程, 但如果要创建很多进程时,⼿动创建的话⼯作量会非常大,此时就可以⽤到multiprocessing模块提供的Pool去创建一个进程池。

multiprocessing.Pool常⽤函数:

  • apply_async(func, args, kwds):使⽤⾮阻塞⽅式调⽤func(任务并⾏执⾏),args为传递给func的参数列表,kwds为传递给func的关键字参数列表
  • apply(func, args, kwds):使⽤阻塞⽅式调⽤func,必须等待上⼀个进程执行完任务后才能执⾏下⼀个进程,了解即可,几乎不用
  • close():关闭Pool,使其不再接受新的任务
  • terminate():不管任务是否完成,⽴即终⽌
  • join():主进程阻塞,等待⼦进程的退出,必须在close或terminate之后使⽤

初始化Pool时,可以指定⼀个最⼤进程数,当有新的任务提交到Pool中时,如果进程池还没有满,那么就会创建⼀个新的进程⽤来执⾏该任务,但如果进程池已满(池中的进程数已经达到指定的最⼤值),那么该任务就会等待,直到池中有进程结束才会创建新的进程来执⾏。

# -*- coding=utf-8 -*-
from multiprocessing import Pool
import os, time

def task(name):
    print('子进程(%s)执行task %s ...' % ( os.getpid() ,name))
    time.sleep(1)       # 休眠1秒

if __name__=='__main__':
    print('父进程(%s).' % os.getpid())
    p = Pool(3)
    for i in range(10):                 # 从0开始循环10次    
        p.apply_async(task, args=(i,))  # 使用非阻塞方式调用task()函数   
    print('等待所有子进程结束...')
    p.close()   # 关闭进程池,关闭后p不再接收新的请求
    p.join()    # 等待子进程结束
    print('所有子进程结束.')
>>>
父进程(22220.
等待所有子进程结束...
子进程(24124)执行task 0 ...
子进程(20972)执行task 1 ...
子进程(17992)执行task 2 ...
子进程(24124)执行task 3 ...
子进程(17992)执行task 4 ...
子进程(20972)执行task 5 ...
子进程(17992)执行task 6 ...
子进程(24124)执行task 8 ...
子进程(20972)执行task 7 ...
子进程(20972)执行task 9 ...
所有子进程结束.


这里看到的是不一定是顺序的

三、进程间通讯

3.1 队列简介

队列就是模仿现实中的排队

3.2 多进程队列的使用

可以使用 multiprocessing 模块的Queue实现多进程之间的数据传递。

初始化Queue()对象(例如:q=Quere(num)),若括号中没有指定最大可接受的信息数量,或数量为负值,那么久代表可接受的消息数量没有上线(直到内存的尽头)

  • Queue. qsize():返回当前队列包含的消息数量
  • Queue. empty():如果队列为空,返回Ture,反之返回False
  • Queue. full():如果队列满了,返回Ture,反之返回False
  • Queue. get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为Ture 1.如果block使用默认值,且没有设置timeout(单位,秒),消息队列为空,此时程序将被阻塞(停在读取状态),直到从消息队列读到信息为止,如果设置了timeout,则会等待timeout秒,若还没有读到任何信息,则抛出Queue.Empty异常 2.如果block值为False,消息队列为空,则会立即抛出Queue.Empty异常
  • Queue. get_nowait():相当于Queue.get(False)
  • Queue.put(item,[block[,timeout]]):将item消息写入队列,block默认值为Ture1.如果block使用默认值,且没有设置timeout(单位,秒),消息队列如果已经没有空间写入,此时程序将被阻塞(停在写入状态),直到从消息队列腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没有空间,则抛出Queue.Full异常 2.如果block值为False,消息队列没有空间写入,则会立即抛出Queue.Full异常
  • Queue.put_nowait(item):相当于Queue.put(item,False)
# -*- coding: utf-8 -*-
from multiprocessing import Process, Queue

# 向队列中写入数据
def write_task(q):
    if not q.full():
        for i in range(5):
            message = "消息" + str(i)
            q.put(message)
            print("写入:%s"%message)
# 从队列读取数据
def read_task(q):
    while not q.empty():
        print("读取:%s" % q.get(True,2)) # 等待2秒,如果还没读取到任何消息,则抛出"Queue.Empty"异常

if __name__ == "__main__":
    print("-----父进程开始-----")
    q = Queue()  # 父进程创建Queue,并传给各个子进程
    pw = Process(target=write_task, args=(q,))      # 实例化写入队列的子进程,并且传递队列
    pr = Process(target=read_task, args=(q,))       # 实例化读取队列的子进程,并且传递队列
    pw.start()   # 启动子进程 pw,写入
    pr.start()   # 启动子进程 pr,读取
    pw.join()    # 等待 pw 结束
    pr.join()    # 等待 pr 结束
    print("-----父进程结束-----")
>>>
-----父进程开始-----
写入:消息0
写入:消息1
写入:消息2
写入:消息3
写入:消息4
-----父进程结束-----

四、什么是线程

一个任务比作一个进程,一个进程有多个线程组成

线程是操作系统能够进行运算调度的最小单位

五、创建线程

由于线程是操作系统直接支持的执行单位,因此高级语言(python、java)通常内置了对线程的支持。python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数我们只要用threading这个高级模块

5.1 使用threading模块创建线程

threading模块提供了一个Thread类来代表一个线程对象,语法如下
Thread([group[,target[,name[,args[,kwargs]]]]])

  1. group:值为none,为以后版本而保留
  2. target:表示一个可调用对象,线程启动的时候,run()方法将调用此对象,默认值为None,表示不调用任何内容
  3. name:表示当前线程的名称,默认创建一个Thread-N格式的唯一名称
  4. args:表示传递给target函数的参数元组
  5. kwargs:表示传递给target函数的参数字典
# -*- coding: utf-8 -*-
import threading,time
def process():
    for i in range(3):
        time.sleep(1)
        msg = "子线程"+threading.current_thread().name+'执行,i='+str(i) #name属性中保存的是当前线程的名字
        print(msg)
if __name__ == '__main__':
    print('-----主线程开始-----')
    threads=[threading.Thread(target=process) for i in range(4)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print('-----主线程结束-----')
》》》

-----主线程开始-----
子线程Thread-3执行,i=0
子线程Thread-4执行,i=0
子线程Thread-2执行,i=0
子线程Thread-1执行,i=0
子线程Thread-3执行,i=1
子线程Thread-4执行,i=1
子线程Thread-1执行,i=1
子线程Thread-2执行,i=1
子线程Thread-4执行,i=2
子线程Thread-3执行,i=2
子线程Thread-1执行,i=2
子线程Thread-2执行,i=2
-----主线程结束-----

5.2 使用Thread子类创建线程

Thread 线程类和Process进程类的使用方式费城相似,也可以定义一个子类,使其继承Thread线程类来创建线程。

# -*- coding: utf-8 -*-
import threading
import time
class SubThread(threading.Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "子线程"+self.name+'执行,i='+str(i) #name属性中保存的是当前线程的名字
            print(msg)
if __name__ == '__main__':
    print('-----主线程开始-----')
    t1 = SubThread() # 创建子线程t1
    t2 = SubThread() # 创建子线程t2
    t1.start()      # 启动子线程t1
    t2.start()      # 启动子线程t2
    t1.join()       # 等待子线程t1
    t2.join()       # 等待子线程t2
    print('-----主线程结束-----')
>>>
-----主线程开始-----
子线程Thread-2执行,i=0
子线程Thread-1执行,i=0
子线程Thread-2执行,i=1
子线程Thread-1执行,i=1
子线程Thread-2执行,i=2
子线程Thread-1执行,i=2
-----主线程结束-----

六、线程间通信

线程之间可以通行,可以定义一个全局变量,让后不同线程调用这变量,可以共享数据

6.1 什么是互斥锁

就是防止多个线程同时读写某一块内存区域。互斥锁为资源引入一个状态:锁定和非锁定

6.2 使用互斥锁

mutex=threading.Lock() # 创建锁
mutex.acquire([blocking]) # 锁定
mutex.release() # 释放锁

acquire([blocking]) : 获取锁,如果有必要,需要阻塞到锁释放为止。如果提供blocking参数,并将它设置为False,当前无法获取锁定时候立即返回False,如果成功获取锁定则返回True

from threading import Thread,Lock
import time
n=100 # 共100张票                      

def task():
    global n
    mutex.acquire()             # 上锁
    temp=n                      # 赋值给临时变量
    time.sleep(0.1)             # 休眠0.1秒
    n=temp-1                    # 数量减1 
    print('购买成功,剩余%d张电影票'%n)
    mutex.release()             # 释放锁

if __name__ == '__main__':
    mutex=Lock()                # 实例化Lock类
    t_l=[]                      # 初始化一个列表
    for i in range(10):         
        t=Thread(target=task)   # 实例化线程类    
        t_l.append(t)           # 将线程实例存入列表中
        t.start()               # 创建线程
    for t in t_l:               
        t.join()                # 等待子线程结束    
>>>
购买成功,剩余99张电影票
购买成功,剩余98张电影票
购买成功,剩余97张电影票
购买成功,剩余96张电影票
购买成功,剩余95张电影票
购买成功,剩余94张电影票
购买成功,剩余93张电影票
购买成功,剩余92张电影票
购买成功,剩余91张电影票
购买成功,剩余90张电影票

6.3 使用队列在线程间通信

一种生产者和消费者模式

# -*- coding: utf-8 -*-
from queue import Queue
import random,threading,time

#生产者类
class Producer(threading.Thread):
    def __init__(self, name,queue):
        threading.Thread.__init__(self, name=name)
        self.data=queue
    def run(self):
        for i in range(5):
            print("生产者%s将产品%d加入队列!" % (self.getName(), i))
            self.data.put(i)
            time.sleep(random.random())
        print("生产者%s完成!" % self.getName())

#消费者类
class Consumer(threading.Thread):
    def __init__(self,name,queue):
        threading.Thread.__init__(self,name=name)
        self.data=queue
    def run(self):
        for i in range(5):
            val = self.data.get()
            print("消费者%s将产品%d从队列中取出!" % (self.getName(),val))
            time.sleep(random.random())
        print("消费者%s完成!" % self.getName())

if __name__ == '__main__':
    print('-----主线程开始-----')
    queue = Queue()        # 实例化队列
    producer = Producer('Producer',queue)   # 实例化线程Producer,并传入队列作为参数
    consumer = Consumer('Consumer',queue)   # 实例化线程Consumer,并传入队列作为参数
    producer.start()    # 启动线程Producer
    consumer.start()    # 启动线程Consumer
    producer.join()     # 等待线程Producer结束
    consumer.join()     # 等待线程Consumer结束
    print('-----主线程结束-----')
>>>
-----主线程开始-----
生产者Producer将产品0加入队列!
消费者Consumer将产品0从队列中取出!
生产者Producer将产品1加入队列!
消费者Consumer将产品1从队列中取出!
生产者Producer将产品2加入队列!
消费者Consumer将产品2从队列中取出!
生产者Producer将产品3加入队列!
生产者Producer将产品4加入队列!
生产者Producer完成!
消费者Consumer将产品3从队列中取出!
消费者Consumer将产品4从队列中取出!
消费者Consumer完成!
-----主线程结束-----

Process finished with exit code 0


你可能感兴趣的:(Python,python,学习,开发语言)