并发编程

目录:

  一 . 操作系统介绍

  二. 进程

  三. 线程

  四. 协程

 

一 . 操作系统介绍

  1. io 输入输出

    input(输入到内存): input read recv recvfrom import

    output(从内存输出):print send sendto write

  2. 操作系统的发展

    多道操作系统: 

      一台计算机上同时可以出现多个任务,而多个任务所使用的资源隔离开的,
      当一个任务遇到输入输出工作的时候能够让另一个任务使用CPU去计算,提高了CPU的利用率

    分时系统:
      任务按着时间片轮转执行,即用户不用等待命令的时间过长,降低了cpu使用率,提高了用户体验

    实时操作系统:
      及时响应输入的命令
    操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序

  3. 并发和并行
    并发:多个程序在一个cpu上交替运行
    并行:在多个cpu上同时有多个程序在执行

  4. 阻塞和非阻塞
    阻塞:cpu处于非工作状态
    非阻塞: cpu处于工作状态

  5. 同步和异步

    同步:执行一个任务,必须等待获取这个任务的结果之后才继续执行其他的任务
    异步:执行一个任务,不等待这个任务的结果就继续执行其他的任务

  6. 进程和线程
    进程: 运行中的程序,是计算机中最小的资源分配单位,多个进程之间的数据互相隔离
    每个进程都有自己的地址空间:一般包括文本区域(存储处理器执行的代码),数据区域(存储变量和进程执行期间使用的动态分配的内存)和堆栈区域(存储着活动过程调用的指令和本地变量)
    pid能够在操作系统中唯一标识一个进程  可以使用os.getpid()查看

    线程:进程中的一个单位,它不能独立存在,是计算机中能够被CPU调度的最小单位

二. 进程

 

  1. 进程三状态图
    

  2. 进程的调度算法:先来先服务调度算法 短作业优先调度算法  时间片轮转法 多级反馈队列

  3. 进程的创建:

    1. 系统初始化

    2. 一个进程在运行过程中开启了子进程(如python中的run)

    3. 用户的交互式请求,而创建一个新进程(如用户双击qq)

    4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

  4. 进程的结束

    1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

    2. 出错退出(自愿,python a.py中a.py不存在)

    3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)

    4. 被其他进程杀死(非自愿,如kill -9)

  5. multiprocessing.process模块

  process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

class Process(object):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): 
#target 表示调用对象 即子进程要执行的任务 args = () 代表传参 必须是一个元组   self.name
= '' #子进程的名称 self.daemon = False #若是True这这个子进程定义为守护进程 必须在stare()之前设置 self.authkey = None self.exitcode = None self.ident = 0 self.pid = 0 self.sentinel = None def run(self): #进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 pass def start(self): #启动进程 pass def terminate(self): #强制终止进程,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。 pass # 如果进程还保存了一个锁那么也将不会被释放,进而导致死锁 def join(self, timeout=None): #主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态) pass #timeout是可选的超时时间,需要强调的是, # p.join只能join住start开启的进程,而不能join住run开启的进程 def is_alive(self): #如果进程仍然运行,返回True return False

 

    使用Process模块创建进程:

import os
import time
from multiprocessing import Process
def func(a,b,c):
    time.sleep(1)
    print(a,b,c,os.getpid(),os.getppid())
    # pid : processid   ppid : parent process id 

# Process进程类
if __name__ == '__main__':
    # windows操作系统下开启子进程子进程中的代码是通过import这种方式被导入到子进程中的
    print('主 :', os.getpid())
    p Process(target=func,args=(1,2,3))  #p进程操作符
   p.strat()   #执行子进程
  如果func有返回值 但是并不能返回到父进程中
  因为进程之间数据隔离,所以子进程中的返回值父进程获取不到


  进程中的其他方法:
# 进程中的其他几个方法
import os
import time
from multiprocessing import Process
def func(a,b,c):
    time.sleep(1)
    print(a,b,c,os.getpid(),os.getppid())

