python基础-并发编程02(python并发编程 pdf)

网友投稿 248 2022-06-19


并发编程

子进程回收的两种方式

join()让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源

from multiprocessing import Process

import time

def task(name):

print(f'子进程{name}:starting……')

time.sleep(1)

print(f'子进程{name}:end……')

if __name__ == '__main__':

print('进入主进程……')

pro_list = []

for i in range(3):

pro_obj = Process(target=task, args=(i,))

pro_list.append(pro_obj)

pro_obj.start()

for pro in pro_list:

# 强制子进程结束后,主进程才可以结束,实现子进程资源回收

pro.join()

print('结束主进程……')

主进程正常结束,子进程与主进程一并被回收资源

了解知识

僵尸进程:子进程结束后,主进程没有正常结束,子进程PID不会被回收。

缺点:操作系统中的PID号是有限的,只用PID号也就是资源被占用,可能会导致无法创建新的进程

孤儿进程:子进程未结束,主进程没有正常结束,子进程PID不会被回收,会被操作系统优化机制回收。

操作系统优化机制:当主进程意外终止,操作系统会检测是否有正在运行的子进程,如果有,操作系统会将其放入优化机制中回收

守护进程

当主进程被结束时,子进程必须结束。守护进程必须在子进程开启之前设置

from multiprocessing import Process

import time

# 进程任务

def task():

print('starting……')

time.sleep(2)

print('ending……')

if __name__ == '__main__':

print('进入主进程……')

obj_list = []

for i in range(2):

# 创建进程

pro_obj = Process(target=task)

obj_list.append(pro_obj)

# 开启守护进程

pro_obj.daemon = True

# 守护进程必须在进程开启之前设置

pro_obj.start()

for obj in obj_list:

obj.join()

print('主进程结束……')

进程间数据是隔离的,代码论证

from multiprocessing import Process

count = 0

def func1():

global count

count += 10

print(f'func1:{count}')

def func2(count):

count += 100

print(f'func2:{count}')

if __name__ == '__main__':

# 创建子进程1

pro_obj1 = Process(target=func1)

# 创建子进程2

pro_obj2 = Process(target=func2, args=(count,))

# 子进程1开启

pro_obj1.start()

pro_obj1.join()

# 子进程2开启

pro_obj2.start()

pro_obj2.join()

print(f'主进程:{count}')

输出结果

func1:10

func2:100

主进程:0

线程

参考: https://blog.csdn.net/daaikuaichuan/article/details/82951084

一般会将进程和线程一起讲,做个区分

进程:操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是资源分配的最小单位

线程:有时被称为轻量级进程,是操作系统调度(CPU调度)执行的最小单位

进程和线程的区别

调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

并发性:进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行

拥有资源:进程是拥有资源的独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

系统开销:在创建或撤销进程时,系统都要为之分配和回收资源。而线程只是一个进程中的不同执行路径。一个进程挂掉就等于所有的线程挂掉。因此,多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些

进程和线程的联系

一个线程只能属于一个进程,而一个进程可以有多个线程,至少有一个线程

资源分配给进程,同一进程的所有线程共享该进程的所有资源

真正在处理机运行的是线程

不同进程的线程间要利用消息通信的办法实现同步

线程的实现

# 创建子线程的方式一

# 1、导入threading模块中的Thread类

from threading import Thread

import time

number = 1000

def task():

global number

number = 200

print('子线程开始……')

time.sleep(1)

print('子线程结束……')

if __name__ == '__main__':

# 2、创建一个子线程对象

thread_obj = Thread(target=task)

# 3、开启子线程

thread_obj.start()

# 4、设置子线程结束,主线程才能结束

thread_obj.join()

print('主进程(主线程)……')

print(number) # 输出结果:200

# 创建子线程的方式二

# 1、导入threading模块中的Thread类

from threading import Thread

import time

# 2、继承Thread类

class MyThread(Thread):

def run(self):

global number

number = 200

print('子线程开始……')

time.sleep(1)

print('子线程结束……')

if __name__ == '__main__':

# 创建一个子线程对象

t = MyThread()

# 开启子线程

t.start()

t.join()

