Day 29 进程互斥锁/队列/线程

进程互斥锁

就是将要执行任务的部门代码(只涉及到修改共享数据的代码)变成串行

第一步: 导入multiprocessing模块下的Lock类

第二步: 在if __name__ = __main__:方法下调用Lock类metux = Lock(),拿到一个对象

第三步: 在子类中需要共享的数据前后加锁:mutex.acquire(),mutex.release()

'''
抢票功能:
    1. 查看余票
    2. 开始抢票
'''

from multiprocessing import Process, Lock
import time
import json


def search(user):
    with open('data.txt', 'r', encoding='utf8') as f:
        dic = json.load(f)
    print(f'用户{user}查看余票, 还剩{dic.get("ticket_num")}')


def buy(user):
    with open('data.txt', 'r', encoding='utf8') as f:
        dic = json.load(f)

    time.sleep(1)

    if dic.get('ticket_num') > 0:
        dic['ticket_num'] -= 1
        with open('data.txt', 'w', encoding='utf8') as f:
            json.dump(dic, f)
        print(f'用户{user}抢票成功')
    else:
        print(f'用户{user}抢票失败')


def run(user, mutex):
    # 并发:异步执行
    search(user)

    mutex.acquire()
    buy(user)
    mutex.release()


if __name__ == '__main__':
    metux = Lock()

    for i in range(10):
        p = Process(target=run, args=(f'用户{i}', metux))
        p.start()

队列

概念介绍

创建共享的队列进程,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递

方法介绍

Queue([maxsize])创建共享的进程队列

参数:maxsize是队列中允许的最大项数.如果省略此参数,则无大小限制

底层队列使用管道和锁定实现

q.get([block [ ,timeout]])返回q中的一个项目.如果q为空,此方法将阻塞,直到队列中有项目可用位止.block用于控制阻塞行为,默认为True.如果设置为False,将引发Queue.Empty异常(定义在Queue模块中).timeout是可选超时时间,用在阻塞模式中.如果在制定的时间间隔没有有项目变为可用,将引发Queue.Empty异常

q.get_nowait():同q.get(False)方法

扫描二维码关注公众号,回复: 7543952 查看本文章

q.put(item [ ,block [,timeout]]):将item放入队列.如果队列已满,此方法将阻塞,知道空间可以为止.block控制阻塞行为,默认为True.如果设置为False,将引发Queue.Full异常(定义在Queue库模块中).timeout指定在阻塞模式中等待可用空间的时间长短.超时后将引发Queue.Full异常

q.empty():如果调用此方法时q为空,返回True.如果其他进程或线程正在往队列中添加项目,结果是不可靠的.也就是说,在返回和使用结果之间,队列中可能已经加入新的项目

q.full():返回队列中目前的正确数量

q.qsize():如果q已满,返回True

from multiprocessing import Queue


q = Queue(3)

q.put([1, 2, 3], block=True, timeout=5)
q.put(2, block=True, timeout=3)
q.put({'username': 'tiny'}, timeout=4)
# q.put(5, timeout=5)

print(q.qsize())

print(q.get())
print(q.get())
print(q.get())
# print(q.get(timeout=2))


print(q.qsize())

子进程发送数据给父进程

from multiprocessing import Queue
from multiprocessing import Process


def task1(q):
    data = '我是数据'
    q.put(data)
    print('进程1开始添加数据到队列中...')


def task2(q):
    data = q.get()
    print(f'进程2从队列中接收数据{data}')


if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=task1, args=(q,))
    p2 = Process(target=task2, args=(q,))

    p1.start()
    p2.start()
    
    print('主')

生产者消费者模型

在并发编程中使用生产者消费者模式能够解决绝大部分并发问题.该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度.

为什么要使用生产者和消费者模式?

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程.在啊多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据.同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者.为了解决这个问题于是引入了生产者消费者模式

什么是生产者消费者模式?

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题.生产和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

from multiprocessing import Queue
from multiprocessing import Process
import time


def producer(name, food, q):
    for i in range(9):
        data = (food, i)
        msg = f'{name}制作了{food, i}'
        print(msg)
        q.put(data)
        time.sleep(0.1)
    q.put(None)


def consumer(name, q):
    while True:
        data = q.get()
        if data is None:
            break
        print(f'{name}吃完了{data}')
        # time.sleep(0.1)


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

    p1 = Process(target=producer, args=('tiny', '包子', q))
    p2 = Process(target=producer, args=('xiaoxiao', '骨头', q))
    p3 = Process(target=consumer, args=('jane', q))
    p4 = Process(target=consumer, args=('nick', q))

    p1.start()
    p2.start()

    p3.daemon = True
    p4.daemon = True

    p3.start()
    p4.start()

    p2.join()
    print('主')

线程

进程是资源分配的最小单位

线程是CPU调度的最小单位

每个进程中至少有一个线程

进程和线程的区别

  1. 地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享,某进程内的线程在其他进程不可见
  2. 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信----需要进程同步互斥手段的辅助,以保证数据的一致性
  3. 调度和切换:线程上下文切换比进程上下文切换快的多
  4. 在多线程操作系统中,进程不是一个可执行的实体

线程的特点

  1. 线程中的实体基本上不拥有系统资源,只是有一点必不可少的,能保证独立运行的资源

  2. 线程的实体包括程序,数据和TCB.线程是动态概念,它的动态特性由线程控制块TCB描述,TCB用于指示被执行指令序列的程序计数器,保留局部变量,少量状态参数和返回地址等的一组寄存器和堆栈

    TCB包括以下信息:

    • 线程状态
    • 当线程不运行时,被保存的现场资源
    • 一组执行堆栈
    • 存放每个线程的局部变量主存区
    • 访问同一个进程中的主存和其他资源
  3. 共享进程资源

    线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味这,线程可以访问该进程的每一个内存资源;此外还可以访问进程所拥有的已打开文件,定时器,信号量机构等.由于同一个进程内的线程共享内存和文件,所有线程之间互相通信不必调用内核.

  4. 可并发执行

    在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行的工作能力.

开启线程的两种方式

from threading import Thread
import time


# 开启线程方式1:
def task():
    print('线程开始')
    time.sleep(1)
    print('线程结束')


if __name__ == '__main__':
    # 调用Thread线程类实例化得到线程对象
    t = Thread(target=task)
    t.start()
    

# 开启线程方式2:
class MyThread(Thread):
    def run(self):
        print('线程开启')
        time.sleep(1)
        print('线程结束')


t = MyThread()  # 如需要传入参数可以传参
t.start()

线程对象的属性

from threading import Thread
import time
from threading import current_thread

'''
current_thread().name  当前线程名字
isAlive()  判断线程是否存活
'''


def task():
    print('线程开启', current_thread().name)
    time.sleep(3)
    print('线程结束', current_thread().name)


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=task)
        t.start()
    print(t.isAlive())

线程互斥锁

from threading import Thread, Lock
import time


mutex = Lock()
n = 100


def task(i):
    print(f'线程{i}启动')
    global n
    mutex.acquire()
    temp = n
    time.sleep(0.1)  # 一共等待10秒
    n = temp - 1
    print(n)
    mutex.release()


if __name__ == '__main__':
    lis = []
    for i in range(100):
        t = Thread(target=task, args=(i,))
        lis.append(t)
        t.start()

    for i in lis:
        i.join()

    print(n)

猜你喜欢

转载自www.cnblogs.com/2222bai/p/11721663.html
今日推荐