并发编程

目录:

  一、进程前戏

  二、进程介绍

  三、进程补充知识

  四、线程

  五、协程

  

   

一、进程前戏

  1、 操作系统

  核心CPU的调度 操作系统负责控制  调度 协调 管理 硬件(计算机硬件)资源 和软件资源(应用程序)

  负责数据运算和逻辑运算

  2、多道技术:a>>>>>IO>>>>

         利用a 程序在执行IO时加载b 程序到内存 

         核心

         1、空间上的复用 多个程序共用同一套计算机系统硬件

         2、时间上的复用  在执行一个程序的IO时 加载另一个成序到内存空间 节省时间  特点 会保存上一次的IO的状态 回到就绪态》》》》排队再进入运行态》》》阻塞态

         eg:比如我们在洗衣服的时间30 分钟内可以去做其他事 做饭 烧水  互不影响

         (1)切换+保存切换时状态

          什么时候才会切换

          >>>1 当一个程序的执行过程遇到IO操作时 操作系统会剥夺程序的CPU的执行权限(切换+保存运行当前的状态)

            >>>2 当一个程序长时间占用CPU 操作系统也会剥夺改程序的CPU的执行权限

            优点:

            提高了CPU的利用率,并且也不会影响程序的执行 

     单道技术:等待A程序全部执行完毕才会执行 下一个程序  实际上是 一个 同步

  

  3、进程三态状态装换图

1 、程序在运行之后并不是直接进入运行态而是 在就绪态等待操作系统的调度 开始执行

2、阻塞态遇到IO操作 会进入阻塞态 如 input time.sleep print()  阻塞态结束 input  time.sleep print 运行结束 并不是直接回到运行态的 而是 进入就绪态等待 

  操作系统的重新调用 开始执行 到之前切换的状态 

3.运行结束释放 退出

  4、进程调度(算法)

    1、FCFS  先进先出

    2、短作业优先调度算法

    3、时间片轮法

二、进程

  1、进程和程序

    程序:一堆代码块 

    进程:正在运行的程序

  2、如何创建进程

  方法一

# 方法一函数的方法
import time
from multiprocessing import Process


def run(name):
    print('%s dancing' % name)
    time.sleep(2)
    print('%s dancing over' % name)


if __name__ == '__main__':
    p = Process(target=run, args=('Lufei',))  # 产生一个进程对象

    p.start()  # 告诉操作系统帮你创一个进程
    print('主进程')
"""
# windows创建进程会将代码以模块的方式 从上往下执行一遍
# linux会直接将代码完完整整的拷贝一份
# 
创建进程就是内存中开辟一块内存空间
将允许产生的代码丢进去
一个进程对应在内存就是一块独立的内存空间

进程与进程间数是相互隔离的 无法直接的进行交互 
但是可以通过某些技术实现简介交互

"""

  方法二

  类继承

# 类继承
import time
from multiprocessing import Process


class Myprocess(Process):
    def __init__(self,name):
        super().__init__()  # 继承父类进程名称空间
        self.name = name

    def run(self):
        print('%s is dancing' % self.name)
        time.sleep(2)
        print('%s is over' % self.name)


if __name__ == '__main__':
    p = Myprocess('jason')  # 类继承的方法 继承Process 的属性 调用super().__init__() 重写自己的名称空间
    p.start()  # 告诉操作系统帮你创建一个进程
    print('主进程')

  3、进程的join 的方法

  # 控制子所有子进程的结束才会结束主进程。。。如果不加的话主进程一结束所有的子进程也会随之结束,不合理啊有些子进程都没有运行

  # 如何使用 

# 创建多个进程 所有的代码都会复制一份 
# join 的先让 所有自尅执行完毕才会执行 主进程
from multiprocessing import Process
import time

def run(name,i):
    print("%s is dancing" % name)
    time.sleep(i)
    print('%s is over' % name)

if __name__ == '__main__':
    p_list = []
    for i in range(5):
        p = Process(target=run, args=('第一号%s子进程 '%i,i))
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()  # 等待子进程运行完毕 内部机制 
    print('主进程')

原始版本 不加for 循环

# 创建多个进程
# join 的先让 所有自尅执行完毕才会执行 主进程
from multiprocessing import Process
import time

def run(name,i):
    print("%s is dancing" % name)
    time.sleep(i)
    print('%s is over' % name)


if __name__ == '__main__':
    # p_list = []
    # for i in range(5):
    #     p = Process(target=run, args=('第一号%s子进程 '%i,i))
    #     p.start()  # 让操作系统帮我们创建进程
    #     p_list.append(p)
    # for p in p_list:

    #     p.join()  # 等待子进程运行完毕 内部机制
    # print('主进程')
    # 原始状态
    p = Process(target=run, args=('jsaon',1))
    p1 = Process(target=run, args=('tank',2))
    p2 = Process(target=run, args=('egon',3))
    p3 = Process(target=run, args=('mmm',4))
    p.start()
    p1.start()
    p2.start()
    p3.start()
    p.join()
    p1.join()
    p2.join()
    p3.join()
    print('主进程')

  4、进程间的数据是否可以通用?代码验证