print('主进程(主线程)……')

print(number) # 输出结果:200

守护子线程:设置子线程对象的demon属性为True,即

def task():

global number

number = 200

print('子线程开始……')

time.sleep(1)

print('子线程结束……')

if __name__ == '__main__':

# 2、创建一个子线程对象

thread_obj = Thread(target=task)

# 3、开启子线程

thread_obj.daemon = True

# 4、开启守护线程

thread_obj.start()

# 5、设置子线程结束,主线程才能结束

thread_obj.join()

print('主进程(主线程)……')

print(number)

队列

队列相当于一个第三方通道,可以存放数据,实现进程之间数据传递(也就是数据交互)。特点是先进先出

可通过三种方式实现

from multiprocessing import Queue

from multiprocessing import JoinableQueue # 推荐使用这种方式

import queue # python内置队列

队列存数据

put(obj):存数据,存放的数据个数超过队列设置的长度,进程进入阻塞状态

put_nowait(obj):存数据,当存放的数据个数超过队列设置的长度,报错

队列取数据

get():取数据,队列中的记录被取完后,继续取,进程进入阻塞状态

get_nowait():取数据,队列中的记录被取完后,继续取,报错

使用

from multiprocessing import JoinableQueue

# from multiprocessing import Queue

# import queue

from multiprocessing import Process

# 往队列中存储数据

def task_put(queue):

number_list = [10, 20, 30, 40]

for i in number_list:

# put() 存数据,存放的数据个数超过队列设置的长度,进程进入阻塞状态

queue.put(i)

print(f'存入记录:{i}')

# put_nowait() 存数据,当存放的数据个数超过队列设置的长度,报错

# queue.put_nowait(i)

# print(f'存入记录:{i}')

queue.put(1000)

print(f'存入记录:{1000}')

# put_nowait() 存数据,当存放的数据超过队列设置的长度,报错

# queue.put_nowait(1000)

# print(f'存入记录:{1000}')

# 从队列中取数据

def task_get(queue):

for i in range(5):

# get() 取数据,队列中的记录被取完后,继续取,进程进入阻塞状态

print(f'取出的第{i+1}个记录:{queue.get()}')

# get_nowait() 取数据,队列中的记录被取完后,继续取,报错

# print(f'取出的第{i+1}个记录:{queue.get_nowait()}')

if __name__ == '__main__':

# from multiprocessing import JoinableQueue 创建队列对象的方式

queue_obj = JoinableQueue(3) # 参数是int类型,表示队列中存放数据的个数

# from multiprocessing import Queue 创建队列对象的方式

# queue_obj = Queue(4)

# import queue 创建队列对象的方式

# queue_obj = queue.Queue(4)

# 进程1 存数据

pro_obj1 = Process(target=task_put, args=(queue_obj,))

pro_obj1.start()

pro_obj1.join()

# 进程2 取数据

pro_obj2 = Process(target=task_get, args=(queue_obj,))

pro_obj2.start()

pro_obj2.join()

复习:

通过列表和有序字典实现队列,先进先出

# 通过列表实现队列

# 定义一个空列表,当做队列

queue = []

# 向列表中插入元素

queue.insert(0, 1)

queue.insert(0, 2)

queue.insert(0, "hello")

print(queue)

for index in range(len(queue)):

print(f"第{index+1}个元素:", queue.pop())

# 通过有序字典实现队列方式一

from collections import OrderedDict

# 向有序字典中插入元素

ordered_dict = OrderedDict()

ordered_dict[1] = 1

ordered_dict[2] = 2

ordered_dict[3] = 'hello'

# 将先插入的元素移到最后

ordered_dict.move_to_end(2)

ordered_dict.move_to_end(1)

print(ordered_dict)

for index in range(3):

print(ordered_dict.pop(index + 1))

# 方式二

# 通过有序字典实现队列

from collections import OrderedDict

ordered_dict = OrderedDict()

ordered_dict['1'] = 1

ordered_dict['2'] = 2

ordered_dict['3'] = 'hello'

ordered_dict.move_to_end('2')

ordered_dict.move_to_end('1')

print(ordered_dict)

ordered_dict.move_to_end('1')

