Python-線程和進程之多線程之謎

第一:多線程運行特點

  • 線程執行的時候是無序的
  • 主線程會等待所有的子線程執行完成以後程序再退出

第二:Python threading模塊

1.直接調用處理:

'''
啓動兩個線程去執行run()
'''
def run(threadName):
    print(threadName,time.ctime(time.time()))

if __name__=='__main__':
    th1=threading.Thread(target=run,args=('thread-1',))
    th2=threading.Thread(target=run,args=('thread-2',))
    th1.start()
    th2.start()

'''
運行結果:
thread-1 Wed Sep 11 16:56:58 2019
thread-2 Wed Sep 11 16:56:58 2019
'''

2.繼承式調用

'''
使用Threading模塊創建線程,直接從threading.Thread繼承,然後重寫__init__方法和run方法
'''
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num=num

    def run(self):
        print(self.num, time.ctime(time.time()))
        time.sleep(2)

if __name__=='__main__':
    th1=MyThread('Thread-1')
    th2=MyThread('Thread-2')
    th3=MyThread('Thread-3')

    th1.start()
    th2.start()
    th3.start()

'''
運行結果:
Thread-1 Wed Sep 11 16:41:30 2019
Thread-2 Wed Sep 11 16:41:30 2019
Thread-3 Wed Sep 11 16:41:30 2019
'''

第三:爲啥要用多線程

業務場景:·小灰灰一邊玩吃雞遊戲,一邊跟老婆大人聊天,兩邊都不能耽誤啊,那怎麼辦?只能並行運作啊

1.併發執行兩個函數(遊戲聊天兩不耽誤)

     運行方式:先創建線程1,再創建線程2,然後去啓動線程1和線程2,並和主線程同時運行,會出現以下兩種情況:

  • 子線程先於主線程運行完畢,子線程要等待主線程執行完畢
  • 主線程先於子線程結束,子線程隨即停止

     (天皇老子都掛了,你們這些小羅羅還忙乎個啥~~~)

'''
並行的進行遊戲和聊天操作
'''
def playGame(name,game_name):
    print("%s在玩%s遊戲" %(name,game_name),time.ctime())
    time.sleep(2)
    print("playGame stop time",time.ctime())

