s9python并发编程

python之路——操作系统的发展史
python之路——博客目录

书籍推荐:现代操作系统

进程

进程

import os
import time
from multiprocessing import Process
def func(args):
    print(args)
    time.sleep(1)
    print('子进程:', os.getpid())
    print('子进程的父进程:', os.getppid())  # 查看子进程的父进程
    print(12345)
if __name__ == '__main__':  #启动进程时在windows系统上需要这句话
    p = Process(target=func,args = ('参数',)) #注册,把函数注册到进程对象里面
    # p是一个进程对象,还没有启动进程
    #参数以元组形式接收,两个参数就写为args = ('参数','参数2')
    p.start()    #启动一个子进程,操作系统创建新进程,执行其中的代码
    print('*'*10)  
    print('父进程:',os.getpid()) #查看当前进程的进程号
    print('父进程的父进程:', os.getppid())  # 查看当前进程的父进程,这里是pycharm

#所有代码都属于父进程,只是p.start()开启了一个子进程,但是p.start()也是在父进程里面执行的,即p.start()也是父进程的代码
#进程的生命周期
    #一般的主进程和子进程:执行完代码就结束
    #开启了子进程的主进程:
        #主进程自己的代码执行时间长,等待自己代码执行结束
        #子进程的执行时间长,主进程会在自己代码执行完毕后,
        #等待子进程代码执行完毕,主进程才结束
        #父进程关闭,子进程不一定会关闭

join()方法
主进程等待子进程结束(强调:是主线程处于等的状态,而有join方法的进程是处于运行的状态)

#join()
import time
from multiprocessing import Process

def func(arg1,arg2):
    print('*'*arg1)
    time.sleep(5)
    print('*'*arg2)

if __name__ == "__main__":
    p = Process(target=func,args=(10,20))
    p.start()
    print('hhhahahah') #会立即执行
    p.join()   #是感知一个子进程的结束,将异步的程序改为同步
    print('==== : 运行完了')

开启多个子进程

import time
from multiprocessing import Process
def func(arg1,arg2):
    print('*'*arg1)
    time.sleep(5)
    print('*'*arg2)

if __name__ == '__main__':
    p_list = []
    for i in range(10):
        p = Process(target=func,args=(10*i,20*i)) #进程不是先创建就先运行,有等待的过程
        p_list.append(p)
        p.start()
        # p.join()
    [p.join() for p in p_list] #之前的所有进程都必须在这里都执行完才能执行下面的代码
    print('运行完了')
    # p1 = Process(target=func,args=(10,20))
    # p1.start()
    # p2 = Process(target=func, args=(10, 20))
    # p2.start()
    # p3 = Process(target=func, args=(10, 20))
    # p3.start()
    # p4 = Process(target=func, args=(10, 20))
    # p4.start()

多进程写文件

#多进程写文件
#首先向文件夹中写文件
#写入文件之后,向用户展示文件夹中所有的文件名
from multiprocessing import Process
import os
def func(filename, content):
    with open(filename,'w') as f:
        f.write(content*'*')

if __name__ == '__main__':
    p_list = []
    for i in range(5):
        p_lst = []
        p = Process(target=func,args=('info%s'%i,i))
        p_lst.append(p)
        p.start()
    [p.join() for p in p_lst]  #之前的所有进程都必须在这里都执行完才能执行下面的代码
    print([i for i in os.walk(r'C:\Users\Yang\PycharmProjects\python_s3\test\pkg')])

开启子进程的第二种方式

import os
from multiprocessing import Process

class MyProcess(Process):
    def run(self):
        print(os.getpid())

if __name__ == '__main__':
    print('主:',os.getpid())
    p1 = MyProcess()
    p1.start()
    p2 = MyProcess()
    P2.start()

#自定义类 继承Process类
#必须实现一个run方法,run方法中是在子进程中执行的代码

传递参数

import os
from multiprocessing import Process

class MyProcess(Process):
    def __init__(self,arg1,arg2):
        super().__init__()
        self.arg1 = arg1
        self.arg2 = arg2

    def run(self):
        print(self.pid) #进程号
        print(self.name) #进程名
        print(os.getpid())
        print(self.arg1)
        print(self.arg2)
if __name__ == '__main__':
    print('主:',os.getpid())
    p1 = MyProcess(1,2)
    p1.start()
    p2 = MyProcess(3,4)
    p2.start()

#自定义类 继承Process类
#必须实现一个run方法,run方法中是在子进程中执行的代码

多进程之间的数据隔离问题

import os
from multiprocessing import Process

def func():
    global n  #声明了一个全局变量
    n = 0       #赋值
    print('pid:%s'%os.getpid(),n)

if __name__ == '__main__':
    n = 100
    p = Process(target=func)
    p.start()
    p.join()
    print(os.getpid(),n)

使用多进程实现socket服务端的并发效果

import socket
from multiprocessing import Process

def serve(conn):
    ret = '你好'.encode('utf-8')
    conn.send(ret)
    msg = conn.recv(1024).decode('utf-8')
    print(msg)
    conn.close()
if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        p = Process(target=serve,args=(conn,))
        p.start()


    sk.close()
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
msg = sk.recv(1024).decode('utf-8')
print(msg)
msg2 = input('>>>').encode('utf-8')
sk.send(msg2)
sk.close()

守护进程

#守护进程
#子进程 --> 守护进程
import time
from multiprocessing import Process

def func():
    while True:
        time.sleep(0.2)
        print('我还活着')
def func2():
    print('in func2')
    time.sleep(8)
    print('in func2 finished')


Process(target=func,args=())

