python基础-并发编程02
并发编程
子进程回收的两种方式
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
python基础-并发编程02的更多相关文章
- python基础-并发编程part01
并发编程 操作系统发展史 穿孔卡片 读取数据速度特别慢,CPU利用率极低 单用户使用 批处理 读取数据速度特别慢,CPU利用率极低 联机使用 脱机批处理(现代操作系统的设计原理) 读取数据速度提高 C ...
- python并发编程02 /多进程、进程的创建、进程PID、join方法、进程对象属性、守护进程
python并发编程02 /多进程.进程的创建.进程PID.join方法.进程对象属性.守护进程 目录 python并发编程02 /多进程.进程的创建.进程PID.join方法.进程对象属性.守护进程 ...
- python基础-函数式编程
python基础-函数式编程 高阶函数:map , reduce ,filter,sorted 匿名函数: lambda 1.1函数式编程 面向过程编程:我们通过把大段代码拆成函数,通过一层一层 ...
- python基础——面向对象编程
python基础——面向对象编程 面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的 ...
- 并发编程 02—— ConcurrentHashMap
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- Python 3 并发编程多进程之进程同步(锁)
Python 3 并发编程多进程之进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理. 1. ...
- Python 3 并发编程多进程之守护进程
Python 3 并发编程多进程之守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemo ...
- Python 3 并发编程多进程之队列(推荐使用)
Python 3 并发编程多进程之队列(推荐使用) 进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的. 可以往 ...
- python 基础网络编程2
python 基础网络编程2 前一篇讲了socketserver.py中BaseServer类, 下面介绍下TCPServer和UDPServer class TCPServer(BaseServer ...
随机推荐
- Cesium专栏-热力图(附源码下载)
Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...
- Andorid Studio 新建模拟器无法联网问题
1.查看自己本机的dns cmd -> ipconfing /all 2.修改模拟器的dns 跟PC本机一致. 开启模拟器 -> cmd -> adb root (需要root ...
- Fundebug前端异常监控插件更新至2.0.0,全面支持TypeScript
摘要: 是时候支持TS了! Fundebug前端异常监控服务 Fundebug提供专业的前端异常监控服务,我们的插件可以提供全方位的异常监控,可以帮助开发者第一时间定位各种前端异常,包括但不限于Jav ...
- GitHub访问速度慢的一种优化方法
GitHub是一个面向开源及私有软件项目的托管平台,因为只支持Git 作为唯一的版本库格式进行托管,故名GitHub. 由于GitHub是一个国外网站,在国内访问速度如何呢? 我们通过浏览器访问下ht ...
- Linux—vi/vim命令详解
如何在 vi 里搜索关键字 在命令模式下敲斜杆( / )这时在状态栏(也就是屏幕左下脚)就出现了 "/" 然后输入你要查找的关键字敲回车就行了. 如果你要继续查找此关键字,敲字符 ...
- ElasticSearch7 设置外网访问失败
elasticsearch外网访问9200端口失败,bootstrap checks failed,the default discovery settings are unsuitable for ...
- Docker组成三要素
目录 镜像 容器 仓库 总结 Docker的基本组成三要素 镜像 容器 仓库 镜像 Docker 镜像(Image)就是一个只读的模板.镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器. ...
- 线性代数笔记24——微分方程和exp(At)
原文:https://mp.weixin.qq.com/s/COpYKxQDMhqJRuMK2raMKQ 微分方程指含有未知函数及其导数的关系式,解微分方程就是找出未知函数.未知函数是一元函数的,叫常 ...
- MATLAB实例:非线性曲线拟合
MATLAB实例:非线性曲线拟合 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 用最小二乘法拟合非线性曲线,给出两种方法:(1)指定非线性函数,(2) ...
- Java总结转载,持续更新。。。
1.Java中内存划分 https://www.cnblogs.com/yanglongbo/p/10981680.html