# 进程间的数据是不能直接获取的
from multiprocessing import Process
num = 99
# 可变数据类型是不需要进行全局的 

def run():
    global num  # global 是将局部修改全局
    num = 100
    # print(num)  #


if __name__ == '__main__':
    p = Process(target=run, args=())  # 没有参数的话是可以不写
    p.start()  # 让操作系统帮我们创建一个进程
    p.join()
    print(num)  # 99

  5、进程对象及其它方法 

  子进程的pid号和父进程的pid 好的 查询方法 

  僵尸进程和孤儿进程 

  6、守护进程

  7、互斥锁(*****)

  # 开多进程会将

import json
import time
from multiprocessing import Process, Lock

# 进程互斥锁
""";操作同一份数据会造成数据的错乱 所以需要加锁

1.代码由并行变成了串行虽然牺牲了效率 降低的这执行效率到但是保证了数据的安全性

"""


#
def check(i):
    with open('data', 'r', encoding='utf-8')as f:
        # 获取数据
        res = f.read()

    my_dic = json.loads(res)
    print('%s用户查询的火车票为%s' % (i, my_dic.get('ticket')))


# 买之前还得再获取数据的
def buy(i):
    #
    with open('data', 'r', encoding='utf-8')as f:
        res = f.read()
    my_dic = json.loads(res)

    time.sleep(2)
    if my_dic['ticket'] > 0:
        my_dic['ticket'] -= 1

        with open('data', 'w', encoding='utf-8')as f:

            json.dump(my_dic, f)
        print('%s用户买票成功' % i)
    else:
        print('没有票了')


def run(i, mutex):
    check(i)
    # 枪锁
    mutex.acquire()  # 枪锁
    buy(i)
    # 释放锁
    mutex.release()  # 释放锁


if __name__ == '__main__':
    # 创建进程
    # 将锁放在主进程中
    mutex = Lock()  # 生成一把锁
    for i in range(10):
        p = Process(target=run, args=((i, mutex)))
        p.start()
7用户查询的火车票为2
5用户查询的火车票为2
0用户查询的火车票为2
1用户查询的火车票为2
4用户查询的火车票为2
6用户查询的火车票为2
3用户查询的火车票为2
2用户查询的火车票为2
8用户查询的火车票为2
9用户查询的火车票为2
7用户买票成功
5用户买票成功
没有票了
没有票了
没有票了
没有票了
没有票了
没有票了
没有票了
没有票了

 作业:FTP上传文件

需求:

    1 用户认证加密

  2 允许同时多个用户登录

  3 每个用户都有自己的家目录(home),并且只能访问自己的home 目录

允许访问自己的家目录

  4 允许用户在FTPserver 上随意切换目录

  5 允许传输过程中显示进度条

  6 附加功能 支持文件的端点的端点续传

 三、进程补充

  1、进程间的相互通信   间接 通过Quueue 实现 

  队列可以实现通讯的本质 利用Qqueue 在一个进程中存 q.put() 的所有变量 存放到队列中的名称空间中 

  可以在另一个进程中通过q.get() 获取 另一个进程的值 就相当于一个链接 进程之间的通道

  也可以比作一座桥梁

  2.

Pipe(管道)

The Pipe() function returns a pair of connection objects connected by a pipe which by default is duplex (two-way).

  

# 简单版本的进程间相互通信是类简介通信的
import os
import time
from multiprocessing import Process, Queue

# q = Queue(5)
# q.put(1)
# q.put(2)
# q.put(3)
# # print(q.full())
# q.put(4)
#
# q.put(5)
# # print(q.full())
#
#
# # 取值
# q.get()
# q.get()
# q.get()
# q.get()
# print(q.get_nowait())
# print(q.empty())
# # print(q.get_nowait())
#
#
# print(q.get())  # q.get() 如果没有取到值则会一致等
#
# print(q.empty())
#
# """
#
# """

  2、进程间通过Queue进行通信的代码

实现过程:

money = 6666


def task(q):
    global money
    money = 9999
    q.put('Hello i love')


def consumer(q):
    print(q.get(),'haha')


if __name__ == '__main__':
    q1 = Queue()
    p = Process(target=task, args=(q1,))
    c = Process(target=consumer,args=(q1,))
    c.start()
    p.start()
    p.join()  # 等待所有的子进程结束才结束
    # print(money)
    print(q1.get(),'luelie')  #

 2.PIpe进程实现通信

from multiprocessing import Process, Pipe