# Process进程类
if __name__ == '__main__':
    p = Process(target=func,args=(1,2,3))
    p.start()   # p是一个进程操作符
    print(p.is_alive())         # True
    p.terminate()  # 异步非阻塞
    print(p.is_alive())         # True 这是因为操作系统可能在执行其他任务没有实时关闭
    time.sleep(0.1)
    print(p.is_alive())         # False
    print(p.name)               # Process-1
    print(p.pid)                # 9392
View Code

 

  join方法:

 

import time
import random
from multiprocessing import Process

def send_mail(name):
    time.sleep(random.uniform(1,3))
    print('已经给%s发送邮件完毕'%name)

if __name__ == '__main__':
    lst = ['aaa','bbb','ccc','ddd']
    # 阻塞等待一个子进程结束
    p_l = []                
    for name in lst:
        p = Process(target=send_mail,args=(name,))
        p.start()
        p_l.append(p)               #将每一个进程都添加到列表中
    for p in p_l : p.join()         #循环列表中的每一个进程  当每个进程都结束之后才执行主程序的内容
    print('所有的信息都发送完毕了')

 

  守护进程:

import time
from multiprocessing import Process
def func():
    for i in range(20):
        time.sleep(0.5)
        print('in func')

def func2():
    print('start : func2')
    time.sleep(5)
    print('end : func2')

if __name__ == '__main__':
    p = Process(target=func)
    p.daemon = True   # True表示设置p为一个守护进程
    p.start()
    p2 =Process(target=func2)
    p2.start()
    print('in main')
    time.sleep(3)
    print('finished')

   '''
执行结果:
in main
start : func2
in func
in func
in func
in func
in func
finished
end : func2
'''
View Code

  总结:

      主进程和子进程互不干扰    

    主进程要负责给子进程回收一些系统资源,所以主进程执行完毕之后程序不会结束,会等待所有的子进程结束之后才结束
    守护进程 :
    是一个子进程,守护的是主进程 结束条件 : 主进程的代码结束,守护进程也结束
    

   面向对象的方式实现多进程:

import os
from multiprocessing import Process
class MyProcess(Process):
    def __init__(self,a,b):
        super().__init__()          #必须实现父类Process的__init__方法
        self.a = a
        self.b = b
    def func(self):
        print('in func')
    def run(self):                  # 希望在子进程中执行的代码就放在run方法中
        print(os.getpid(),self.a,self.b)
        self.func()
if __name__ == '__main__':
    MyProcess(1,2).start()          # 通知操作系统开进程,执行run方法
'''执行结果
9788 1 2
in func
'''

   锁--multiprocessing.Lock

    尽管并发编程让我们能更加充分的利用IO资源,但多个进程需要操作同一个文件/数据库的时候 ,

    会产生数据不安全,这时候可以使用锁来避免多个进程同时修改一个文件.,来确保数据的安全.
  
import json
import time
from multiprocessing import Process,Lock
def search_ticket(name):
    with open('ticket',encoding='utf-8') as f:
        dic = json.load(f)
        print('%s查询余票为%s'%(name,dic['count']))

def buy_ticket(name):
    with open('ticket',encoding='utf-8') as f:          #票数数据 只看不改不需要加锁
        dic = json.load(f)
    time.sleep(2)
    if dic['count'] >= 1:
        print('%s买到票了'%name)
        dic['count'] -= 1
        time.sleep(2)
        with open('ticket', mode='w',encoding='utf-8') as f:
            json.dump(dic,f)
    else:
        print('余票为0,%s没买到票' % name)

def use(name,lock):
    search_ticket(name)        #查看数据不需要加锁
    print('%s在等待'%name)
    # lock.acquire()                               
    # print('%s开始执行了'%name)
    # buy_ticket(name)
    # lock.release()
    with lock:                      #使用with 自动执行lock.acquire() 和 lock.release()
        print('%s开始执行了'%name)  #将要修改的数据函数加锁
        buy_ticket(name)


