【Python】学习笔记 -- CH13. 进程与线程

CH13. 进程与线程

  • 初识程序与进程
    • 程序(Program), 是指一系列有序指令的集合,使用编程语言所编写,用于实现一定的功能
    • 进程,进程则是指启动后的程序,系统会为进程分配内存空间

函数式创建子进程

创建进程的方式

  • 第一种创建进程的语法结构:函数式
    • Process(group=None, target, name, args, kwargs)
    • 参数说明:
      • group: 表示分组,实际上不适用,值默认为None即可
      • target: 表示子进程要执行的任务,支持函数名
      • name: 表示子进程的名称
      • args:表示调用函数的位置函数,以元组的形式进行传递
      • kwargs:表示调用函数的关键字参数,以字典的形式进行传递
import os
import time
from multiprocessing import Process


def test():
    print(f'我是子进程,我的PID是:{os.getpid()}, 我的父进程是:{os.getppid()}')
    time.sleep(1)

if __name__ == '__main__':
    print('主进程开始执行')
    lst = []
    # 创建五个子进程
    for i in range(5):
        # 创建子进程
        p = Process(target=test)

        #启动子进程
        p.start()
        # 启动中的进程添加到列表中
        lst.append(p)
    
    for item in lst: # Process类型
        item.join() # 阻塞主进程

    print('主进程执行结束')
'''
PS D:\2_Demolight\python\study-plan\single-files> & C:/Python312/python.exe d:/2_Demolight/python/study-plan/single-files/test_process.py
主进程开始执行
主进程执行结束
我是子进程,我的PID是:29996, 我的父进程是:28144
我是子进程,我的PID是:30228, 我的父进程是:28144
我是子进程,我的PID是:17140, 我的父进程是:28144
我是子进程,我的PID是:26652, 我的父进程是:28144
我是子进程,我的PID是:9616, 我的父进程是:28144
PS D:\2_Demolight\python\study-plan\single-files> & C:/Python312/python.exe d:/2_Demolight/python/study-plan/single-files/test_process.py
主进程开始执行
我是子进程,我的PID是:2828, 我的父进程是:2456
我是子进程,我的PID是:27192, 我的父进程是:2456
我是子进程,我的PID是:15064, 我的父进程是:2456
我是子进程,我的PID是:18972, 我的父进程是:2456
我是子进程,我的PID是:12156, 我的父进程是:2456
主进程执行结束
'''

Process类中常用的属性和方法

方法/属性名称功能描述
name当前进程实例别名,默认为Process-N
pid当前进程对象的PID值
is_alive()进程是否执行完,没执行完结果为True,否则为False
join(timeout)等待结束或等待timeout秒
start()启动进程
run()如果没有指定target参数,则启动进程后,会调用父类中的run方法
terminate()强制终止进程
import os
import time
from multiprocessing import Process


def sub_process(name):
    print(f'子进程PID是:{os.getpid()}, 父进程PID是:{os.getppid()}, -----{name}')
    time.sleep(1)

def sub_process2(name):
    print(f'子进程PID是:{os.getpid()}, 父进程PID是:{os.getppid()}, -----{name}')
    time.sleep(1)

if __name__ == '__main__':
    print('父进程开始执行...')
    for i in range(5):
        p1 = Process(target=sub_process, args=('Mike',))
        p2 = Process(target=sub_process2, args=(18,))
        p1.start()
        p2.start()
        print(p1.name, '是否执行完毕:', p1.is_alive())
        print(p2.name, '是否执行完毕:', p2.is_alive())

        print(p1.name,'pid是: ', p1.pid)
        print(p2.name,'pid是: ', p2.pid)

        p1.join() # 阻塞主进程,等待p1进程结束
        p2.join()

        print(p1.name, '是否执行完毕:', p1.is_alive())
        print(p2.name, '是否执行完毕:', p2.is_alive())

    print('父进程执行完毕')

继承式创建子进程

import os
import time
from multiprocessing import Process

# 继承式创建进程
class SubProcess(Process):
    # 初始化
    def __init__(self, name):
        super().__init__()
        self.name = name

    # 重写父类中的run方法
    def run(self):
        print(f'子进程名称:{self.name}, PID:{os.getpid()}, 父进程的PID:{os.getppid()}')


if __name__ == '__main__':
    print('父进程开始执行')
    lst = []
    for i in range(1, 6):
        p1 = SubProcess(f'进程:{i}')
        # 启动进程
        p1.start()
        lst.append(p1)

    for item in lst:
        item.join()
    
    print('父进程执行结束')