def f(conn):
    conn.send('11')
    conn.send('22')
    print("from parent:",conn.recv())
    print("from parent:", conn.recv())
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()   #生成管道实例,可以互相send()和recv()

    p = Process(target=f, args=(child_conn,))
    p.start()

    print(parent_conn.recv())      # prints "11"
    print(parent_conn.recv())      # prints "22"
    parent_conn.send("33")         # parent 发消息给 child
    parent_conn.send("44")
    p.join()

Pipe

  3.Manage

进程之间是相互独立的 ,Queue和pipe只是实现了数据交互,并没实现数据共享,Manager可以实现进程间数据共享 。

Manager还支持进程中的很多操作 , 比如Condition , Lock , Namespace , Queue , RLock , Semaphore等

from multiprocessing import Process, Manager
import os

def f(d, l):
    d[os.getpid()] =os.getpid()
    l.append(os.getpid())
    print(l)

if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()  #{} #生成一个字典,可在多个进程间共享和传递

        l = manager.list(range(5))     #生成一个列表,可在多个进程间共享和传递
        p_list = []
        for i in range(2):
            p = Process(target=f, args=(d, l))
            p.start()
            p_list.append(p)
        for res in p_list: #等待结果
            res.join()
        print(d)
        print(l)

  4、进程中的IPC机制

  生产者和消费者模型

  # 

"""
生产者消费者模型
1.生产者:生产制造数据的
2、消费者:消费处理数据的
应用场景:解决生产与消费的供需不平衡的问题
供==求
列子:
···老王 开了一家面馆10碗面 老二 开了包子10个包子铺>>>>> 生产者
···小明 小红 吃面和包子

"""
def producer(name, food, q):
    for i in range(10):
        data = '%s生产的%s 个数:%d' % (name, food,i)
        # 生产需要时间的所以
        # time.sleep(random.random())
        time.sleep(random.random())
        q.put(data)
        print(data)


def consumer(name, q):
    # 消费者
    while True:
        data = q.get()
        # print(data)
        # if data == None:  # q.task_done() 其实已经帮我们处理完毕所有的数值
        #     break
        print("%s吃了%s"%(name,data))
        time.sleep(random.random())
        q.task_done()  # 告诉队列你已经从你的队列中取出一个值并且已经处理完毕


if __name__ == '__main__':
    # 创建进程可等待队列
    q = JoinableQueue()
    p1 = Process(target=producer, args=('大厨San', '包子', q))
    p2 = Process(target=producer, args=('二厨tank', '生蚝', q))
    p1.start()
    p2.start()

    c1 = Process(target=consumer, args=('吃货lufei', q))
    c2 = Process(target=consumer, args=('吃货Nami', q))
    # 守护进程 c1 c2 等待 运行完毕 运行主进程结束程序
    c1.daemon = True
    c2.daemon = True

    c1.start()  # 让操作系统帮我们创建一个消费者进程
    c2.start()
    # 让是生产进程全部执行完毕 才走 消费 进程
    p1.join()
    p2.join()
    # print('zhu')
    q.join()  # 等待队列中的所有值取完

  5.进程池

  pass

四、线程和进程的关系

#

"""
进程:进程是资源单位,每个进程下面自带了一个线程
线程:是正真运行代码的执行单位
并发:单核下是一个进程下的多个线程进行IO切换


eg: 进程好比一个工厂 线程是里面的一条条流水线

"""
1 主进程,父进程和子进程之间的关系
父子进程之间的定义:当一个进程创建一个或多个子进程时,那么这个进程可以称之这些进程的父进程, 他们之间是父子关系,也可以说是继承关系,子进程会继承父进程的属性。
进程是一个资源单位,在进程创建的过程,系统会自动为其开辟一块独立的内存空间。因此,在子进程的创建的过程中,系统会自动为其开辟一块独立的内存空间 ,并且会将父进程的代码拷贝到这个内存空间中。
一般来说,主进程默认是最初始的父进程。
所以在Python中创建子进程的过程中为了避免无限递归主进程的问题,必须将创建子进程的代码放在if name == ‘__main__’下面
2 主线程,父线程和子线程之间的关系
父线程和子线程之间的关系类似与主进程和子进程之间的关系,但是在线程的创建过程中并不需要开辟内存空间吗,而且线程与线程之间是没有主次之分的,他们共享同一块内存资源。每一个进程都自带一个线程,
这个线程称之为主线程。一般来说,主线程不会是父线程,他与子线程是同等地位的,这一点与进程有很大的不同。主线程创建的子线程只会去执行主线程交给他的任务,不会去执行子线程的创建任务,所以在线程的创建的过程不需要加if判断。
注意:如果子线程执行的是函数任务,函数的加载并不会在子线程中创建,他只是去执行这个函数体代码,而不会去执行创建过程,创建过程在主线程中完成。

2、创建线程的两种方法