if __name__ == '__main__':
    p = Process(target=func)
    p.daemon = True #设置子进程为守护进程
    p.start()
    p2 = Process(target=func2)
    p2.start()
    p2.terminate()  #在主进程内结束一个子进程
    print(p2.is_alive()) #打印为True,因为不会立即关闭
    time.sleep(2)
    print(p2.is_alive()) #打印为Flase,检验一个进程是否还活着
    print(p2.name)    #打印进程名字
    # i = 0
    # while i<5:
    #     print('我是socket server')
    #     time.sleep(1)
    #     i+=1

#守护进程会随着主进程的代码执行完毕而结束
#主进程完需要1.主进程的代码执行完2.子进程执行完
#在主进程内结束一个子进程 p.terminate()
    #结束一个进程不是在执行方法后立即生效,需要一个操作系统响应的过程
#  检验一个进程是否活着的状态  p.is_alive()
# p.name p.pid 这个进程的名字和进程号

进程同步控制(锁、信号量、事件)

买票,进程锁

import json
import time
from multiprocessing import Process
from multiprocessing import Lock

def show(i):
    with open('ticket') as f:
        dic = json.load(f)
    print('余票: %s'%dic['ticket'])
def buy_ticket(i,lock):
    lock.acquire()  # 拿钥匙进门
    with open('ticket') as f:
        dic = json.load(f)
        time.sleep(0.1)
    if dic['ticket'] > 0:
        dic['ticket'] -= 1
        print('\033[32m%s买到票了\033[0m'%i)
    else:
        print('\033[31m%s没买到票\033[0m'%i)
    time.sleep(0.1)
    with open('ticket','w') as f:
        json.dump(dic,f)
    lock.release()      #还钥匙

if __name__ == '__main__':
    for i in range(10):
        p =  Process(target=show,args=(i,))
        p.start()
    lock = Lock()
    for i in range(10):
        p = Process(target=buy_ticket,args=(i,lock))
        p.start()

文件ticket内容

{"ticket": 0}

小结:

from multiprocessing import Process
方法
进程对象.start() 开启一个子进程
进程对象.join() 感知一个子进程的结束
进程对象.terminate() 结束一个子进程
进程对象.is_alive() 查看某个子进程是否还在运行
属性
进程对象.name 进程名
进程对象.pid 进程号
进程对象.daemon 值为True的时候,表示子进程是一个守护进程,在start之前设置
守护进程会随着主进程的代码执行完毕而结束
from multiprocessing import Lock
l = Lock()
l.acquire() #拿钥匙
会造成数据不安全的操作写在这里
l.release() #还钥匙

子进程里面不能写输入,因为子进程无法感知主进程的输入(输入是在主进程发生的)
这段代码会报错
但是多线程是可以写输入的

from multiprocessing import Process
def func():
    num = input('>>>')
    print(num)

if __name__ == '__main__':
    Process(target=func).start()

信号量

#多进程中的组件
#一套资源 同一时间 只能被n个人访问
#某一段代码 同一时间 只能被n个进程访问
import time
import random
from multiprocessing import Process
from multiprocessing import Semaphore
def ktv(i,sem):
    sem.acquire()    #获取钥匙
    print('%s走进ktv'%i)
    time.sleep(random.randint(1,5))
    print('%s走出ktv' % i)
    sem.release()     #还钥匙
if __name__ == '__main__':
    sem = Semaphore(4)
    for i in range(20):s
        p = Process(target=ktv,args=(i,sem))
        p.start()

红绿灯

#红绿灯事件
import time
import random
from multiprocessing import Event,Process
def cars(e,i):
    if not e.is_set():
        print('car%s在等待'%i)
        e.wait()    #阻塞
    print('\033[0;32;40mcar%s通过\033[0m'%i)
def light(e):
    while True:
        if e.is_set():
            e.clear()
            print('\033[31m红灯亮了\033[0m')
        else:
            e.set()
            print('\033[32m绿灯亮了\033[0m')
        time.sleep(2)

if __name__ == '__main__':
    e = Event()
    traffic = Process(target=light,args=(e,))
    traffic.start()
    for i in range(20):
        car = Process(target=cars,args=(e,i))
        car.start()
        time.sleep(random.random()) #这里是一个循环,所以这里的sleep有等待的作用
                                    #类似于几个进程之间有sleep()

进程间的通信(队列和管道)

队列

#队列 先进先出
from multiprocessing import Queue
q = Queue(5)  #参数表示队列大小是5,也可以不传参数
for i in range(5):
    q.put(i)    #放入
print(q.full())  #队列是否满了
print(q.get())   #取出
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.empty())  #队列是否空
try:
    q.get_nowait()  #若队列为空,会引发queue.Empty异常
except:
    print('队列已空')

进程间的通信

import time
from multiprocessing import Queue,Process
def produce(q):
    q.put('hello')
    q.put('abc')
def consume(q):
    print('123'+q.get())   #abc传入这里,sleep可以改变传入的位置
if __name__ == '__main__':
    q = Queue()
    p = Process(target=produce,args=(q,))
    p.start()
    c = Process(target=consume, args=(q,))
    c.start()
    # time.sleep(1)
    print('456'+q.get())  #hello传入这里,sleep可以改变传入的位置
    #说明可以通过队列在子进程和子进程之间通信
    #说明可以通过队列在子进程和主进程之间通信
    #这里的结果显示的传递顺序问题没搞清楚

生产者消费者模型初步

#生产者消费者模型
#生产者 进程
#消费者 进程
import time
import random
from multiprocessing import Queue,Process
def consumer(q,name):
    while True:
        food = q.get()
        if food is None:
            print('%s获取了一个空'%name)
            break
        print('\033[31m%s消费了%s\033[0m' %(name,food))
        time.sleep(random.randint(1,3))