'''
父进程开始执行
子进程名称:进程:1, PID:3020, 父进程的PID:15580
子进程名称:进程:2, PID:28284, 父进程的PID:15580
子进程名称:进程:3, PID:16548, 父进程的PID:15580
子进程名称:进程:4, PID:25132, 父进程的PID:15580
子进程名称:进程:5, PID:28692, 父进程的PID:15580
父进程执行结束
'''

Pool进程池

  • 进程池的原理是:创建一个进程池,并设置进程池中最大的进程数量。假设进程池中最大的进程数为3,现在有10个任务需要执行,那么进程池一次可以执行3个任务,4次即可完成全部任务的执行。
  • 创建进程池的语法结构:进程池对象=Pool(N)
方法名功能描述
apply_async(func,args,kwargs)使用非阻塞方式调用函数func
apply(func,args,kwargs)使用阻塞方式调用函数func
close()关闭进程池,不再接收新任务
terminate()不管任务是否完成,立即终止
join()阻塞主进程,必须在terminate()close()之后调用

非阻塞方式

import os
import time
from multiprocessing import Pool


def sub_process(name):
    print(f'子进程PID是:{os.getpid()}, -----{name}')
    time.sleep(1)

if __name__ == '__main__':
    # 主进程
    start = time.time()
    print('父进程开始执行')

    p = Pool(3)
    for i in range(10):
        # 以非阻塞方式
        p.apply_async(func=sub_process, args=(i,))

    p.close() # 关闭进程池,不再接受新任务
    p.join()
    print('所有子进程执行完毕, 父进程执行完毕')
    print('耗时:', time.time()-start)

'''
父进程开始执行
子进程PID是:6980, -----0
子进程PID是:9660, -----1
子进程PID是:26692, -----2
子进程PID是:6980, -----3
子进程PID是:9660, -----4
子进程PID是:26692, -----5
子进程PID是:6980, -----6
子进程PID是:9660, -----7
子进程PID是:26692, -----8
子进程PID是:6980, -----9
所有子进程执行完毕, 父进程执行完毕
耗时: 4.210505723953247
'''

阻塞方式

import os
import time
from multiprocessing import Pool


def sub_process(name):
    print(f'子进程PID是:{os.getpid()}, -----{name}')
    time.sleep(1)

if __name__ == '__main__':
    # 主进程
    start = time.time()
    print('父进程开始执行')

    p = Pool(3)
    for i in range(10):
        # 以非阻塞方式
        p.apply(func=sub_process, args=(i,))

    p.close() # 关闭进程池,不再接受新任务
    p.join()
    print('所有子进程执行完毕, 父进程执行完毕')
    print('耗时:', time.time()-start)
'''
父进程开始执行
子进程PID是:22824, -----0
子进程PID是:20980, -----1
子进程PID是:28148, -----2
子进程PID是:22824, -----3
子进程PID是:20980, -----4
子进程PID是:28148, -----5
子进程PID是:22824, -----6
子进程PID是:20980, -----7
子进程PID是:28148, -----8
子进程PID是:22824, -----9
所有子进程执行完毕, 父进程执行完毕
耗时: 10.160722494125366
'''

并发和并行

并发

  • 是指两个或多个事件同一时间间隔发生,多个任务被交替轮换着执行,比如A事件是吃苹果,在吃苹果的过程中有快递员敲门让你收下快递,收快递就是B事件,那么收完宽邸继续吃没吃完的苹果。这就是并发
  • 在这里插入图片描述

并行

  • 指两个或多个事件在同一时刻发生,多个任务在同一时刻在多个处理器上同时执行。比如A事件是泡脚,B事件是打电话,C事件是记录电话内容,这三件事则可以在同一时刻发生,这就是并行

  • 在这里插入图片描述

多个进程之间的通信

  • 多个进程之间的数据没有共享
import os
import time
from multiprocessing import Process

a = 100

def add():
    print('子进程1开始执行')
    global a
    a += 30
    print('a=', a)
    print('子进程1执行结束')

def sub():
    print('子进程2开始执行')
    global a
    a -= 50
    print('a=', a)
    print('子进程2执行结束')

if __name__ == '__main__':
    print('主进程开始执行')
    print('a = ', a)
    p1 = Process(target=add)
    p2 = Process(target=sub)
    p1.start()
    p2.start()

    p1.join()
    p2.join()
    print('a = ', a)
    print('主进程执行结束')

