并发编程二

一、守护进程

  主进程创建守护进程,守护进程的主要的特征为:①守护进程会在主进程代码执行结束时立即终止;②守护进程内无法继续再开子进程,否则会抛出异常。

实例:

from multiprocessing import Process
import time
def foo():
    print(‘starting123‘)
    time.sleep(1)
    print(‘endig123‘)
def bar():
    print(‘starting456‘)
    time.sleep(3)
    print(‘ending456‘)
if __name__==‘__main__‘:
    p1=Process(target=foo)
    p2=Process(target=bar)
    p1.daemon=True                       #必须在start()之前开守护进程
    p1.start()                           #向操作系统发出开启p1子进程请求
    p2.start()                           #向操作系统发出开启p2子进程请求
    print(‘main‘)                        #主进程最后一行代码执行打印完后,立即终止p1子进程的执行如果想保留子进程只需要在p1.start()后面加上p1.join()

  注:打印最后一行主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息‘start123’,因为主进程打印main-时,p1也执行了,但是随即被终止。

二、锁 

  进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理(即局部实行串行)。

模拟抢票实例:

#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import json
import time
def search(n):
    dic=json.load(open(‘db‘))
    print(‘<%s> 剩余票数%s‘ %(n,dic[‘count‘]))
def get(n):
    dic=json.load(open(‘db‘))
    if dic[‘count‘]>0:
        dic[‘count‘]-=1
        time.sleep(1)           #模拟网络延迟
        json.dump(dic,open(‘db‘,‘w‘))
        print(‘<%s> 购票成功‘ %n)
    else:
        print(‘剩余票数为%s,购票失败‘ %dic[‘count‘])
def task(n,lock):
    search(n)                   #10个人都可以并发的查询剩余票数
    lock.acquire()              #获得锁
    get(n)                      #通过锁,查询到结果的10人通过竞争逐一买票。前一个释放锁后后一个才可以进入,即串行
    lock.release()              #释放锁
    #互斥锁的另一种形式
    # with lock:
    #     get(n)
if __name__==‘__main__‘:
    lock=Lock()
    for i in range(10):         #模拟10个抢票的人
        p=Process(target=task,args=(i,lock))
        p.start()
import time,random
from mulitiprocessing import Semaphore
def sing(i,sem):
    sem.acquire()  #需要锁
    time.sleep(random.randint(1,8))
    sem.release()   #释放锁
if __name__ == ‘__main__‘:
    sem = Semaphore(4)  #控制4个信号量
    for i in range(20)
        p = Processing(target = sing,args = (i,sem)) #创建进程 传参必须为元祖 一个参数后面加逗号
        p.start()

  注:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。缺点:①共享数据基于文件,而文件是硬盘上的数据,导致效率低;②需要自己加锁处理。

三、进程间通信机制(IPC)

  基于互斥锁以上两种缺点,multiprocessing模块为我们提供了基于消息通信IPC机制:队列和管道。队列和管道都是将数据存放于内存中;队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

1、队列(推荐)

(1)队列相关知识

  队列创建介绍:

from multiprocessing import Queue      #引入Queue类
q=Queue(n)                             #实例化,参数n代表队列中最大允许存放数,省略则无限制

  常见使用方法:

q.put()                                #用于插入数据到队列
q.get()                                #用于从队列读取并删除一个数据
q.put_nowait()                         #当队列存在数据已超过最大限制数,则抛出Queue.full异常
q.get_nowait()                         #当队列中已经不存在可取数据时,则抛出Queue.empty异常

  实例:

from multiprocessing import Queue
q=Queue(3)
q.put({‘a‘:1})
q.put(‘bbbb‘)
q.put((3,2,1))
# q.put_nowait(1111111)  #queue.Full

print(q.get())
print(q.get())
print(q.get())
# print(q.get_nowait())  #queue.Empty

(2)生产消费者模型

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

  实例1:

from multiprocessing import Queue,Process
import time,random
def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res=‘泔水%s‘ %i
        q.put(res)
        print(‘厨师 %s 生产了 %s‘ %(name,res))