·  1、函数的方法

# def producer(name):
#     print('%s is runing'% name)
#     time.sleep(1)
#     print('%s is over'% name)
#
#
# def consumer(name):
#     print('%s is runing '% name)
#     time.sleep(1)
#     print('% is over'% name)
#
#
# # 线程可以不用在双下__main__
# t1 = Thread(target=producer, args=('san',))
#
# t2 = Thread(target=producer, args=('Nami',))
# t1.start()  # 创建线程的资源开支小于 进程 时间消耗也小于进程 不需要开辟内存空间
# t2.start()  # 所以执行创建线程
# print('主线程')

  2、类继承创建线程

# 类继承
class MyThread(Thread):
    def __init__(self, name):  #
        super().__init__()
        self.name = name

    def task(self):
        print('%s is running' % self.name)
        time.sleep(1)
        print('%s is over' % self.name)


# if __name__ == '__main__':
t1 = MyThread('San')
t1.task()
t1.start()

t1.join()
print('主线程', os.getpid())


# #

# 创建多个线程


def task1(i):
    print('%s is running' % i)
    time.sleep(1)
    print('%s is over' % i)
    # print('主线程1', os.getppid())  # 主线程1 32092
    # print('子线程', os.getpid())  # 子线程 38928


for i in range(6):
    t = Thread(target=task1, args=(i,))
    t.daemon = True
    t.start()
    # print('主线程2', os.getpid())  # 主线程2 38928
    # print('主主',os.getppid())  # 主主 32092

#

小结: 

"""
主线程的结束也就意味进程的结束
主线程必须等待其他非守护线程的结束才能结束
(意味子线程云运行的时候需要使用进程中的资源,而主线程一旦结束资源也就销毁了)

"""

3、线程中的是可以进行相互通信的 因为同一进程下的线程是可以共用进程的所有资源单位的

from threading import Thread

money = 6666


def task():
    global money
    money = 9999


t = Thread(target=task)


t.start()
t.join()
print(money)

4、线程中的互斥锁 当多个线程对同一份数据进行操作的时候 会造成数据的错乱 所以需要进行加锁 虽然牺牲了效率 但保证数据的安全 

# 多个线程对同一份数据的操作 会造成数据的错乱
# 需要加锁 保障数据的安全
import time
from threading import Thread,Lock
num = 100
def task(mutex):

    # print('%s is running' % i)
    # time.sleep(2)
    # print('%s is over '% i)
    global num
    mutex.acquire()  # 加到要操作的数据上
    temp = num
    time.sleep(0.01)
    num = temp-1
    mutex.release()  # 操作完毕进行释放锁
t_list
= [] mutex = Lock() # 加锁的位置 for i in range(100): t = Thread(target=task, args=(mutex,)) t.start() t_list.append(t) for t in t_list: t.join() print(num) # 多个线程在操作同一份的数据的时候 会造成数据的错乱的 所以需要对我们的操作的数据加锁 #
五、协程

1.简介

协程(Coroutine) : 是单线程下的并发 , 又称微线程 , 纤程 . 协程是一种用户态的轻量级线程 , 即协程有用户自己控制调度

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态

使用协程的优缺点

优点 :

  1. 协程的切换开销更小 , 属于程序级别的切换 , 更加轻量级
  2. 单线程内就可以实现并发的效果 , 最大限度利用CPU

缺点 :

  1. 协程的本质是单线程下 , 无法利用多核 , 可以是一个程序开启多个进程 , 每个进程内开启多个线程 , 每个线程内开启协程
  2. 协程指的是单个线程 , 因而一旦协程出现阻塞 将会阻塞整个线程

2.Greenlet

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

·  greenlet
为了更好使用协程来完成多任务,python中greenlet模块对其封装,从而使得切换任务变得更加简单
安装方式
pip install greenlet 
from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()      #到这里切换到gr2,执行test2()
    print(34)
    gr2.switch()      #切换到上次gr2运行的位置

def test2():
    print(56)
    gr1.switch()      #切换到上次gr1运行的位置
    print(78)

gr1 = greenlet(test1)      #启动一个协程gr1
gr2 = greenlet(test2)      #启动一个协程gr2

gr1.switch()        #开始运行gr1

greenlet

3、gevent
greenlet已经实现了协程,但是这个工人切换,是不是觉得太麻烦了,不要着急,python还有一个比greenlet更强大的并且能够自动切换任务的模块`gevent`
其原理是当一个greentlet遇到IO(指的是input ouput输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO完成,再适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。
由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:
View Code

安装:

  

pip install gevent 
import gevent

def f(n):
for i in range(n):
  print(gevent.getcurrent(), i)
  # 用来模拟一个耗时操作,注意不是time模块中的sleep
  gevent.sleep(1)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
原文地址:https://www.cnblogs.com/mofujin/p/11328251.html