Python併發編程(六):多線程(實戰提高篇)

一、信號量(瞭解)

同進程的一樣

Semaphore管理一個內置的計數器, 每當調用acquire()時內置計數器-1; 調用release() 時內置計數器+1; 計數器不能小於0;當計數器爲0時,acquire()將阻塞線程直到其他線程調用release()。

實例:(同時只有5個線程可以獲得semaphore,即可以限制最大連接數爲5):

from threading import Thread,Semaphore
import threading
import time
# def func():
#     if sm.acquire():
#         print (threading.currentThread().getName() + ' get semaphore')
#         time.sleep(2)
#         sm.release()
def func():
    sm.acquire()
    print('%s get sm' %threading.current_thread().getName())
    time.sleep(3)
    sm.release()
if __name__ == '__main__':
    sm=Semaphore(5)
    for i in range(23):
        t=Thread(target=func)
        t.start()

二、Event事件(瞭解)

同進程的一樣,看下代碼即可,詳細理論請看多進程的Event事件

from threading import Thread,Event
import threading
import time,random
def conn_mysql():
    count=1
    while not event.is_set():
        if count > 3:
            raise TimeoutError('鏈接超時')
        print('<%s>第%s次嘗試鏈接' % (threading.current_thread().getName(), count))
        event.wait(0.5)
        count+=1
    print('<%s>鏈接成功' %threading.current_thread().getName())


def check_mysql():
    print('\033[45m[%s]正在檢查mysql\033[0m' % threading.current_thread().getName())
    time.sleep(random.randint(2,4))
    event.set()
if __name__ == '__main__':
    event=Event()
    conn1=Thread(target=conn_mysql)
    conn2=Thread(target=conn_mysql)
    check=Thread(target=check_mysql)

    conn1.start()
    conn2.start()
    check.start()

三、定時器

定時器,指定n秒後執行某操作

from threading import Timer

def hello():
    print("hello, world")

t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed
time.sleep(3)
t.cancel() # 結束定時任務

驗證碼定時器

from threading import Timer
import random,time

class Code:
    def __init__(self):
        self.make_cache() # 類的對象的初始化階段就已經開始執行定時生成驗證碼任務了

    def make_cache(self,interval=5):
        self.cache=self.make_code() # 賦值給類的一個屬性,便於類外訪問得到驗證碼
        print(self.cache) 
        self.t=Timer(interval,self.make_cache) # 每隔一段時間,執行一次make_cache函數,同時生成驗證碼的make_code函數也會被重新執行!
        self.t.start() # 開始執行

    def make_code(self,n=4):
        res=''
        for i in range(n):
            s1=str(random.randint(0,9))
            s2=chr(random.randint(65,90))
            res+=random.choice([s1,s2])
        return res

    def check(self):
        while True:
            inp=input('>>: ').strip()
            if inp.upper() ==  self.cache:
                print('驗證成功',end='\n')
                self.t.cancel()
                break


if __name__ == '__main__':
    obj=Code()
    obj.check()

四、三種線程安全的隊列

1、queue.Queue(maxsize)  先進先出
'''
import queue

q = queue.Queue()  # 先進先出
q.put('1234')
q.put(1234)
q.put(['qweqwe'])

print(q.get())
print(q.get())
print(q.get())

執行結果:
1234
1234
['qweqwe']
'''

2、queue.IifoQueue(maxsize) 堆棧  先進後出
'''
import queue
q = queue.LifoQueue()   # 先進後出
q.put('1234')
q.put('123')
q.put(['1234'])

print(q.get())
print(q.get())
print(q.get())

執行結果:
['1234']
123
1234
'''

3、queue.PriorityQueue(maxsize)  優先級隊列,存儲數據時可以設置優先級的隊列,數值越小,優先級越高
'''
import queue
q = queue.PriorityQueue()
q.put((22,'hhhh'))
q.put((12,'wssss'))
q.put((1,'yeeee'))
q.put((44,'seeee'))

print(q.get())
print(q.get())
print(q.get())
print(q.get())

執行結果:
(1, 'yeeee')   
(12, 'wssss')
(22, 'alxe')
(44, 'seeee')
'''

五、Python標準模塊–concurrent.futures

1、線程池、進程池介紹

concurrent.futures模塊提供了高度封裝的異步調用接口
ThreadPoolExecutor:線程池,提供異步調用
ProcessPoolExecutor: 進程池,提供異步調用


#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)
異步回調函數

2、線程池創建操作

進程池在多進程那章已經做了詳盡的介紹!因此這裏主要講解線程池。但是線程池和進程池幾乎用法完全一致!

3、map的用法

from concurrent.futures import ThreadPoolExecutor

import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ThreadPoolExecutor(max_workers=3)

    # for i in range(11):
    #     future=executor.submit(task,i)

    executor.map(task,range(1,12)) #map取代了for+submit

4、異步回調函數

請見進程池:https://blog.csdn.net/weixin_44571270/article/details/106577032
線程池的異步回調的用法和進程池一樣的!

這裏說一下:我們多線程或進程對一個任務進行處理,用一個變量接收處理完任務的返回值,然後操作數據也可以啊。爲什麼一定要異步回調函數?用變量接收,那麼原來異步的多線程、多進程,就成了同步了,降低了效率。異步回調函數是任務處理完,函數自己調用異步回調函數處理結果,整個過程還是異步的!而且很方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章