def producer(name,food,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        f = '%s生产了%s%s' %(name,food,i)
        print(f)
        q.put(f)

if __name__ == '__main__':
    q = Queue(20)
    p1 = Process(target=producer,args=('Egon','包子',q))
    p2 = Process(target=producer, args=('wusir','饺子',q))
    c1 = Process(target=consumer, args=(q,'alex'))
    c2 = Process(target=consumer, args=(q, 'jin'))
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    q.put(None)
    q.put(None)
    #队列默认的满足锁的机制,即同一时间只有一个进程可以访问数据
#生产者消费者模型
#生产者 进程
#消费者 进程
import time
import random
from multiprocessing import Process,JoinableQueue
def consumer(q,name):
    while True:
        food = q.get()
        print('\033[31m%s消费了%s\033[0m' %(name,food))
        time.sleep(random.randint(1,3))
        q.task_done()      #

def producer(name,food,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        f = '%s生产了%s%s' %(name,food,i)
        print(f)
        q.put(f)
    q.join()       # 感知一个队列中的数据 全部被执行完毕
                    #阻塞,直到一个队列中的所有数据全部被处理完毕

if __name__ == '__main__':
    q = JoinableQueue(20)
    p1 = Process(target=producer,args=('Egon','包子',q))
    p2 = Process(target=producer, args=('wusir','饺子',q))
    c1 = Process(target=consumer, args=(q,'alex'))
    c2 = Process(target=consumer, args=(q, 'jin'))
    p1.start()
    p2.start()
    c1.daemon = True   #设置为守护进程 主进程中的代码执行完毕之后,子进程自动结束
    c2.daemon = True
    c1.start()
    c2.start()
    p1.join()
    p2.join()       #感知一个进程的结束

#q.join()会导致队列的数据全部被消费者执行之后,生产者进程才会结束
#p1.join()会阻塞,直到生产者进程结束,当生产者进程结束之后,主进程代码就会执行完毕
#主进程代码执行完毕,会引起守护进程自动结束(我们将消费者设置为守护进程),即整个程序停止了
#不用像前一个代码一样,使用 q.put(None)
#q.task_done() 和q.join()是JoinableQueue相对于Queue的特有方法,JoinableQueue也是队列


#在消费者这一端
    #每次获取一个数据
    #处理一个数据:标志一个数据被处理成功   q.task_done()

#在生产者这一端:
    #每一次生产一个数据,将数据放在队列中
    #当生产者全部生产完毕,且要等待数据被处理完毕 q.join()
    #q.join()感知一个队列中的数据 全部被执行完毕
    #q.join()阻塞,直到一个队列中的所有数据全部被处理完毕
    #q.join()当数据被处理完毕,阻塞结束

#生产端生产出所有任务且消费端中把所有任务消耗完
#生产端的q.join() 感知到,停止阻塞
#所有的生产者进程结束
#主进程中的p.join停止阻塞
#主进程代码执行完毕
#守护进程(消费者进程)结束

小结

#信号量 Semaphore
#用锁的原理实现的,内置一个计数器
#在同一时间只能有制定数量的进程执行某一段被控制住的代码

#事件
#wait阻塞受到事件状态控制的同步组件
#状态 True False is_set
        # True -> False clear()
        # False -> True set()
#wait 状态为True不阻塞,状态为False的时候阻塞

#队列
#Queue
    #put  当队列满的时候阻塞等待队列有空位置
    #get  当队列空的时候阻塞等待队列有数据
    #full empty 不完全准确,因为这个函数返回值的时候,队列可能发生了数据输入和输出
#JoinableQueue
    #比Queue多的方法
        #task_done
        #join

管道的基本用法

from multiprocessing import Pipe

conn1,conn2 = Pipe()
conn1.send('123456')
print(conn2.recv())

进程间的通信

from multiprocessing import Pipe,Process

def func(conn):
    conn.send('吃了么')

if __name__ == '__main__':
    conn1,conn2 = Pipe()
    Process(target=func,args=(conn1,)).start()
    print(conn2.recv())

防止一直等待接收的方法

from multiprocessing import Pipe,Process

def func(conn):
    while True:
        msg = conn.recv()
        if msg == None:
            break
        print(msg)

if __name__ == '__main__':
    conn1,conn2 = Pipe()
    Process(target=func,args=(conn1,)).start()
    for i in range(20):
        conn2.send('吃了么')
    conn2.send(None)  #send(None)可以让上面的函数break,否则函数将一直运行

另外一个方法,防止一直等待接收

from multiprocessing import Pipe,Process

def func(conn1,conn2):
    conn2.close()
    while True:
        try:
            msg = conn1.recv()
            print(msg)
        except EOFError:
            conn1.close()
            break

if __name__ == '__main__':
    conn1,conn2 = Pipe()
    Process(target=func,args=(conn1,conn2)).start()
    conn1.close()
    for i in range(20):
        conn2.send('吃了么')
    conn2.close()
#多个进程间的conn不会影响,所以主进程的conn1.close()不会影响子进程
#EOFError引发的条件,输入端全部关闭的情况下接收,用close()关闭
#管道两端即可以接收也可以发送

应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭管道的输入端。如果忘记执行这些步骤,程序可能在消费者中的recv()操作上挂起(就是阻塞)。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成EOFError异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。

管道的数据不安全引发的报错

from multiprocessing import Process,Pipe,Lock

def consumer(produce, consume,name,lock):
    produce.close()
    while True:
        lock.acquire()  #收数据前加锁
        baozi=consume.recv()
        lock.release() #收数据后解锁
        if baozi:
            print('%s 收到包子:%s' %(name,baozi))
        else:
            consume.close()
            break

def producer(produce, consume,n):
    consume.close()
    for i in range(n):
        produce.send(i)
    produce.send(None) 
    produce.send(None) 
    produce.close()
#发送两次None的原因是因为,程序的接收是两个进程控制的,
#每个进程都需要接收一个None来保证consume.close()关闭
#如果有n个进程,则需要发送n次
if __name__ == '__main__':
    produce,consume=Pipe()
    lock = Lock()
    c1=Process(target=consumer,args=(produce,consume,'c1',lock))
    c2=Process(target=consumer,args=(produce,consume,'c2',lock))
    p1=Process(target=producer,args=(produce,consume,30))
    c1.start()
    c2.start()
    p1.start()
    produce.close()
    consume.close()
#pipe 数据不安全性
#管道是数据不安全的
#加锁来控制操作管道的行为,来避免进程之间争抢数据(多个进程访问一个数据)造成的数据不安全现象

#队列 进程之间数据安全的
#管道 + 锁实现队列
#这段代码以后补上

进程间的数据共享(Manager模块)

from multiprocessing import Manager,Process

def main(dic):
    dic['count'] -= 1
    print(dic)

if __name__ == '__main__':
    m = Manager()
    dic = m.dict({'count':100})   #这里的dic是一个具有数据共享属性的字典
    p_lst = []
    p = Process(target=main,args=(dic,))
    p.start()
    p.join()
    print('主进程:',dic)
from multiprocessing import Manager,Process,Lock

def main(dic,lock):
    lock.acquire()
    dic['count'] -= 1
    lock.release()

if __name__ == '__main__':
    m = Manager()
    l = Lock()
    dic = m.dict({'count':100})   #这里的dic是一个具有数据共享属性的字典
    p_lst = []
    for i in range(50):
        p = Process(target=main,args=(dic,l))
        p.start()
        p_lst.append(p)
    for i in p_lst: i.join()
    print('主进程',dic)

#结果可能不是50,因为数据可能被多个进程同时取到,只发生了一次减一
#通过加锁解决这个问题

实际工作中可能不会用到上面的东西,可能会用到下面的东西(消息中间件),这些东西自带锁的机制
kafak
rebbitmq
memcache

进程池与开启多个进程性能对比

#为什么会有进程池的概念
    #效率
    #没开启进程,都需要开启属于这个进程的内存空间
    #寄存器 堆栈 文件
    #进程过多 操作系统的调度

#python中的进程池
    #先创建一个属于进程的池子
    #这个池子指定能存放多少个进程
    #先将这些进程创建好

#更高级的进程池(python中没有,其他语言有)
    #有进程数量的上下限
    #当任务较多时会增加进程数量,直到上限
    #当任务较少时会减少进程数量,直到下限
import time
from multiprocessing import Pool,Process
def func(n):
    for i in range(10):
        print(n+1)
if __name__ == '__main__':
    start  = time.time()
    pool = Pool(5)     #进程池里面进程的数量,这里是5个进程
    pool.map(func,range(100))  #100个任务,接收的类型只能是可迭代类型,map自带join方法,即执行完所有进程的任务后才能执行后续代码
    t1 = time.time() - start
    start = time.time()
    p_lst = []
    for i in range(100):
        p = Process(target=func,args=(i,))
        p_lst.append(p)
        p.start()
    for p in p_lst:p.join()
    t2 = time.time() - start
    print(t1,t2)

进程池的其他开启方式

import time
import os
from multiprocessing import Pool
def func(n):
    print('start func%s' %n,os.getpid())
    time.sleep(1)
    print('end func%s' %n, os.getpid())

if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        #p.apply(func,args=(i,)) #t同步
        p.apply_async(func,args=(i,))  #主进程执行完立即结束,不会等待子进程,所以需要加上阻塞方法join
    p.close()     #结束进程池接收任务
    p.join()      #感知进程池中的任务执行结束,在join之前首先要没有新的任务加入

用进程池实现的基于TCP的server

import socket
from multiprocessing import Pool

def func(conn):
    conn.send(b'hello')
    print(conn.recv(1024).decode('utf-8'))
    conn.close()

if __name__ == '__main__':
    p = Pool(5)
    sk = socket.socket()
    sk.bind(('127.0.0.1',8080))
    sk.listen()
    while True:
        conn,addr = sk.accept()
        p.apply_async(func,args=(conn,))
    sk.close()
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))