'''
主进程开始执行
a =  100
子进程1开始执行
a= 130
子进程1执行结束
子进程2开始执行
a= 50
子进程2执行结束
a =  100
主进程执行结束
'''
  • 进程之间可以通过队列(Queue)进行通信,队列是一种先进先出(FIFO)的数据结构
    • 创建队列的语法结构:队列对象 = Queue(N)
方法功能
qsize()获取当前队列包含的消息数量
empty()判断队列是否为空,为空结果为True,否则为False
full()判断队列是否满了,满了结果为True,否则为False
get(block=True)获取队列中的一条消息,然后从队列中移除,block默认值为True;消息队列为空时,不报错,但是会一直等待
get_nowait()相当于get(block=False),消息队列为空时,抛出异常
put(item,block=True)将item消息放入队列,block默认为True,消息队列为满时,不报错,但是会一直等待
put_nowait(item)相当于put(item,block=False),消息队列为满时,抛出异常
from multiprocessing import Queue

if __name__ == '__main__':
    # 创建一个队列
    q = Queue(3)
    print('队列是否为空:', q.empty()) # True
    print('队列是否为满:', q.full()) # False

    # 向队列中添加信息
    q.put('hello')
    q.put('world')
    print('队列是否为空:', q.empty()) # False
    print('队列是否为满:', q.full()) # False

    q.put('python')
    print('队列是否为空:', q.empty()) # False
    print('队列是否为满:', q.full()) # True

    print('队列中有多少个消息:', q.qsize())
    print(q.get())
    print('队列中有多少个消息:', q.qsize())
    q.put('Java')
    print('队列中有多少个消息:', q.qsize())

    if not q.empty():
        for i in range(q.qsize()):
            print(q.get_nowait())

    print('队列是否为空:', q.empty()) # True
    print('队列是否为满:', q.full()) # False
    print('队列中有多少个消息:', q.qsize())

'''
队列是否为空: True
队列是否为满: False
队列是否为空: False
队列是否为满: False
队列是否为空: False
队列是否为满: True
队列中有多少个消息: 3
hello
队列中有多少个消息: 2
队列中有多少个消息: 3
world
python
Java
队列是否为空: True
队列是否为满: False
队列中有多少个消息: 0
'''
  • 使用队列实现进程之间的通信
import time
from multiprocessing import Process, Queue

a = 100

# 入队
def write_msg(q):
    global a
    if not q.full():
        for i in range(6):
            a -= 10
            q.put(a)
            print('a入队时的值:', a)

def read_msg(q):
    time.sleep(1)
    while not q.empty():
        print('出队时a的值:', q.get())

if __name__ == '__main__':
    print('父进程开始执行')
    q = Queue() # 父进程创建队列,没有指定参数,可接收的消息的个数没有上限
	
    # 创建两个子进程
    p1 = Process(target=write_msg, args=(q,))
    p2 = Process(target=read_msg, args=(q,))
    p1.start()
    p2.start()

    p1.join()
    p2.join()

    print('父进程执行完成')

'''
父进程开始执行
a入队时的值: 90
a入队时的值: 80
a入队时的值: 70
a入队时的值: 60
a入队时的值: 50
a入队时的值: 40
出队时a的值: 90
出队时a的值: 80
出队时a的值: 70
出队时a的值: 60
出队时a的值: 50
出队时a的值: 40
父进程执行完成
'''

线程

  • 线程是CPU可调度的最小单位,被包含在进程中,是进程中实际的运作单位。
  • 一个进程中可以拥有N多个线程并发执行,而每个线程并行执行不同的任务。

创建线程的方式

函数式
  • 创建线程的语法结构:
    • t=Thread(group,target,name,args,kwargs)
  • 函数说明:
    • group:创建线程对象的进程组
    • target:创建的线程对象所要执行的目标函数
    • name:创建线程对象的名称,默认为“Thread-n"
    • args:用元组以位置参数的形式传入target对应函数的参数
    • kwargs:用字典以关键字参数的形式传入target对应函数的参数
import threading
from threading import Thread
import time

def test():
    for i in range(3):
        time.sleep(1)
        print(f'线程:{threading.current_thread().name}正在执行{i}')

if __name__ == '__main__':
    start = time.time()
    print('主进程开始执行')

    # 线程

    lst = [Thread(target=test) for i in range(2)]
    for item in lst:
        # 启动线程
        item.start()

    for item in lst:
        item.join()

    print(f'执行结束,一共耗时{time.time() - start}')
