1. 程式人生 > 實用技巧 >併發程式設計——定時器,協程,greenlet模組,gevent模組,單執行緒的套接字併發,asyncio模組

併發程式設計——定時器,協程,greenlet模組,gevent模組,單執行緒的套接字併發,asyncio模組

一、定時器

Timer的父類是Thread,所以定時器這裡用的是執行緒

# 多長時間之後執行一個任務
from threading import Timer


def task(name):
    print('我是大帥比--%s'%name)


if __name__ == '__main__':
    # t = Timer(2, task,args=('lqz',))  # 本質是開了個執行緒,延遲一秒執行  Timer(秒數,函式,arges=一個元組)
    t = Timer(2, task,kwargs={'name':'lqz'})  # 本質是開了個執行緒,延遲一秒執行
t.start()

二、協程

協程是為了實現單執行緒下的併發,屬性執行緒下
協程要解決的問題:儲存狀態+切換
yield:生成器,只要函式中有yield關鍵字,這個函式就是生成器,通過yield可以實現儲存狀態+切換

import time

# 序列執行
def func1():
    for i in range(100000000):
        i += 1


def func2():
    for i in range(100000000):
        i += 1


if __name__ == '__main__':
    ctime = time.time()
    func1()
    func2()
    
print(time.time() - ctime) # 7.03256796435 # 通過yield,實現儲存狀態加切換(自定義的切換,並不是遇到io才切,所有它並不能節約時間) # 單純的切換,不但不會提高效率,反而會講低效率 def func1(): for i in range(100000000): i += 1 yield def func2(): g=func1() # 先執行一下func1 for i in range(100000000): i += 1 next(g) # 回到func1執行 if __name__
== '__main__': ctime = time.time() func2() print(time.time() - ctime) #14.764776706695557 # 協程並不是真是存在的某個東西,而是程式設計師臆想出來的 # 程式設計師控制,不讓自己的程式遇到io,看上去,就實現併發了 ''' 優點如下: 協程的切換開銷更小,屬於程式級別的切換,作業系統完全感知不到,因而更加輕量級 單執行緒內就可以實現併發的效果,最大限度地利用cpu
缺點如下: 協程的本質是單執行緒下,無法利用多核,可以是一個程式開啟多個程序,每個程序內開啟多個執行緒,每個執行緒內開啟協程 協程指的是單個執行緒,因而一旦協程出現阻塞,將會阻塞整個執行緒
總結協程特點: 必須在只有一個單執行緒裡實現併發 修改共享資料不需加鎖 使用者程式裡自己儲存多個控制流的上下文棧(需要儲存狀態) 附加:一個協程遇到IO操作自動切換到其它協程(如何實現檢測IO,yield、greenlet都無法實現,就用到了gevent模組(select機制))
'''

三、greenlet模組

協程相關模組

from greenlet import greenlet
import time
# 遇到io不會切,初級模組,gevent模組基於它寫的,處理io切換
def eat():
    print('我吃了一口')
    time.sleep(1)
    # p.switch()
    print('我又吃了一口')
    # p.switch()


def play():
    print('我玩了一會')
    e.switch()
    print('我又玩了一會')


if __name__ == '__main__':
    e = greenlet(eat)
    p = greenlet(play)
    e.switch()

四、gevent模組

協程相關模組

# gevent基於greenlet寫的,實現了遇見io自動切換
import gevent
import time

def eat(name):
    print('%s 吃了一口' % name)
    gevent.sleep(1)  # io操作
    print('%s 又吃了一口' % name)


def play(name):
    print('%s 玩了一會' % name)
    gevent.sleep(2)
    print('%s 又玩了一會' % name)


if __name__ == '__main__':
    ctim = time.time()
    e = gevent.spawn(eat,'lqz')
    p = gevent.spawn(play,'lqz')
    e.join() # 等待e執行完成
    p.join()
    print('')
    print(time.time() - ctim)  #2.0165154933929443

    這個程式執行完成,最少需要多長時間 2s多一點
    ctim=time.time()
    eat('lqz')
    play('lqz')
    print(time.time()-ctim) # 3.0190377235412598

gevent模組加上猴子補丁

from gevent import monkey;monkey.patch_all()#這句話必須寫
import gevent
import time

def eat(name):
    print('%s 吃了一口' % name)
    time.sleep(1)  # io操作,被猴子補丁替換之後,gevent.sleep()
    print('%s 又吃了一口' % name)


def play(name):
    print('%s 玩了一會' % name)
    time.sleep(2)
    print('%s 又玩了一會' % name)


if __name__ == '__main__':
    ctim = time.time()
    e = gevent.spawn(eat,'lqz')
    p = gevent.spawn(play,'lqz')
    e.join() # 等待e執行完成
    p.join()
    print('')
    print(time.time() - ctim)  #2.0165154933929443

五、單執行緒的套接字併發

使用gevent實現單執行緒下的套接字併發效果

六、asyncio模組

3.4及其之前版本使用的asyncio模組

import time
import asyncio

# 把普通函式變成協程函式
# 3.4及其以前這麼寫,3.5開始就棄用了,3.5版本及其之後用了不報錯但會提示已經啟用
@asyncio.coroutine
def task():
    print('開始了')
    yield from asyncio.sleep(1)  #asyncio.sleep(1)模擬io
    print('結束了')


loop=asyncio.get_event_loop()  # 獲取一個時間迴圈物件#

# 協程函式加括號,並不會真正的去執行,它需要提交給loop,讓loop迴圈著去執行
# 協程函式列表

ctime=time.time()
t=[task(),task()]
loop.run_until_complete(asyncio.wait(t))
loop.close()
print(time.time()-ctime)

現在使用的asyncio模組用法

import time
import asyncio
from threading import current_thread
# 表示我是協程函式,等同於3.5之前的裝飾器
async def task():
    print('開始了')
    print(current_thread().name)
    await asyncio.sleep(3)  # await等同於原來的yield from
    print('結束了')

async def task2():
    print('開始了')
    print(current_thread().name)
    await asyncio.sleep(2)
    print('結束了')

loop=asyncio.get_event_loop()

ctime=time.time()
t=[task(),task2()]
loop.run_until_complete(asyncio.wait(t))
loop.close()
print(time.time()-ctime)

---40---