ret = sk.recv(1024).decode('utf-8')
print(ret)
msg = input('>>>').encode('utf-8')
sk.send(msg)
sk.close()

进程池的返回值

# p = Pool()
# p.map(funcname,iterable) 默认异步的执行任务,且自带close和join
# p.apply  同步调用
# p.apply_async 异步调用和主进程完全同步 需要手动close和join
#同步
from multiprocessing import Pool
def func(i):
    return i*i

if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        res = p.apply(func,args=(i,))   #apply的结果就是func的返回值
        print(res)

#这是进程池特有的,在子进程return是不能被父进程接收的
#异步
import time
from multiprocessing import Pool
def func(i):
    time.sleep(1)
    return i*i

if __name__ == '__main__':
    p = Pool(5)
    res_l = []
    for i in range(10):
        res = p.apply_async(func,args=(i,))
        res_l.append(res)
        # print(res.get())    #等着func的计算结果
    for res in res_l: print(res.get())
#map
import time
from multiprocessing import Pool
def func(i):
    time.sleep(1)
    return i*i

if __name__ == '__main__':
    p = Pool(5)
    ret = p.map(func,range(10)) #返回一个列表,一次性返回
    print(ret)

回调函数

#回调函数
import os
from multiprocessing import Pool
def func1(n):
    print('in func1',os.getpid())
    return n*n
def func2(nn):
    print('in func2',os.getpid())
    print(nn)