'''
主进程开始执行
线程:Thread-2 (test)正在执行0
线程:Thread-1 (test)正在执行0
线程:Thread-1 (test)正在执行1
线程:Thread-2 (test)正在执行1
线程:Thread-2 (test)正在执行2
线程:Thread-1 (test)正在执行2
执行结束,一共耗时3.003098249435425
'''
继承式
  • 使用Thread子类创建线程
  • 操作步骤:
    • 自定义类继承threading模块下的Thread类
    • 实现run方法
import threading
import time
from threading import Thread


class SubThread(Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            print(f'线程:{threading.current_thread().name}正在执行{i}')


if __name__ == '__main__':
    print('主进程开始执行')
    start = time.time()
    lst = [SubThread() for i in range(2)]
    for item in lst:
        item.start()

    for item in lst:
        item.join()
    
    print(f'执行结束,一共耗时{time.time() - start}')
'''
主进程开始执行
线程:Thread-2正在执行0
线程:Thread-1正在执行0
线程:Thread-2正在执行1
线程:Thread-1正在执行1
线程:Thread-2正在执行2
线程:Thread-1正在执行2
执行结束,一共耗时3.002912759780884
'''

线程之间数据共享

  • 线程之间的数据是共享的
from threading import Thread

a = 100

def add():
    print('加进程开始执行')
    global a
    a+=30
    print(f'a的值为:{a}')
    print('加进程执行结束')

def sub():
    print('减进程开始执行')
    global a
    a-=50
    print(f'a的值为:{a}')
    print('减进程执行结束')

if __name__ == '__main__':
    print('主进程开始执行')
    print(f'a = {a}')
    t1 = Thread(target=add)
    t2 = Thread(target=sub)

    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print('主进程执行结束')
'''
主进程开始执行
a = 100
加进程开始执行
a的值为:130
减进程开始执行
加进程执行结束
a的值为:80
减进程执行结束
主进程执行结束
'''

线程操作共享数据的安全性问题

import threading
import time
from threading import Thread

ticket = 50

def sale_ticket():
    global ticket
    for i in range(20):
        if ticket>0:
            print(f'线程:{threading.current_thread().name}正在出售第{ticket}张票')
            ticket -= 1
        time.sleep(1)

if __name__ == '__main__':
    for i in range(3):
        t = Thread(target=sale_ticket)
        t.start()

'''
线程:Thread-1 (sale_ticket)正在出售第50张票
线程:Thread-2 (sale_ticket)正在出售第49张票
线程:Thread-3 (sale_ticket)正在出售第48张票
线程:Thread-3 (sale_ticket)正在出售第47张票
线程:Thread-2 (sale_ticket)正在出售第47张票
线程:Thread-1 (sale_ticket)正在出售第47张票
线程:Thread-1 (sale_ticket)正在出售第44张票
线程:Thread-2 (sale_ticket)正在出售第44张票
线程:Thread-3 (sale_ticket)正在出售第44张票
'''
  • 出现多个线程同时出售同一张票
  • 问题原因:线程1判断完if ticket>0:之后,后续代码还未执行,其他线程就开始进行判断
Lock
  • 锁定状态:acquire()
  • 非锁定状态:release()
import threading
import time
from threading import Lock, Thread

ticket = 50
lock_obj = Lock()

def sale_ticket():
    global ticket
    for i in range(20):
        lock_obj.acquire() # 上锁
        if ticket>0:
            print(f'线程:{threading.current_thread().name}正在出售第{ticket}张票')
            ticket -= 1
        time.sleep(1)
        lock_obj.release() # 释放锁

if __name__ == '__main__':
    for i in range(3):
        t = Thread(target=sale_ticket)
        t.start()

'''
线程:Thread-1 (sale_ticket)正在出售第50张票
线程:Thread-2 (sale_ticket)正在出售第49张票
线程:Thread-2 (sale_ticket)正在出售第48张票
线程:Thread-1 (sale_ticket)正在出售第47张票
线程:Thread-1 (sale_ticket)正在出售第46张票
线程:Thread-1 (sale_ticket)正在出售第45张票
线程:Thread-1 (sale_ticket)正在出售第44张票
线程:Thread-1 (sale_ticket)正在出售第43张票
线程:Thread-3 (sale_ticket)正在出售第42张票
线程:Thread-3 (sale_ticket)正在出售第41张票
线程:Thread-3 (sale_ticket)正在出售第40张票
线程:Thread-3 (sale_ticket)正在出售第39张票
线程:Thread-3 (sale_ticket)正在出售第38张票
线程:Thread-3 (sale_ticket)正在出售第37张票
线程:Thread-3 (sale_ticket)正在出售第36张票
线程:Thread-3 (sale_ticket)正在出售第35张票
线程:Thread-3 (sale_ticket)正在出售第34张票
线程:Thread-3 (sale_ticket)正在出售第33张票
线程:Thread-1 (sale_ticket)正在出售第32张票
线程:Thread-1 (sale_ticket)正在出售第31张票
线程:Thread-1 (sale_ticket)正在出售第30张票
线程:Thread-2 (sale_ticket)正在出售第29张票
线程:Thread-2 (sale_ticket)正在出售第28张票
线程:Thread-1 (sale_ticket)正在出售第27张票
线程:Thread-3 (sale_ticket)正在出售第26张票
线程:Thread-2 (sale_ticket)正在出售第25张票
线程:Thread-2 (sale_ticket)正在出售第24张票
线程:Thread-2 (sale_ticket)正在出售第23张票
线程:Thread-1 (sale_ticket)正在出售第22张票
线程:Thread-1 (sale_ticket)正在出售第21张票
线程:Thread-1 (sale_ticket)正在出售第20张票
线程:Thread-1 (sale_ticket)正在出售第19张票
线程:Thread-1 (sale_ticket)正在出售第18张票
线程:Thread-3 (sale_ticket)正在出售第17张票
线程:Thread-3 (sale_ticket)正在出售第16张票
线程:Thread-3 (sale_ticket)正在出售第15张票
线程:Thread-3 (sale_ticket)正在出售第14张票
线程:Thread-3 (sale_ticket)正在出售第13张票
线程:Thread-2 (sale_ticket)正在出售第12张票
线程:Thread-2 (sale_ticket)正在出售第11张票
线程:Thread-2 (sale_ticket)正在出售第10张票
线程:Thread-2 (sale_ticket)正在出售第9张票
线程:Thread-2 (sale_ticket)正在出售第8张票
线程:Thread-1 (sale_ticket)正在出售第7张票
线程:Thread-1 (sale_ticket)正在出售第6张票
线程:Thread-1 (sale_ticket)正在出售第5张票
线程:Thread-1 (sale_ticket)正在出售第4张票
线程:Thread-1 (sale_ticket)正在出售第3张票
线程:Thread-3 (sale_ticket)正在出售第2张票
线程:Thread-3 (sale_ticket)正在出售第1张票
'''

生产者与消费者模式

  • 是线程模型中的经典问题,与编程语言无关。当程序中出现了明确的两类任务,一个任务负责生产数据,一个任务负责处理生产的数据时就可以使用该模式。
生产数据放入仓库
从仓库取出数据
生产者线程
中间仓库
消费者线程
  • Python内置模块queue中的Queue类
方法功能
put(item)向队列中放置数据,如果队列为满,则阻塞
get()从队列中取走数据,如果队列为空,则阻塞
join()如果队列不为空,则等待队列变为空
task_done()消费者从队列中取走一项数据,当队列变为空时,唤醒调用join()的线程
import time
from queue import Queue
from threading import Thread


# 创建一个生产者类
class Producer(Thread):
    def __init__(self, name, queue):
        Thread.__init__(self, name=name)
        self.queue = queue
    
    def run(self):
        for i in range(1, 6):
            print(f'{self.name}将产品{i}放入队列')
            self.queue.put(i)
            time.sleep(1)
        print('生产者完成了所有数据的存放')

# 创建一个消费者类
class Consumer(Thread):
    def __init__(self, name, queue):
        Thread.__init__(self, name=name)
        self.queue = queue
    
    def run(self):
        for _ in range(5):
            value = self.queue.get()
            print(f'{self.name}将产品{value}取出')
            time.sleep(1)
        print('消费完成了所有数据的取出')

if __name__ == '__main__':
    queue = Queue()

    p = Producer('Producer', queue)
    c = Consumer('Consumer', queue)
    p.start()
    c.start()

    p.join()
    c.join()

    print('主线程执行结束')
'''
Producer将产品1放入队列
Consumer将产品1取出
Producer将产品2放入队列
Consumer将产品2取出
Producer将产品3放入队列
Consumer将产品3取出
Producer将产品4放入队列
Consumer将产品4取出
Producer将产品5放入队列
Consumer将产品5取出
生产者完成了所有数据的存放
消费完成了所有数据的取出
主线程执行结束
'''
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值