def consumer(name,q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print(‘%s 吃了 %s‘ %(name,res))

if __name__ == ‘__main__‘:
    q=Queue()
    p1=Process(target=producer,args=(‘egon‘,q))
    c1=Process(target=consumer,args=(‘alex‘,q))

    p1.start()
    c1.start()

  此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。

  实例 2:

from multiprocessing import Queue,Process
import time,random
def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res=‘包子%s‘ %i
        q.put(res)
        print(‘厨师 %s 生产了 %s‘ %(name,res))
def consumer(name,q):
    while True:
        res=q.get()
        if res is None:break
        time.sleep(random.randint(1,3))
        print(‘%s 吃了 %s‘ %(name,res))

if __name__ == ‘__main__‘:
    q=Queue()
    p1=Process(target=producer,args=(‘egon‘,q))
    c1=Process(target=consumer,args=(‘alex‘,q))

    p1.start()
    c1.start()
    p1.join()
    q.put(None)

注意:以上发送可以放在生产函数中循环完进行发送,当然也可以如上放在主进程中进行发送,但是前提是必须等生产子进程结束才可以。

原文地址:https://www.cnblogs.com/Ebola-/p/8413130.html

时间: 2024-10-13 00:29:51

并发编程二的相关文章

【Java并发编程二】同步容器和并发容器

一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchornized. 另一个是Collections类中提供的静态工厂方法创建的同步包装类. 同步容器都是线程安全的.但是对于复合操作(迭代.缺少即加入.导航:根据一定的顺序寻找下一个元素),有时可能需要使用额外的客户端加锁进行保护.在一个同步容器中,复合操作是安全

Java 并发编程(二):如何保证共享变量的原子性?

线程安全性是我们在进行 Java 并发编程的时候必须要先考虑清楚的一个问题.这个类在单线程环境下是没有问题的,那么我们就能确保它在多线程并发的情况下表现出正确的行为吗? 我这个人,在没有副业之前,一心扑在工作上面,所以处理的蛮得心应手,心态也一直保持的不错:但有了副业之后,心态就变得像坐过山车一样.副业收入超过主业的时候,人特别亢奋,像打了鸡血一样:副业迟迟打不开局面的时候,人就变得惶惶不可终日. 仿佛我就只能是个单线程,副业和主业并行开启多线程模式的时候,我就变得特别没有安全感,尽管整体的收入

并发编程(二)

五.线程的概述 5.1 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程就是进程 车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线 流水线的工作需要电源,电源就相当于cpu 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上执行的单位. 多线程(即多个控制线程)的概念是,在一个进程中存在朵儿控制线程,多个控制线程共

漫谈并发编程(二):java线程的创建与基本控制

java线程的创建 定义任务 在java中使用任务这个名词来表示一个线程控制流的代码段,用Runnable接口来标记一个任务,该接口的run方法为线程执行的代码段. public class LiftOff implements Runnable { protected int countDown = 10; private static int taskCount = 0; private final int id = taskCount++; public void run() { whil

python并发编程(二):协程

'''协程: 1. 协程的定义: 1) 是一种用户态的轻量级线程, 即协程是由用户程序自己控制调度的 2) 是一种协作而非抢占式的处理并发方式, A --> B ---> A --> C 3) 协程的切换属于程序级别的, 操作系统不需要切换 2. 协程的特点: 1) 协程本身是一个线程, 是用户态的切换 2) 相比线程优点: 1> 切换没有消耗 2> 修改共享程序不需要加锁 3) 相比线程缺点: 一旦引入协程,就需要检测单线程下所有的IO行为, 实现遇到IO就切换,少一个都不

《Java并发编程实战》第三章 对象的共享 读书笔记

一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见性的描写叙述即多线程能够实时获取其它线程改动后的状态. *** 待补充   两个工人同一时候记录生产产品总数问题 1. 失效数据 可见性出现故障就是其它线程没有获取到改动后的状态,更直观的描写叙述就是其它线程获取到的数据是失效数据. 2. 非原子64位操作 3. 加锁与可见性 比如在一个变量的读取与

并发编程(二):全视角解析volatile

一.目录 1.引入话题-发散思考 2.volatile深度解析 3.解决volatile原子性问题 4.volatile应用场景 二.引入话题-发散思考 public class T1 { /*volatile*/ boolean running=true; public void m(){ System.out.println(Thread.currentThread().getName()+":start!"); while(running){ /*try { TimeUnit.M

[CSAPP笔记][第十二章并发编程]

第十二章 并发编程 如果逻辑控制流在时间上是重叠,那么它们就是并发的(concurrent).这种常见的现象称为并发(concurrency). 硬件异常处理程序,进程和Unix信号处理程序都是大家熟悉的例子. 我们主要将并发看做是一种操作系统内核用来运行多个应用程序的机制. 但是,并发不仅仅局限于内核.它也可以在应用程序中扮演重要的角色. 例如 Unix信号处理程序如何允许应用响应异步事件 例如:用户键入ctrl-c 程序访问虚拟存储器的一个未定义的区域 其他情况 访问慢速I/O设备 当一个应

第十二章 并发编程 学习笔记

第十二章 并发编程 进程是程序级并发,线程是函数级并发. 三种基本的构造并发程序的方法: 进程:每个逻辑控制流是个一个进程,由内核进行调度和维护. I/O多路复用:应用程序在一个进程的上下文中显式地调度他们自己的逻辑流. 线程:运行在单一进程上下文中的逻辑流,由内核进行调度. 12.1 基于进程的并发编程 构造并发程序最简单的方法就是用进程. 使用大家都很熟悉的函数例如: fork exec waitpid 关于在父.子进程间共享状态信息:共享文件表,但不共享用户地址空间. 进程又独立的地址空间