if __name__ == '__main__':
    print('主进程::',os.getpid())
    p = Pool(5)
    p.apply_async(func1,args=(10,),callback=func2)  #func1的返回值会被当作参数传递给func2
    p.close()
    p.join()
    
#func2与主进程在一个进程里面
# 程序运行结果
# 主进程:: 20120
# in func1 14252
# in func2 20120
# 100

小结

#进程池
    #创建的进程个数一般为cpu个数+1
    # ret = map(func,iterable)
        #异步 自带close和join
        #返回所有结果的列表
    # apply
        #同步的
        #apply(func,args=())  只有当func执行完之后,才会继续向下执行其他代码
        #ret = apply(func,args=())
        #返回值就是func的return

    # apply_async
        #异步的:当fanc被注册进入一个程序之后,程序就继续向下执行
        #apply_async(func,args=())
        #返回值:apply_async返回的对象obj
            #为了用户能从中获取func的返回值obj.get()
            #get会阻塞直到对应的func执行完毕拿到结果
            #使用apply_async给进程池分配任务
                #需要先close后join来保持多进程和主进程代码的同步性

爬取数据的例子

import requests
from multiprocessing import Pool

def get(url):
    response = requests.get(url)
    if response.status_code == 200:
        return url,response.content.decode('utf-8')

def call_back(args):
    url,content = args
    print(url,len(content))

if __name__ == '__main__':
    url_lst = [
        'http://www.cnblogs.com/',
        'http://www.baidu.com/',
        'http://www.sogou.com/',
        'http://www.sohu.com/'
    ]
    p = Pool(5)
    for url in url_lst:
        p.apply_async(get,args=(url,),callback=call_back)
    p.close()
    p.join()
#由于下载数据花费时间长,处理数据耗费时间短,
#所以将下载数据用进程池去做,然后用主进程做数据处理,这样可以提高效率。

爬虫

import re
from urllib.request import urlopen
from multiprocessing import Pool

def get_page(url,pattern):
    response=urlopen(url).read().decode('utf-8')
    return pattern,response   # 正则表达式编译结果 网页内容

def parse_page(info):
    pattern,page_content=info
    res=re.findall(pattern,page_content)
    for item in res:
        dic={
            'index':item[0].strip(),
            'title':item[1].strip(),
            'actor':item[2].strip(),
            'time':item[3].strip(),
        }
        print(dic)
if __name__ == '__main__':
    regex = r'
.*?<.*?class="board-index.*?>(\d+).*?title="(.*?)".*?class="movie-item-info".*?

(.*?)

.*?

(.*?)

' pattern1=re.compile(regex,re.S) url_dic={'http://maoyan.com/board/7':pattern1} p=Pool() res_l=[] for url,pattern in url_dic.items(): res=p.apply_async(get_page,args=(url,pattern),callback=parse_page) res_l.append(res) for i in res_l: i.get()

线程出现的原因

进程的缺点:

  1. 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
  2. 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
  3. 进程之间内存不共享

进程是资源分配的最小单位,线程是CPU调度的最小单位,每一个进程中至少有一个线程。
模块datatime是基于time模块实现的,socketserver是基于socket实现的,Threading是基于Thread实现的

多线程并发

#多线程并发
import time
from threading import Thread
def func(n):
    time.sleep(1)
    print(n)
for i  in range(10):
    t = Thread(target=func,args=(i,))
    t.start()

令一种开启多线程的方法

import time
from threading import Thread
class MyTread(Thread):
    def __init__(self,arg):
        super().__init__()
        self.arg = arg
    def run(self):
        time.sleep(1)
        print(self.arg)

t = MyTread(10)
t.start()

主线程和子线程的进程是一样的

import os
import time
from threading import Thread
def func(n):
    time.sleep(1)
    print(n,os.getpid())
print('主线程', os.getpid())
for i in range(10):
    t = Thread(target=func,args=(i,))
    t.start()
s9python并发编程_第1张图片
进程和线程的内存资源分配情况.jpg

全局变量

import os
import time
from threading import Thread
def func(n):
    global g
    g = 0
    print(g,os.getpid())
g = 100
t_lst = []
for i in range(10):
    t = Thread(target=func,args=(i,))
    t.start()
    t_lst.append(t)
for t in t_lst: t.join()
print(g)

进程是最小的内存分配单位
线程是操作系统调度的最小单位
线程被CPU执行了
进程内至少含有一个线程
进程中可以开启多个线程
开启一个线程所需时间要远远小于开启一个进程
多个线程内部有自己的数据栈,这些数据不共享
全局变量在多个线程之间是共享的

s9python并发编程_第2张图片
同一进程里面的线程可能被多个cpu执行

Cpython解释器
全局解释器锁-GIL
同一时刻只能有一个线程访问CPU
锁的是什么?线程
这是python语言的问题吗?
不是,是Cpython解释器的特性(解释性语言都可能存在这个问题,读一行
在Cpython解释器下的python程序,在同一时刻,多个线程中只能有一个线程被CPU执行
在这个链接里搜索IO
高CPU:计算类--- 高CPU利用率 (用多进程处理)
高IO:(python对这个有优势)

  1. 爬取网页 200个网页
  2. qq聊天 send recv
  3. 处理日志文件 读文件
  4. 处理web请求
  5. 读数据库

GIL全局解释锁

在多线程环境中,Python 虚拟机按以下方式执行:

a、设置 GIL;

b、切换到一个线程去运行;

c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

d、把线程设置为睡眠状态;

e、解锁 GIL;

d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL

线程和进程效率对比

import time
from threading import Thread
from multiprocessing import Process
def func(n):
    n + 1

if __name__ == '__main__':
    start = time.time()
    t_lst = []
    for i in range(100):
        t = Thread(target=func,args=(i,))
        t.start()
        t_lst.append(t)
    for t in t_lst:t.join()
    t1 = time.time() - start

    start = time.time()
    t_lst = []
    for i in range(100):
        t = Process(target=func, args=(i,))
        t.start()
        t_lst.append(t)
    for t in t_lst: t.join()
    t2 = time.time() - start
    print(t1,t2)

多线程输入问题

# from threading import Thread
#
# def demo(n):
#     inp = input('%s:'%n)
#     print(inp)
# for i in range(5):
#     t = Thread(target=demo,args=('name%s'%i,)) #多个%s叠加的问题
#     t.start()
#多线程是可以输入的,多进程中子进程不可以输入

多线程socket server聊天

import socket
from threading import Thread

def chat(conn):
    conn.send(b'hello')
    msg = conn.recv(1024).decode('utf-8')
    print(msg)
    conn.close()

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while True:
    conn,addr = sk.accept()
    Thread(target=chat,args=(conn,)).start()
sk.close()
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))