ordered_dict.move_to_end('2')

ordered_dict.move_to_end('3')

index = 1

for key in ordered_dict:

print(f'第{index}个元素:{key}')

index += 1

IPC机制

IPC(Inner-Process Communication,进程间通信)

进程间的通信可通过队列实现,详情参见队列的示例

互斥锁

互斥:散布在不同任务之间的若干程序片段,当某个任务运行其中一个程序片段时,其它任务就不能运行他们之中的任一程序片段,只能等到该任务运行完这个程序片段才可以运行。最基本场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源

互斥锁:一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁[lock对象.acquire()]和解锁[lock对象.release()]

作用:让并发变成了串行,牺牲了执行效率,保证了数据安全

特点:原子性、唯一性、非繁忙等待

原子性:如果一个进程/线程锁定了一个互斥量,没有其他进程/线程在同一时间可以成功锁定这个互斥量

唯一性:如果一个进程/线程锁定了一个互斥量,在它解锁之前,没有其他进程/线程可以锁定这个互斥量

非繁忙等待:如果一个进程/线程已锁定了一个互斥量,第二个进程/线程又试图去锁定这个互斥量,则第二个进程/线程将被挂起(不占用任何cpu资源),直到第一个进程/线程解锁,第二个进程/线程则被唤醒并执行执行,同时锁定这个互斥量

互斥锁操作流程:

通过模块[multiprocessing]中的[Lock]类创建互斥锁lock对象

对共享资源的临界区域前,对互斥量进行加锁

在访问前进行上锁,使用lock对象.acquire(),在访问完成后解锁,使用lock对象.release()

进程互斥锁: 购票小例子

# data.json 文件中的内容:{"number": 1}

# 购票小例子

from multiprocessing import Process # 进程

from multiprocessing import Lock # 进程互斥锁

import datetime

import json

import random

import time

# 查看余票

def check_ticket(name):

with open('data.json', 'r', encoding='utf-8') as f:

ticket_dic = json.load(f)

print(f'[{datetime.datetime.now()}]用户{name}查看余票,'

f'当前余票:{ticket_dic.get("number")}')

# 购票

def buy_ticket(name):

# 获取当前票的数量

with open('data.json', 'r', encoding='utf-8') as f:

ticket_dic = json.load(f)

number = ticket_dic.get('number')

if number:

number -= 1

# 模拟购票的网络延迟

time.sleep(random.random())

ticket_dic['number'] = number

# 购票成功

with open('data.json', 'w', encoding='utf-8') as f:

json.dump(ticket_dic, f)

print(f'[{datetime.datetime.now()}]{name}成功抢票!')

else:

# 购票失败

print(f'[{datetime.datetime.now()}]{name}抢票失败!')

def main(name, lock):

# 查看余票

check_ticket(name)

# 对购票这个过程使用互斥锁

# 上锁

lock.acquire()

buy_ticket(name)

# 解锁

lock.release()

if __name__ == '__main__':

pro_list = []

# 创建互斥锁对象

lock = Lock()

# 创建10个进程

for i in range(9):

pro_obj = Process(target=main, args=(f'pro_obj{i+1}', lock))

pro_obj.start()

for pro in pro_list:

pro.join()

线程互斥锁示例

"""

开启10个线程,对一个数据进行修改

"""

from threading import Lock

from threading import Thread

import time

# 创建线程互斥锁对象

lock = Lock()

# 要修改的记录

number = 100

# 线程任务

def task():

global number

# 上锁

# lock.acquire()

# 修改值

number2 = number

time.sleep(1)

number = number2 - 1

# 解锁

# lock.release()

if __name__ == '__main__':

# 创建10个线程

list1 = []

for line in range(10):

t = Thread(target=task)

t.start()

list1.append(t)

# 限制子线程结束后,主线程才能结束

for t in list1:

t.join()

print(number) # 加互斥锁,输出:90;不加互斥锁,输出:99


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python学习笔记—自动化部署【新手必学】(python自动化部署平台)
下一篇:【Python3网络爬虫开发实战】 分析Ajax爬取今日头条街拍美图(python3网络爬虫开发实战pdf下载)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~