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资源)直到第一个进程/线程解锁,第二个进程/线程则被唤醒并执行执行,同时锁定这个互斥量

互斥锁操作流程:

  1. 通过模块[multiprocessing]中的[Lock]类创建互斥锁lock对象
  2. 共享资源的临界区域前,对互斥量进行加锁
  3. 在访问前进行上锁,使用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

原文地址:https://www.cnblogs.com/xiaodan1040/p/12016381.html

时间: 2024-10-10 13:13:48

python基础-并发编程02的相关文章

python基础-并发编程part01

并发编程 操作系统发展史 穿孔卡片 读取数据速度特别慢,CPU利用率极低 单用户使用 批处理 读取数据速度特别慢,CPU利用率极低 联机使用 脱机批处理(现代操作系统的设计原理) 读取数据速度提高 CPU的利用率提高 多道技术(基于单核背景下产生的) 单道(串行):一个任务完完整整地运行完毕后,才能运行下一个任务 多道技术:允许多个程序同时进入内存并运行.同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬.软件资源.当一道程序因I/O请求而暂停运行时,CPU便立即转去运

Python基础并发编程——操作系统

一.操作系统简介 1.手工操作--穿孔卡片 1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作方式.此时还没有操作系统的概念. 程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存, 接着通过控制台开关启动程序针对数据运行:计算完毕,打印机输出计算结果:用户取走结果并卸下纸带(或卡片) 后,才让下一个用户上机. 手工操作方式两个特点: (1)用户独占全机.不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低. (

python并发编程02/多进程

目录 python并发编程02/多进程 1.进程创建的两种方式 1.1开启进程的第一种方式 1.2开启进程的第二种方式 1.3简单应用 2.进程pid 2.1命令行获取所有的进程的pid tasklist 2.2代码级别如何获取一个进程的pid 2.3获取父进程(主进程)的pid 3.验证进程之间的空间隔离 4.进程对象join方法 5.进程对象其他属性 6.守护进程 python并发编程02/多进程 1.进程创建的两种方式 1.1开启进程的第一种方式 from multiProcessing

Python 3 并发编程多进程之队列(推荐使用)

Python 3 并发编程多进程之队列(推荐使用) 进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的. 可以往队列里放任意类型的数据 创建队列的类(底层就是以管道和锁定的方式实现): 1 Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递. 参数介绍: 1 maxsize是队列中允许最大项数,省略则无大小限制. 方法介绍: 1.主要

Python 3 并发编程多进程之进程同步(锁)

Python 3 并发编程多进程之进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理. 1.多个进程共享同一打印终端 from multiprocessing import Process import os,time def work(): print('%s is running' %os.getpid()) time.sleep(2) print('%s is done' %os.g

python基础-函数式编程

python基础-函数式编程  高阶函数:map , reduce ,filter,sorted 匿名函数:  lambda  1.1函数式编程 面向过程编程:我们通过把大段代码拆成函数,通过一层一层的函数,可以把复杂的任务分解成简单的任务,这种一步一步的分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元. 函数式编程:是使用一系列函数去解决问题,函数式编程就是根据编程的范式来,得出想要的结果,只要是输入时确定的,输出就是确定的. 1.2高阶函数 能把函数作为参数传入,这样的

python中并发编程基础1

并发编程基础概念 1.进程. 什么是进程? 正在运行的程序就是进程.程序只是代码. 什么是多道? 多道技术: 1.空间上的复用(内存).将内存分为几个部分,每个部分放入一个程序,这样同一时间在内存中就有了多道程序. 2.时间上的复用(CPU的分配).只有一个CPU,如果程序在运行过程中遇到了I/O阻塞或者运行时间足够长.操作系统会按照算法将CPU分配给其他程序使用,依次类推.直到第一个程序被重新分配到CPU会继续运行. 多道技术中的问题解决: 空间复用:程序之间的内存必须分割.这种分割需要在硬件

python语法基础-并发编程-进程-长期维护

###############    进程的启动方式1    ############## """ 并发编程: 进程 1,运行中的程序,就是进程,程序是没有生命的实体,运行起来了就有生命了, 操作系统可以管理进程,进程是操作系统基本的执行单元, 2,每一个进程都有它自己的地址空间,进程之间是不会混的,比如qq不能访问微信的地址空间, 操作系统替你隔离开了,这也是操作系统引入进程这个概念的原因, ####################################### 进

Java并发编程(02):线程核心机制,基础概念扩展

本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效率.下面提供一个基础的演示案例. 2.应用案例 场景:假设有一个容器集合,需要拿出容器中的每个元素,进行加工处理,一般情况下直接遍历就好,如果数据偏大,可以根据线程数量对集合切割,每个线程处理一部分数据,这样处理时间就会减少很多. public class ExtendThread01 { publ