msg = sk.recv(1024)
print(msg)
inp = input('>>>').encode('utf-8')
sk.send(inp)
sk.close()

线程模块的其他方法

import threading
import time
def wahaha(n):
    time.sleep(0.5)
    print(n,threading.current_thread(),threading.get_ident())

for i in range(10):
    threading.Thread(target=wahaha,args=(i,)).start()
print(threading.active_count())  #查看当前活跃线程数量,前面的sleep是为了当代码运行到这里的时候让线程活着
print(threading.current_thread())
print(threading.enumerate())  #让所有活着的线程名字的结果都在一个列表里,列表的顺序是先主进程,然后子进程按开启的顺序排列

#threading.current_thread()查看线程名字和id
#threading.get_ident()查看线程ID
#线程
    #线程是进程中的执行单位
    #线程是CPU执行的最小单位
    #线程之间资源共享
    #线程的开启和关闭以及切换的时间开销远远小于进程
    #线程本身可以在同一时间使用多个CPU
#python与线程
    #Cpython解释器在解释代码过程中容易产生数据不安全的问题
    #GIL全局解释器锁,锁的是线程
  1. 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
  2. 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

守护线程

import time
from threading import Thread
def func1():
    while True:
        print('*'*10)
        time.sleep(1)

def func2():
    print('in func2')
    time.sleep(5)


t = Thread(target=func1,)
t.daemon = True
t.start()
t2 = Thread(target=func2,)
t2.start()
print('主线程')
#主线程会等待子线程的结束
#若只有主线程,主线程结束,守护线程随之结束
#守护线程会等待所有线程(子和主)结束之后才结束
#守护进程随着主进程代码的执行结束而结束

from threading import Lock

lock = Lock()
# lock.acquire()
# lock.acquire()
print(123)
#只有两个acquire的时候才会出现阻塞,打印不出123
#这说明已经获取锁而不释放锁的情况下,再去获取锁会引起阻塞

全局解释器锁GIL并不能保证数据一定安全

import time
from threading import Lock,Thread

def func():
    global n
    temp = n
    time.sleep(0.2)
    n = temp -1

n = 10
t_lst = []
for i in range(10):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)

for t in t_lst: t.join()
print(n)
#若取出数据后,CPU的时间片到了,由于GIL锁的是进程,
#所以其他线程这时候也可以访问线程,这样就造成了数据不安全
#本程序是用sleep引起阻塞,这样可以保证每一个线程都可以访问最原始的数据,这样最终的返回值就是9

问题的解决方法

import time
from threading import Lock,Thread

def func(lock):
    global n
    lock.acquire()
    temp = n
    time.sleep(0.2)
    n = temp -1
    lock.release()

n = 10
t_lst = []
lock = Lock()
for i in range(10):
    t = Thread(target=func,args=(lock,))
    t.start()
    t_lst.append(t)

for t in t_lst: t.join()
print(n)

哲学家就餐问题

#哲学家就餐问题
#会导致死锁
import time
from threading import Lock,Thread
noodle_lock = Lock()   #互斥锁
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条了'%name)
    fork_lock.acquire()
    print('%s拿到叉子了'%name)
    print('%s吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s拿到面条了'%name)
    print('%s吃面'%name)
    noodle_lock.release()
    fork_lock.release()

Thread(target=eat1,args=('alex',)).start()
Thread(target=eat2,args=('Egon',)).start()
Thread(target=eat1,args=('bossjin',)).start()
Thread(target=eat2,args=('nezha',)).start()

解决方法

#哲学家就餐问题
#与线程里面的一样,进程里面也存在死锁现象
#会导致死锁
import time
from threading import RLock,Thread
noodle_lock = RLock()   #递归锁,可以被acquire多次,理解为一串钥匙
fork_lock= noodle_lock
print(fork_lock is noodle_lock)  #返回True
def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条了'%name)
    fork_lock.acquire()
    print('%s拿到叉子了'%name)
    print('%s吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s拿到面条了'%name)
    print('%s吃面'%name)
    noodle_lock.release()
    fork_lock.release()

Thread(target=eat1,args=('alex',)).start()
Thread(target=eat2,args=('Egon',)).start()
Thread(target=eat1,args=('bossjin',)).start()
Thread(target=eat2,args=('nezha',)).start()

信号量

import time
from threading import Semaphore,Thread
def func(sem,a,b):
    sem.acquire()
    time.sleep(1)
    print(a+b)
    sem.release()

sem = Semaphore(4)  #同一时间只能有4个线程访问一段代码
for i in range(10):
    t = Thread(target=func,args=(sem,i,i+5))
    t.start()

事件

#事件被创建的时候
#False状态
    #wait()阻塞
#True状态
    #wait()非阻塞
#clear 设置状态为False
#set   设置状态为True

#连接数据库
#检测数据库的可连接情况