if __name__ == '__main__':
    lock = Lock()                   #调用Lock
    l = ['aaa','bbb','ccc','ddd']
    for name in l:
        Process(target=use,args=(name,lock)).start()

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

  队列 --multiprocessing.Queue

    我们知道多个进程间的数据是隔离的,那么如何进行多个数据之间的交互呢? 可以通过网络或文件来实现

    IPC--inter process communication  进程间通信

      通过python模块的实现:

        1 基于原生的socket模块

        2 基于进程队列multiprocess.Queue

      通过第三方软件/工具实现:

        基于网络的:memcache  redis  rabbitMQ   kafka

  下面介绍一下multiprocessing.Queue

    Queue的方法介绍:

Queue([maxsize]) 
创建共享的进程队列。maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。 
Queue的实例q具有以下方法:

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

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

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

q.qsize() 
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。


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

q.full() 
如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。

方法介绍
View Code

  生产者消费者模型

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

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

    在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,

    那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。

    为了解决这个问题于是引入了生产者和消费者模式。

  什么是生产者消费者模式

    生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,

    所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,

    而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

 

   使用Queue队列+time.sleep+random 模拟生产者消费者模式:   

from multiprocessing import Process,Queue
import time
import random

def product(q):
    for i in range(10):
        time.sleep(random.random())
        print(f' +++apple {i}')
        q.put(f'apple {i}')             #将产生的数据添加到队列里

def consume(q,name):
    while 1:
        g=q.get()                       #从队列中取值
        time.sleep(random.uniform(1,2))
        if g==None:break                      #收到最后发来的None结束信息 就结束循环
        print(f'{name}---{g}')


if __name__ == '__main__':
    q = Queue()                                 #实例化一个Queue
    p = Process(target=product,args=(q,))       #开启一个生产数据的子进程 同时把q传过去
    p.start()
    lst =[]
    for i in ['x','y','z']:                     #开启多个消耗数据的子进程 同时把q传过去
        c = Process(target=consume, args=(q, i))
        lst.append(c)
    for ii in lst:
        ii.start()
    p.join()                                    #等待子进程p结束后 向队列中添加消耗数据的子进程个数的None
    for x in range(len(lst)):
        q.put(None)

 使用multiprocessing.JoinableQueue模拟生产者消费者模式:

from multiprocessing import Process,JoinableQueue
import time
import random

def product(jq):
    for i in range(10):
        time.sleep(random.random())
        print(f'+++apple {i}')
        jq.put(f'apple {i}')
    jq.join()                       #等待JoinableQueue为空

def consume(jq,name):
    while 1:
        x = jq.get()
        time.sleep(random.uniform(1,2))
        print(f'{name}--{x}')
        jq.task_done()              #每处理完完(不是拿到数据)一个数据 使用.task_done()JoinableQueue从减去1


if __name__ == '__main__':
    jq = JoinableQueue()
    p = Process(target=product,args=(jq,))
    p.start()
    lst = []
    for i in ['x','y','z']:
        c = Process(target=consume,args=(jq,i))
        lst.append(c)
    for ii in lst:
        ii.daemon= True                 #将消费者进程设置为守护进程
        ii.start()
    p.join()                        #等待子进程结束 所有的(守护进程)消费者进程就会自行结束

 

三. 线程

    线程是能独立运行的最小单位.每一个进程至少有一个线程.线程也可以理解为轻型进程

    线程与进程的区别:

      进程之间的数据是隔离的;而线程之间的数据共享

      进程之间通信靠IPC,线程之间通信可以通过全局变量(进程的数据)

      线程的开启\销毁\切换都比进程要高效很多

    使用threading模块创建线程:

import os
import time
from threading import Thread

def func(i):
    time.sleep(1)
    print('in func',i,os.getpid())

print('in main',os.getpid())
for i in range(20):
    Thread(target=func,args=(i,)).start()           #用法和multiprocessing.Process模块类似
'''
in main 2728       
in func 3 2728        
in func 2 2728
in func 0 2728
in func 1 2728
in func 4 2728
顺序不一定
'''
    

  使用面向对象自定义创建线程:

from threading import Thread
import time
class Mythread(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        time.sleep(2)
        print('%s say hello' % self.name)


if __name__ == '__main__':
    t = Mythread('aaa')
    t.start()
    print('主线程')

创建线程的方式2
View Code

 

  Thread模块的方法:

# Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

# threading模块提供的一些方法:
    # t = threading.currentThread(): 返回当前的线程变量。 <Thread(Thread-1, started 4588)>
    #t.name :Thread-1
    #t.ident:4588
    # threading.enumerate(): 返回一个正在运行的线程对象的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
    # threading.activeCount()(threading.active_count()): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

 

 

# lst = [1,2,3,4,5,6,7,8,9,10]
# 按照顺序把列表中的每一个元素都计算一个平方
# 使用多线程的方式
# 并且将结果按照顺序返回
import time
import random
from threading import Thread,currentThread
dic = {}
def func(i):
    t = currentThread()
    time.sleep(random.random())
    dic[t.ident] = i**2
t_lst = []
for i in range(1,11):
    t = Thread(target=func,args=(i,))
    t.start()
    t_lst.append(t)
for t in t_lst:
    # print(t)    #<Thread(Thread-1, started 11276)> 线程的名称和ident
    t.join()
    print(dic[t.ident])

 

   一道面试题:

from threading import active_count   # active_count返回当前有多少个正在工作的线程
import time
import random
from threading import Thread,currentThread
dic = {}
def func(i):
    t = currentThread()
    time.sleep(random.random())
    dic[t.ident] = i**2

for i in range(10):
    Thread(target=func,args=(i,))
print(active_count())    #   -->  1 注意线程并没有开始执行
View Code

 

   守护线程:

    

import time
from threading import Thread

def daemon_func():
    while True:
        time.sleep(0.5)
        print('守护线程')

def son_func():
    print('start son')
    time.sleep(3)
    print('end son')

t = Thread(target=daemon_func)
t.daemon = True
t.start()
Thread(target=son_func).start()
time.sleep(2)
print('主线程结束')
'''
start son
守护线程
守护线程
守护线程
主线程结束
守护线程
守护线程
end son'''
# 结果分析: 守护线程不同于守护进程
# 1.主线程会等待子线程的结束而结束
# 2.守护线程会随着主线程的结束而结束
#   守护线程会守护主线程和所有的子线程

  总结:

    1.主线程需不需要回收子线程的资源
不需要,线程资源属于进程,所以进程结束了,线程的资源自然就被回收了
2.主线程为什么要等待子线程结束之后才结束
主线程结束意味着进程结束,进程结束,所有的子线程都会结束
要想让子线程能够顺利执行完,主线程只能等
3.守护线程到底是怎么结束的
主线程结束了,主进程也结束,守护线程被主进程的结束结束掉了

   同步锁:

 
  在线程中也是会出现数据不安全的
   1.对全局变量进行修改
   2.对某个值 += -= *= /=
  通过加锁来解决
#不加锁:并发执行,速度快,数据不安全
from threading import currentThread,Thread,Lock
import time
def task():
    global n
    for i in range(100000):
        n-=1

n=500000
lst=[]
start_time=time.time()
for i in range(5):
    t=Thread(target=task)
    lst.append(t)
    t.start()
for t in lst:
    t.join()
    
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
主:0.05240154266357422 n:86296
'''


#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import ,Thread,Lock
import time
def task(lock):
  global n #如果是可变数据类型就不用写global
#加锁的代码串行运行 for i in range(100000): with lock: n-=1       n=500000 lock=Lock() lst =[] start_time=time.time() for i in range(5): t=Thread(target=task,args=(lock,)) lst.append(t) t.start() for t in lst: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ''' 主:1.2022631168365479 n:0 '''

 

  死锁和递归锁:
'''
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,
因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,
这些永远在互相等待的进程称为死锁进程,如下就是死锁
'''
from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
print(123)
mutexA.acquire()     #只acquire 不release 当再次执行require的时候就会阻塞在这里
print(456)
mutexA.release()
mutexA.release()
    

解决方法:使用递归锁

# list pop/append  pop列表为空的时候会报错
# queue put/get    get队列为空的时候会等待
# 在Python中为了支持在同一线程中多次请求同一资源,
# python提供了可重入锁RLock。
# 这个RLock内部维护着一个Lock和一个counter变量,
# counter记录了acquire的次数,从而使得资源可以被多次require。
# 直到一个线程所有的acquire都被release,其他的线程才能获得资源。
# 上面的例子如果使用RLock代替Lock,则不会发生死锁:
from threading import Thread,RLock

def func1(rl):
rl.acquire()
print(1111)
rl.acquire()
print(2222)
rl.release()
rl.release() #这里如果少释放一个下一个线程就会阻塞 必须有几个acquire就有几个release



def func2(rl):
rl.acquire()
print(3333)
rl.release()

rl = RLock()
Thread(target=func1,args=(rl,)).start()
Thread(target=func2,args=(rl,)).start()
'''
1111
2222
3333
'''

  队列:

  Queue:先进先出队列
from queue import Queue
# Queue就是一个线程队列的类,自带lock锁,实现了线程安全的数据类型
# 队列是一个线程安全的数据类型

q = Queue()   # 先进先出队列
# # 在多线程下都不准
# q.empty() 判断是否为空
# q.full()  判断是否为满
# q.qsize() 队列的大小
q.put({1,2,3})
q.put_nowait('abc')
print(q.get_nowait())
print(q.get())

    

    LifoQueue队列:先进后出队列

from queue import LifoQueue   #线程安全的队列  栈和后进先出的场景都可以用
lfq = LifoQueue()
lfq.put(1)
lfq.put('abc')
lfq.put({'1','2'})
print(lfq.get())
print(lfq.get())
print(lfq.get())

    

    PriorityQueue:优先级队列
from queue import PriorityQueue  # 优先级队列
pq = PriorityQueue()
pq.put((3,'fds'))       # 放入的是元组(优先级编号,数据)
pq.put((2,'fdsa'))
pq.put((1,'fdsa'))
pq.put((4,'fds'))
print(pq.get())
print(pq.get())
print(pq.get())
print(pq.get())

'''
(1, 'fdsa')
(2, 'fdsa')
(3, 'fds')
(4, 'fds')
'''

 

   池

 

#1 介绍
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
Both implement the same interface, which is defined by the abstract Executor class.

#2 基本方法
#submit(fn, *args, **kwargs)
异步提交任务

#map(func, *iterables, timeout=None, chunksize=1) 
取代for循环submit的操作

#shutdown(wait=True) 
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

#result(timeout=None)
取得结果

#add_done_callback(fn)
回调函数

# done()
判断某一个线程是否完成

# cancle()
取消某个任务

 

 

 

import os
import time
import random
from concurrent.futures import ProcessPoolExecutor
def make(i):

    time.sleep(random.random())
    print('%s 制作螺丝%s'%(os.getpid(),i))
    return iif __name__ == '__main__':
    p = ProcessPoolExecutor(4)      # 创建一个进程池
    # lst = []
    '''
    for i in range(50):
        ret = p.submit(make,i)   # 向进程池中提交任务
        lst.append(ret)
    for i in lst:
        print(i.result())       #result取结果
        '''
    ret = p.map(make,range(50)) #ret--><generator object _chain_from_iterable_of_lists at 0x0000018313153E08>
    print(ret)                  #可以看到ret是一个生成器 这样不仅简便 而且还节省内存
    for i in ret:               #使用map相当于上述两个for循环
        print(i)          #结果与是按添加顺序打印的 而回调函数是按执行完成的顺序打印的
   p.shutdown()  # 阻塞 直到池中的任务都完成为止
   print('所有的螺丝都制作完了')
 

 

  回调函数:

import os
import time
import random
from concurrent.futures import ProcessPoolExecutor
def make(i):

    time.sleep(random.random())
    print('%s 制作螺丝%s'%(os.getpid(),i))
    return i
def func(ret):
    print(ret.result())

if __name__ == '__main__':
    p = ProcessPoolExecutor(4)      # 创建一个进程池    
    for i in range(50):
        ret = p.submit(make,i)   # 向进程池中提交任务
        ret.add_done_callback(func)  # 回调函数 当ret执行完成后立即执行func
   p.shutdown()  # 阻塞 直到池中的任务都完成为止
   print('所有的螺丝都制作完了')

 

 四.协程

    协程的就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,所以协程又称单线程下的并发,是用户态的轻量级线程.

#线程和协程的区别
#1. python的线程属于内核级别的,即由操作系统控制调度
#(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,
#以此来提升效率(!!!非io操作的切换与效率无关)

#协程的优点:
#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级(协程切换的时间基本在函数调用的时间级别)
#2. 单线程内就可以实现并发的效果,最大限度地利用cpu

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

  总结协程特点:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

  greenlet协程模块:

import time
from greenlet import greenlet   # 协程模块
def eat():    # 协程任务 协程函数
    print('start eating')
    g2.switch()                 
    time.sleep(1)
    print('end eating')
    g2.switch()

def sleep():  # 协程任务 协程函数
    print('start sleeping')
    g1.switch()
    time.sleep(1)
    print('end sleeping')

g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch()                 #switch切换任务 后面可以传参

  greenlet只是单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

  gevent协程模块:

    gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,

    在gevent中用到的主要模式是greenlet, 它是以C扩展模块形式接入Python的轻量级协程。

    greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

gecent 参数介绍
g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的

g2=gevent.spawn(func2)

g1.join() #等待g1结束

g2.join() #等待g2结束

#或者上述两步合作一步:gevent.joinall([g1,g2])

g1.value#拿到func1的返回值

 

用法: 

import time

import gevent
from gevent import monkey

monkey.patch_all()


def func(i):
time.sleep(random.random())
# print(i)
return i ** 2

lst = []
for i in range(100):
g = gevent.spawn(func, i)
lst.append(g)

gevent.joinall(lst) # 等待所有任务执行完毕

for i, g in enumerate(lst, 0):
print(i, g.value) # 使用g.value拿到函数的返回值

 

 

from gevent import monkey;monkey.patch_all()
#将monkey.patch_all()放在开头就可以识别所有阻塞了


import time
import gevent
from threading import currentThread

def eat():    # 协程任务 协程函数
    print('start eating')
    print(currentThread())
# 使用currentThread()查看当前线程发现是 <_DummyThread(DummyThread-1, started daemon 1263865797448)>
#DummyThread-n,即假线程
    time.sleep(1)
    print('end eating')

def sleep():  # 协程任务 协程函数
    print('start sleeping')
    print(currentThread()) #<_DummyThread(DummyThread-2, started daemon 1263865797704)>
    time.sleep(1)
    print('end sleeping')

g1 = gevent.spawn(eat)   # 创建协程
g2 = gevent.spawn(sleep)
gevent.joinall([g1,g2])  # 阻塞 直到协程任务结束

 

 单线程下实现并发的socket
from gevent import monkey;monkey.patch_all()
from socket import *
import gevent

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def server(server_ip,port):
    s=socket(AF_INET,SOCK_STREAM)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind((server_ip,port))
    s.listen(5)
    while True:
        conn,addr=s.accept()
        gevent.spawn(talk,conn,addr)

def talk(conn,addr):
    try:
        while True:
            res=conn.recv(1024)
            print('client %s:%s msg: %s' %(addr[0],addr[1],res))
            conn.send(res.upper())
    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    server('127.0.0.1',8080)
client 端
from gevent import monkey;monkey.patch_all()
import gevent
import socket

def func(i):

    sk = socket.socket()
    sk.connect(('127.0.0.1',9001))
    msg = 'this is '+str(i)*5
    sk.send(msg.encode('utf=8'))
    print(sk.recv(1024).decode('utf-8'))
    sk.close()


lst = [gevent.spawn(func,i) for i in range(100)]
gevent.joinall(lst)

 

  


 

posted @ 2019-04-18 18:31  RunningTron  阅读(168)  评论(0编辑  收藏  举报