def Chat(name,chat_name):
    print('%s同時用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(2)
    print("Chat stop time", time.ctime())


if __name__=='__main__':
    th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    th1.start()
    th2.start()
    # playGame("Ryan","Battlegrounds")
    # Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
多次運行結果:
first:
Ryan在玩Battlegrounds遊戲 Wed Sep 11 18:31:35 2019
Ryan同時用QQ在聊天 Wed Sep 11 18:31:35 2019
--------now time:Wed Sep 11 18:31:35 2019---------------
Chat stop time Wed Sep 11 18:31:37 2019
playGame stop time Wed Sep 11 18:31:37 2019
second:
Ryan在玩Battlegrounds遊戲 Wed Sep 11 18:32:01 2019
--------now time:Wed Sep 11 18:32:01 2019---------------
Ryan同時用QQ在聊天 Wed Sep 11 18:32:01 2019
playGame stop time Wed Sep 11 18:32:03 2019
Chat stop time Wed Sep 11 18:32:03 2019
'''

以上運行結果分析:

每個線程都要運行2s(因爲我設置了等待2s),但是最後運行結束時間都是一樣的,這也證明了併發執行

2.單線程式執行兩個函數(打完遊戲才能聊天的方式)

可以看出下面總共執行的時間爲:4s

'''
先結束遊戲函數,在執行聊天函數的單線程執行方式
'''
def playGame(name,game_name):
    print("%s在玩%s遊戲" %(name,game_name),time.ctime())
    time.sleep(2)

def Chat(name,chat_name):
    print('%s同時用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(2)


if __name__=='__main__':
    # th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    # th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    # th1.start()
    # th2.start()
    playGame("Ryan","Battlegrounds")
    Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
多次運行結果:
Ryan在玩Battlegrounds遊戲 Wed Sep 11 18:06:04 2019
Ryan同時用QQ在聊天 Wed Sep 11 18:06:06 2019
--------now time:Wed Sep 11 18:06:08 2019---------------
'''

3.join(主線程等待子線程執行完成後才執行)

業務場景:做好飯你喊小灰灰吃飯(可以理解爲這裏的主線程),小灰灰說,決賽圈了,老婆不要打擾我啊,我要等吃雞了再去吃飯,這時就要等待子線程結束,之後再運行主線程(也就是等他打完遊戲我才能盛飯)(join()用法)

'''
並行的進行遊戲和聊天操作
'''
def playGame(name,game_name):
    print("%s在玩%s遊戲" %(name,game_name),time.ctime())
    time.sleep(2)
    print("playGame stop time",time.ctime())

def Chat(name,chat_name):
    print('%s同時用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(2)
    print("Chat stop time", time.ctime())


if __name__=='__main__':
    threadList=[]
    th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    threadList.append(th1)
    threadList.append(th2)
    for x in threadList:
        x.start()
    x.join()
    #用最後一個子線程去阻塞主線程,等待最後一個子線程執行完成再運行主線程
    # playGame("Ryan","Battlegrounds")
    # Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
first:
Ryan在玩Battlegrounds遊戲 Wed Sep 11 19:05:32 2019
Ryan同時用QQ在聊天 Wed Sep 11 19:05:32 2019
Chat stop time Wed Sep 11 19:05:34 2019
--------now time:Wed Sep 11 19:05:34 2019---------------
playGame stop time Wed Sep 11 19:05:34 2019
second:
Ryan在玩Battlegrounds遊戲 Wed Sep 11 19:09:30 2019
Ryan同時用QQ在聊天 Wed Sep 11 19:09:30 2019
Chat stop timeplayGame stop time Wed Sep 11 19:09:32 2019
 Wed Sep 11 19:09:32 2019
--------now time:Wed Sep 11 19:09:32 2019---------------
'''

結果分析:

每一次都是等最後一個子線程Chat執行完成才能執行主線程

4.守護線程

業務場景:

  (1)join使用:有可能老婆等小灰灰遊戲和聊天結束(他老婆是真好),開始盛飯,上菜。

(2)不限制併發執行:小灰灰遊戲是結束了,但是聊天沒好結束,老婆就開始盛飯,上菜,所以不管上面哪一種情況,最後都是吃完飯才洗碗(這裏洗碗可以理解爲主線程結束)

 其實可以看出來遊戲很重要,因爲要吃雞,但是聊天無所謂,所以遊戲一結束,老婆就開始盛飯,上菜,最會不管聊天是不是還在繼續,我們吃完飯(主線程執行完畢)就自動掛掉

所謂’線程守護’,就是主線程不管該線程(設置爲守護進程說明該進程不重要)的執行情況,只要是其他子線程結束且主線程執行完畢,主線程都會關閉。也就是說:主線程不等待該守護線程的執行完再去關閉,主線程結束,那麼這個進程也結束了

'''
並行的進行遊戲和聊天操作
'''
def playGame(name,game_name):
    print("%s在玩%s遊戲" %(name,game_name),time.ctime())
    time.sleep(2)
    print("playGame stop time",time.ctime())

def Chat(name,chat_name):
    print('%s同時用%s在聊天' %(name,chat_name), time.ctime())
    time.sleep(5)
    print("Chat stop time", time.ctime())


if __name__=='__main__':
    threadList=[]
    th1=threading.Thread(target=playGame,args=("Ryan","Battlegrounds",))
    th2=threading.Thread(target=Chat,args=('Ryan','QQ',))
    # th1設置爲守護進程
    # th2.setDaemon(True)
    threadList.append(th1)
    threadList.append(th2)
    for x in threadList:
        x.start()
    # playGame("Ryan","Battlegrounds")
    # Chat("Ryan","QQ")
    print("--------now time:%s---------------" %time.ctime())

'''
1.設置th2爲守護進程,運行結果:(設置th2爲守護進程,故Chat才啓動,還沒執行完畢,程序就退出了)
Ryan在玩Battlegrounds遊戲 Wed Sep 11 20:01:15 2019
Ryan同時用QQ在聊天 Wed Sep 11 20:01:15 2019
--------now time:Wed Sep 11 20:01:15 2019---------------
playGame stop time Wed Sep 11 20:01:17 2019
--------------------------------------------------
2.未設置守護進行運行結果:(主線程等待所有的子線程執行結束才退出)
Ryan在玩Battlegrounds遊戲 Wed Sep 11 20:05:09 2019
Ryan同時用QQ在聊天 Wed Sep 11 20:05:09 2019--------now time:Wed Sep 11 20:05:09 2019---------------

playGame stop time Wed Sep 11 20:05:11 2019
Chat stop time Wed Sep 11 20:05:14 2019
'''

備註:setDaemon方法必須在start之前且要帶一個必填的布爾型參數

第四:線程鎖

1.互斥鎖

業務場景:一個進程下可以啓動多個線程,多個線程共享父進程的內存空間,也就意味着每個線程可以訪問同一份數據,此時,如果2個線程同時要修改同一份數據,會出現什麼狀況?
(1)無同步鎖情況下:

'''
減1的函數,初始賦值100,期望最終結果是0
'''
def subtraction():
    global sum
    tmp=sum
    time.sleep(0.002)
    sum=tmp-1

sum=100
if __name__ == '__main__':
    thread=[]
    #依次啓動100個線程
    for x in range(100):
        t=threading.Thread(target=subtraction)
        thread.append(t)
        t.start()
    #每一個子進程執行完畢才執行主線程
    for x in thread:
        t.join()
    print('sum = ',sum)
'''
運行結果:
first:
sum =  87
---------------
second:
sum =  94
'''

運行結果解釋:

我們在開啓100個線程的時候,當100個線程在進行subtraction函數操作時,首先要獲取各自的sum(漏洞:共同的數據不能共享同時被多線程操作)和tmp,但是此時多線程會按照時間規則來進行切換,如果當前面某些線程在處理sum時未結束,後面的進程已經開始了(增加了休眠時間來體現該效果),此時拿到的sum就不再是sum-1的期望結果了,而是拿到了sum的值。這樣就會導致,此次的線程進行自減1的操作失效了。

(2)有同步鎖之後:

'''
減1的函數,初始賦值100,期望最終結果是0
'''
#生成全局鎖
lock = threading.Lock()
def subtraction():
    global sum
    #運行之加鎖
    lock.acquire()
    tmp=sum
    time.sleep(0.002)
    sum=tmp-1
    #運行之後解鎖
    lock.release()

sum=100
if __name__ == '__main__':
    thread=[]
    #依次啓動100個線程
    for x in range(100):
        t=threading.Thread(target=subtraction)
        thread.append(t)
        t.start()
    #每一個子進程執行完畢才執行主線程
    for x in thread:
        t.join()
    print('sum = ',sum)
'''
運行結果:
sum =  0
'''

運行結果解釋:

加鎖的時候要注意,需要在引起多線程相互矛盾的共同數據部分枷鎖,並且加鎖部分的代碼就相當於單線程運行

2.信號量Semaphore

業務需求:食堂打飯,總共有5個窗口,每次最多允許5人進去挑選,有的人墨跡半天沒選好,有的人很快選好出來,此時釋放了一個窗口,那麼阿姨就會喊再進來一個人,不管怎樣,5個窗口同時運作。Semaphore就是同時允許一定數量的線程更改數據

'''
減1的函數,初始賦值100,最後sum值爲隨機
每次最多五個線程同時執行
'''
#生成全局鎖
semaphore = threading.BoundedSemaphore(5)
def subtraction(n):
    global sum
    #運行之加鎖
    semaphore.acquire()
    tmp=sum
    sum=tmp-1
    time.sleep(2)
    print("run the thread: %s\n" %n)
    # 運行之後解鎖
    semaphore.release()

sum=100
if __name__ == '__main__':
    thread=[]
    #依次啓動24個線程
    for x in range(22):
        t=threading.Thread(target=subtraction,args=(x,))
        thread.append(t)
        t.start()
    #每一個子進程執行完畢才執行主線程
    for x in thread:
        t.join()
    print('sum = ',sum)

運行結果:

疑問:爲什麼每次sum值計算的都對,因爲每次都應該有五個線程併發啊,還有就是打印的進程有的換行,有的沒換行

疑惑解答:

真是百思不得其解,但是我還是堅持同時間內應該有五個線程同時操作啊,爲什麼操作結果是單線程的結果,後來我在計算之前加了一個等待時間,再運行發現發現是五個線程併發執行的結果,後來我才明白,我創建線程1,然後啓動線程1,這時候再去循環創建線程2,再去啓動線程2,其實我在創建線程2的時候可能線程1已經執行完了,因爲實例化線程也是要時間的,所以我這邊看到以爲每次都是串行執行的,其實不是!

'''
減1的函數,初始賦值100,最後sum值爲隨機
每次最多五個線程同時執行
'''
#生成全局鎖
semaphore = threading.BoundedSemaphore(5)
def subtraction(n):
    global sum
    #運行之前加鎖
    semaphore.acquire()
    print("get lock: %s \n" %sum )
    #此時加等待拖延線程執行時間
    time.sleep(1)
    tmp=sum
    sum=tmp-1
    print("run the thread: %s\n" %n)
    # 運行之後解鎖
    semaphore.release()

sum=100
if __name__ == '__main__':
    thread=[]
    #依次啓動24個線程
    for x in range(12):
        t=threading.Thread(target=subtraction,args=(x,))
        thread.append(t)
        t.start()
    #每一個子進程執行完畢才執行主線程
    for x in thread:
        t.join()
    print('sum = ',sum)
#只是加了一個線程等待時間,就證明了之前想的是對的

運行結果:

加了等待時間會發現,第一次進來的五個線程,共享的sum值都是100

第五:線程中隊列

業務場景:某麪包店推出營銷策略,說每天早上的第一個蛋糕打3折,那麼好多人早上都去搶購那第一個蛋糕

1.多線程的方式去刪除列表中的第一個元素(搶購第一個蛋糕)

'''
用多線程刪除表中的第一個元素
'''
list = ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
def MyRemove(x):
    #獲取列表第一個個元素
    sale = x[0]
    print('被刪除的元素爲:',sale)
    time.sleep(0.001)
    x.remove(sale)


if __name__ == '__main__':
    th = []
    for i in range(3):
        t = threading.Thread(target=MyRemove, args=(list,))
        th.append(t)
        print('當前線程爲:',t.getName())
        t.start()
    for i in th:
        i.join()
    print('沒有被刪除的列表元素爲:', list)

'''
當前線程爲: Thread-1
被刪除的元素爲: bread1當前線程爲: 
Thread-2
被刪除的元素爲: bread1
當前線程爲: Thread-3
被刪除的元素爲: bread1
Traceback (most recent call last):
  File "D:/tests/th.py", line 25, in <module>
    x.join()
NameError: name 'x' is not defined
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python28\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:\Python28\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:/tests/th.py", line 14, in MyRemove
    x.remove(sale)
ValueError: list.remove(x): x not in list

Exception in thread Thread-2:
Traceback (most recent call last):
  File "C:\Python28\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:\Python28\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "D:/tests/th.py", line 14, in MyRemove
    x.remove(sale)
ValueError: list.remove(x): x not in list

'''

運行結果分析:可以看到線程1和線程2都報異常,因爲他們去刪除這個元素的時候,該元素已經被線程3刪除了,故他們找不到這個元素,所以發生異常了,這也體現了線程無序併發執行,不過可以用線程鎖去實現,

2.Queue線程隊列

Queue運行的三大特徵:

  • 先進先出(FIFO)

         Q=queue.Queue()
         Q.put(maxsize)

  • 先進後出(LIFO)

        Q=queue.LifoQueue()
        Q.put(maxsize)

  • 按照優先級進出

        Q = queue.PriorityQueue()

       Q.put(list)list長度至少爲2,第一個元素表示優先級,第二個元素表示存放的對應的值,如:Q.put([]1,23)

      2.1:先進先出的原則去操作列表(相當於線程鎖,不顧這裏執行的線程順序一定是12345。。。這樣,但是線程中互斥鎖就不一定了,只是說同時間只能有一個線程在執行,只有等該線程執行完成之後,才能釋放鎖給其他線程操作)

'''
用隊列的方式依次去刪除列表的第一個元素
'''
list = ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
def MyRemove(x):
    #獲取列表第一個個元素
    sale = x[0]
    print('被刪除的元素爲:',sale)
    time.sleep(0.001)
    x.remove(sale)


if __name__ == '__main__':
    print('初始化隊列元素爲:',list)
    Q = queue.Queue()
    for i in range(3):
        t = threading.Thread(target=MyRemove, args=(list,))
        #每次創建一個線程都加到隊列Q裏面
        Q.put(t)
    while not Q.empty():
        run_th = Q.get()
        print('running thread:',run_th.getName())
        run_th.run()
    print('沒有被刪除的列表元素爲:', list)

'''
初始化隊列元素爲: ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
running thread: Thread-1
被刪除的元素爲: bread1
running thread: Thread-2
被刪除的元素爲: bread2
running thread: Thread-3
被刪除的元素爲: bread3
沒有被刪除的列表元素爲: ['bread4', 'bread5']
'''

      2.2:先進後出的方式執行

結果可以看出線程3是最後進到隊列裏面的,但是確實最先執行的

初始化隊列元素爲: ['bread1', 'bread2', 'bread3', 'bread4', 'bread5']
running thread: Thread-3
被刪除的元素爲: bread1
running thread: Thread-2
被刪除的元素爲: bread2
running thread: Thread-1
被刪除的元素爲: bread3
沒有被刪除的列表元素爲: ['bread4', 'bread5']

      2.3:優先級執行

if __name__=='__main__':
    Q = queue.PriorityQueue()
    Q.put([4, 'zhu'])
    Q.put([8, 'xiao'])
    Q.put([1, 'pi'])

    while not Q.empty():
        run_th = Q.get()
        print('最後結果:',run_th)
'''
最後結果: [1, 'pi']
最後結果: [4, 'zhu']
最後結果: [8, 'xiao']
'''

 

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