#起两个线程
#第一个线程:连接数据库
    #等待一个信号  此信号表示网络是否连通  
    #连接数据库
#等二个线程:检测与数据库之间的网络是否连通
    #用time.sleep()来模拟
    #将事件的状态设置为True
import time
import random
from threading import Thread,Event
def connect_db(e):
    count = 0
    while count < 3:
        e.wait(0.6)   #状态为False的时候,只等待参数的时间就结束
        if e.is_set() == True:
            print('连接数据库')
            break
        else:
            count += 1
            print('第%s次连接失败'%count)
    else:
        raise TimeoutError('数据库连接超时')

def check_web(e):
    time.sleep(random.randint(0,3))
    e.set()

e = Event()
t1 = Thread(target=connect_db,args=(e,))
t2 = Thread(target=check_web,args=(e,))
t1.start()
t2.start()

条件

#条件

#有点类似与过收费站,给几个人的钱就放行几个人
#acquire release notify wait
#一个条件被创造之初,wait一直处于等待状态,直到拿到钥匙
#notify(int数据类型) #创造钥匙,int数据类型表示钥匙的数量
#这里创造的钥匙是不还的
#notify和wait都要处于acquire和release之间

from threading import Thread,Condition
def func(con,i):
    con.acquire()
    con.wait()
    print('在等%s个循环里'%i)
    con.release()
con = Condition()
for i in range(10):
    Thread(target=func,args = (con,i)).start()
while True:
    num = int(input('>>>'))
    con.acquire()
    con.notify(num)
    con.release()

定时器

#定时器
#每隔5秒钟会有一次时间同步
import time
from threading import Timer
def func():
        print('时间同步')    #这里假设时间同步是需要耗时的
while True:
    Timer(5,func).start()     #等待5秒钟后执行func程序
    time.sleep(5)              #两个Timer之间是异步的

队列

import queue
#队列自带锁的机制,传递数据是安全的。
#而且主线程和子线程是可以使用这个队列的
#为什么不使用列表,因为列表数据不安全。(可以加锁解决这个问题,但是比较麻烦)
#程序里面的全局变量(等数据)是被线程之间共享的
#所以可以使用列表、字典、管道等传递数据,但是这些的数据都不安全。
q = queue.Queue()   #队列 先进先出
# 4个方法
# q.put()
# q.get()
# q.put_nowait()
# q.get_nowair()

q = queue.LifoQueue()  #栈 先进后出
# q.put()
# q.get()

q = queue.PriorityQueue() #优先级队列,在里面会有一个比较
q.put((20,'a'))
q.put((10,'c'))
print(q.get())
print(q.get())

线程池

import time
from concurrent.futures import ThreadPoolExecutor
def func(n):
    time.sleep(2)
    print(n)
    return n*n

tpool = ThreadPoolExecutor(max_workers=5)  #默认不要超过cpu个数*5
# tpool.map(func,range(20))   #map方法拿不到返回值
t_lst = []
for i in range(20):
    t = tpool.submit(func,i)
    t_lst.append(t)          #由于for  i in range列表的接收是按顺序来的
tpool.shutdown()   #相当于以前的close和 join
#close表示关闭池不让任务进来,join表示阻塞直到池里面任务被执行完毕
print('主进程')
for t in t_lst:print('***',t.result()) #用result取出结果值 #若注释掉tpool.shutdown会先打印一部分结果

回调函数

import time
from concurrent.futures import ThreadPoolExecutor
def func(n):
    time.sleep(2)
    print(n)
    return n*n

def call_back(m):
    print('结果是 %s'%m.result())
tpool = ThreadPoolExecutor(max_workers=5)
for i in range(20):
     t = tpool.submit(func,i).add_done_callback(call_back)
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def parse_page(res):
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    # p=Pool(3)
    # for url in urls:
    #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
    # p.close()
    # p.join()

    p=ProcessPoolExecutor(3)
    for url in urls:
        p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果

考试题

协程

协程的概念

#进程 启动多个进程 进程之间是有操作系统负责调用
#线程 启动多个线程 真正被CPU执行的最小单位实际是线程
    #开启一个线程 创建一个线程 寄存器 堆栈
    #关闭一个线程
#协程
    #本质上是一个线程
    #能够在多个任务之间切换来节省一些IO时间
    #协程中任务之间的切换也消耗时间,但是时间开销远小于线程和进程的切换
#实现并发的手段
import time
def consumer():
    while True:
        x = yield
        time.sleep(1)
        print('处理了数据:',x)

def producer():
    c = consumer()
    next(c)
    for i in range(10):
        time.sleep(1)
        print('生成了数据:',i)
        c.send(i)

producer()
#此程序实现了函数之间的来回切换,但是没有实现节省IO时间,所以不是一个实际的协程。

greenlet

真正的协程模块就是使用greenlet完成的切换
from greenlet import greenlet   
def eat():
    print('eating start')     #2
    g2.switch()    #3
    print('eating end')  #6
    g2.switch()   #7

def play():
    print('playing start')   #4
    g1.switch()     #5
    print('playing end')   #8

g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()     #1
#工作中,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#5进程*20线程*500协程=50000

gevent

#g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,
#spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参
import gevent
def eat():
    print('eating start')
    gevent.sleep(1)
    print('eating end')

def play():
    print('playing start')
    gevent.sleep(1)
    print('playing end')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
#缺点是程序只认识gevent.sleep(),不认识time.sleep()

猴子补丁

#协程是通过greenlet的switch来回切换的,封装在gevent里面了,所以看不见。
from gevent import monkey;monkey.patch_all()
#自动将后面的模块的阻塞部分打补丁,这样就可以认识其他模块的阻塞了
import threading
import time
import gevent
def eat():
    print(threading.current_thread().getName()) #查看的结果为DummyThread-1
    print(threading.current_thread())   #返回结果也不一样
    print('eating start')
    time.sleep(1)
    print('eating end')

