21 并发编程01

目录:

 

 

 

 

基础概念:

一、进程、程序和线程

程序:程序只是一堆代码而已

进程:指的是程序的运行过程,是对正在运行的程序的一个抽象。进程是一个资源单位

线程:每个进程都有一个地址空间,而且默认就有一个控制线程。线程才是cpu上的执行单位。

二、并行与并发

无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务

并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)在一个时间段上可以看出是同时执行的

并行:同时运行,只有具备多个cpu才能实现并行。就是在一个精确的时间片刻

三、同步与异步

同步:发出一个功能调用,在没有得到结果之前,该调用就不会返回。

异步:异步功能调用发出后,不会等结果,而是继续往下执行,当该异步功能完成后,通过状态、通知或回调函数来通知调用者。

四、阻塞和非阻塞

阻塞:调用结果返回之前,当前进程会被挂起(如遇到IO操作)。函数只有在得到结果之后才会将阻塞的线程激活。

非阻塞:在不能立即得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。(就绪态,运行态)

异步非阻塞效率更高

 

 

多进程

一、进程的3种状态

   运行、就绪、阻塞

二、multiprocessing模块介绍

  • python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。
  • multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
  • 与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

三、Process类

1、方法介绍

  • p.start():启动进程,并调用该子进程中的p.run()  wins中,开启进程必须写在__main__里面
  • p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  通知操作系统去开启进程
  • p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
  • p.is_alive():如果p仍然运行,返回True
  • p.join():让主进程等待子进程代码运行结束之后再继续运行,不影响其他子进程的执行

    2、属性介绍

    • p.daemon:守护进程  默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
    • p.name:进程的名称
    • p.pid:进程的pid
    • 3、开启子进程的两种方式

      注意:在windows中Process()必须放到# if __name__ == '__main__':下

      在windows中使用process模块的注意事项

    #方式一:
    from multiprocessing import Process
    import time,os
     
    def task(name):
        print('%s %s is running,parent id is <%s>' %(name,os.getpid(),os.getppid()))
        time.sleep(3)
        print('%s %s is running,parent id is <%s>' % (name, os.getpid(), os.getppid()))
     
    if __name__ == '__main__':
        # Process(target=task,kwargs={'name':'子进程1'})
        p=Process(target=task,args=('子进程1',))
        p.start() #仅仅只是给操作系统发送了一个信号
     
        print('主进程', os.getpid(), os.getppid())

    四、守护进程

 

  • 守护进程会在主进程代码执行结束后就终止
    • 守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
    • 注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
    • 一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
from multiprocessing import Process
 
import time
def foo():
    print(123)
    time.sleep(1)
    print("end123")
 
def bar():
    print(456)
    time.sleep(3)
    print("end456")
 
if __name__ == '__main__':
    p1=Process(target=foo)
    p2=Process(target=bar)
 
    p1.daemon=True
    p1.start()
    p2.start()
    # p2.join()
    print("main-------")
 
"""
#主进程代码运行完毕,守护进程就会结束
main-------
456
end456
"""

 五、进程之间的数据隔离问题

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

 

from multiprocessing import Process

def work():
    global n
    n=0
    print('子进程内: ',n)


if __name__ == '__main__':
    n = 100
    p=Process(target=work)
    p.start()
    print('主进程内: ', n)
    p.join()
    print('主进程内: ',n)

"""
主进程内:  100
子进程内:  0
主进程内:  100
"""

进程之间的数据隔离问题

 

from multiprocessing import Process, Lock
import os

import time


def task(i, lock):
    # 开始上锁
    lock.acquire()
    print("第%s个: 进程id号:%s开始进来了" % (i, os.getpid()))
    time.sleep(2)
    print("第%s个: 进程id号:%s开始走了" % (i, os.getpid()))
    # 释放锁
    lock.release()


if __name__ == '__main__':
    lock = Lock()  # 5个人用同一把锁
    for i in range(5):
        p = Process(target=task, args=(i, lock))
        p.start()

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。

虽然可以用文件共享数据实现进程间通信,但问题是:

1.效率低(共享数据基于文件,而文件是硬盘上的数据)

2.需要自己加锁处理

 

#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。这两种方式都是使用消息传递的。

1 队列和管道都是将数据存放于内存中

2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,

我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

六、队列(推荐使用)

1、Queue

  • Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
  • maxsize是队列中允许最大项数,省略则无大小限制。
  • q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
  • q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
  • q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
  • q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
  • q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
  • """
    解决进程间数据隔离问题
    1. 进程之间数据是互不影响的 """ from multiprocessing import Process, Queue n = 10 def task(queue): time.sleep(2) queue.put('ly') # print('ly') import time if __name__ == '__main__': # task() q = Queue(3) p = Process(target=task, args=(q,)) p.start() print(q.get())

     

 生产者消费者模型

 版本6:消费者多,生产者少
def producer(queue, food):
    for i in range(10):
        data = '%s:生产了第%s个%s' % (os.getpid(), i, food)
        # print(data)
        queue.put('第%s个%s' % (i, food))


def consumer(queue, name):
    while True:
        try:
            data = queue.get(timeout=3)
            if data is None: break
            time.sleep(0.2)

            print("消费者:%s,消费了:%s" % (name, data))
        except Exception as e:
            print(e)
            break

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer, args=(q, '馒头'))
    p2 = Process(target=producer, args=(q, '花卷'))
    p3 = Process(target=producer, args=(q, '烧麦'))
    p1.start()
    p2.start()
    p3.start()

    p4 = Process(target=consumer, args=(q, 'egon'))
    p5 = Process(target=consumer, args=(q, 'ly'))
    p6 = Process(target=consumer, args=(q, 'tom'))
    p7 = Process(target=consumer, args=(q, 'qq'))
    p4.start()
    p5.start()
    p6.start()
    p7.start()

    # put(None) 放在这里不行,原因是先执行了主进程,放进了None,消费者先拿到None,程序直接结束了
    p1.join()
    p2.join()
    p3.join()

    q.put(None)
    q.put(None)
    q.put(None)
    q.put(None)

    print("end===========>")

 

posted @ 2021-08-31 18:29  甜甜de微笑  阅读(30)  评论(0编辑  收藏  举报