def play():
    print(threading.current_thread().getName()) #查看的结果为DummyThread-2,伪线程
    print(threading.current_thread())  #返回结果也不一样
    print('playing start')
    time.sleep(1)
    print('playing end')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()   #等待g1结束
g2.join()   #等待g2结束
#或者上述两步合作一步:gevent.joinall([g1,g2])
#进程和线程的任务切换由操作系统完成
#协程任务之间的切换由程序(代码)完成,只有遇到协程模块能识别的阻塞的时候,
#程序才会进行任务切换,实现并发效果

同步异步对比

#同步异步
from gevent import monkey;monkey.patch_all()
import time
import gevent

def task():
    time.sleep(1)
    print(12345)

def sync():
    for i in range(10):
        task()

def async():
    g_lst = []
    for i in range(10):
        g = gevent.spawn(task)
        g_lst.append(g)
    gevent.joinall(g_lst)  #for g in g_lst:g.join()

sync()
async()

爬虫

#协程 :能够在一个线程中实现并发效果的概念
    #能够规避一些任务中的IO操作
    #在任务的执行过程中,检测到IO就切换到其他任务

#爬虫的例子
#主要关注请求过程中的IO等待
#多线程在cpython中被弱化了
#协程在一个线程上提高CPU的利用率
#协程相比于多线程的优势,切换的效率更快
from gevent import monkey
monkey.patch_all() #写作两行也可以
import gevent
from urllib.request import urlopen
def get_url(url):
    response = urlopen(url)
    content = response.read().decode('utf-8')
    return len(content)

g1 = gevent.spawn(get_url,'http://www.baidu.com')
g2 = gevent.spawn(get_url,'http://www.sogou.com')
g3 = gevent.spawn(get_url,'http://www.taobao.com')
g4 = gevent.spawn(get_url,'http://www.hao123.com')
g5 = gevent.spawn(get_url,'http://www.cnblogs.com')
gevent.joinall((g1,g2,g3,g4,g5))  #传一个可迭代对象就可以了
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)

协程TCP

from gevent import monkey
monkey.patch_all()
import socket
import gevent
def talk(conn):
    conn.send(b'hello')
    print(conn.recv(1024).decode('utf-8'))
    conn.close()
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while True:
    conn,addr = sk.accept() #为什么这句话不能放在前面函数里面
    gevent.spawn(talk,conn)
sk.close()
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))
print(sk.recv(1024))
msg = input('>>>').encode('utf-8')
sk.send(msg)

IO模型

以前写的程序都是阻塞IO
非阻塞IO 老师在这里也讲了如何debug代码

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(False)  #把socket中所有需要阻塞的方法都改变成非阻塞 recv recvfrom accept
sk.listen()
conn_l = []
del_conn = []
while True:
    try:
        conn,addr = sk.accept()  #不阻塞,但是没人连我会报错
        print('建立连接了:',addr)
        conn_l.append(conn)
    except BlockingIOError:
        for con in conn_l:
            try:
                msg = con.recv(1024)  # 非阻塞,如果没有数据就报错
                if msg == b'':  #连接断开后会发空消息
                    del_conn.append(con)
                    continue
                print(msg)
                con.send(b'byebye')
            except BlockingIOError:pass
        for con in del_conn:
            con.close()
            conn_l.remove(con)
        del_conn.clear()
import time
import socket
import threading
def func():
    sk = socket.socket()
    sk.connect(('127.0.0.1',9000))
    sk.send(b'hello')
    time.sleep(1)
    print(sk.recv(1024))
    sk.close()

for i in range(2):
    threading.Thread(target=func).start()
s9python并发编程_第3张图片
异步IO的逻辑.jpg

IO多路复用


s9python并发编程_第4张图片
IO多路复用.jpg
import select
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8000))
sk.setblocking(False)   #设置成阻塞
sk.listen()

read_lst = [sk]     #[sk,conn]
while True:
    r_lst, w_lst, x_lst = select.select(read_lst,[],[])
    #会返回一个元祖,对应r_lst, w_lst, x_lst
    #r_lst就是sk和conn,如果有connect请求就会有sk,如果有send数据,就会有conn
    for i in r_lst:
        if i is sk:
            conn,addr = i.accept()
            read_lst.append(conn)
        else:
            ret = i.recv(1024)
            if ret == b'':
                i.close()
                read_lst.remove(i)
                continue
            print(ret)
            i.send(b'goodbye!')
import time
import socket
import threading
def func():
    sk = socket.socket()
    sk.connect(('127.0.0.1',8000))
    sk.send(b'hello')
    time.sleep(3)
    print(sk.recv(1024))
    sk.close()

for i in range(20):
    threading.Thread(target=func).start()
s9python并发编程_第5张图片
IO多路复用.jpg
#同步 提交一个任务之后要等待这个任务执行完毕
#异步 只管提交任务,不等待这个任务执行完毕就可以做其他事情
#阻塞 recv recvfrom accept
#非阻塞
#IO多路复用
    #seclet机制 Windows linux  都是操作系统轮询每一个被监听的项,看是否有读操作
    #poll机制  linux    它可以监听的对象比seclet机制可以监听的多
                        # 随着监听项的增多,导致效率降低
    #epoll机制  linux
s9python并发编程_第6张图片
IO多路复用epoll机制.jpg

s9python并发编程_第7张图片
异步IO.jpg

s9python并发编程_第8张图片
模型比较.jpg

django不是异步框架,异步的框架有Twisted tornado

你可能感兴趣的:(s9python并发编程)