Python第十二章-多進程和多線程02-多線程



接上一章,進程和線程之間可以存在哪些形式呢?

1 單進程單線程:一個人在一個桌子上吃菜。
2 單進程多線程:多個人在同一個桌子上一起吃菜。
3 多進程單線程:多個人每個人在自己的桌子上吃菜。

多線程的問題是多個人同時吃一道菜的時候容易發生爭搶,例如兩個人同時夾一個菜,一個人剛伸出筷子,結果伸到的時候已經被夾走菜了。。。此時就必須等一個人夾一口之后,在還給另外一個人夾菜,也就是說資源共享就會發生沖突爭搶。

二、線程

threading是用來提供在一個進程內實現多線程的編程模塊.

前面我們學習了多進程編程.

完成多任務, 也可以在一個進程內使用多個線程. 一個進程至少包括一個線程, 這個線程我們稱之為主線程. 在主線程中開啟的其他線程我們稱之為子線程.

一個進程內的所有線程之間可以直接共享資源, 所以線程間的通信要比進程間通信方便了很多.

python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用

單線程示例:

import time


def say_sorry():
    print("親愛的,我錯了,我能吃飯了嗎?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        say_sorry()

多線程示例:

import threading
import time


def say_sorry():
    print("親愛的,我錯了,我能吃飯了嗎?")
    time.sleep(1)


if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=say_sorry)
        t.start() #啟動線程,即讓線程開始執行

說明:

  1. 可以明顯看出使用了多線程並發的操作,花費時間要短很多
  2. 創建好的線程,需要調用start()方法來啟動

2.1 threading

Python3 線程中常用的兩個模塊為:

  • _thread

    thread 模塊已被廢棄。用戶可以使用 threading 模塊代替。所以,在 Python3 中不能再使用"thread" 模塊。為了兼容性,Python3 將 thread 重命名為 "_thread"

  • threading(推薦使用)

    threading用於提供線程相關的操作,線程是應用程序中工作的最小單元。

2.2 Thread類

Thread類表示在單獨的控制線程中運行的活動。有兩種方法可以指定這種活動:

2.2.1、給構造函數傳遞回調對象

mthread=threading.Thread(target=xxxx,args=(xxxx))
mthread.start()

2.2.2、在子類中重寫run() 方法  這里舉個小例子:

import threading
import time
class MyThread(threading.Thread):
    def __init__(self,arg):
        super(MyThread, self).__init__()#注意:一定要顯式的調用父類的初始化函數。
        self.arg=arg
    def run(self):#定義每個線程要運行的函數
        time.sleep(1)
        print('the arg is:%s\r' % self.arg)

for i in range(4):
    t =MyThread(i)
    t.start()

print('main thread end!')

2.2.3、多線程之間共享全局變量

​ 多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響, 而多線程中,所有變量都由所有線程共享,下面觀察一下兩條線程中共享同一份數據。

from threading import Thread
import time 

g_num = 100		# 定義全局變量g_num

def work1():
    num = 1		# 定義局部變量num
    global g_num		# 關鍵字global標記全局變量g_num
    for i in range(3):
        g_num += 1		# 更改全局變量的值
        print("---子線程1---work1函數---g_num:%d" % g_num)

def work2():
    num = 2
    global g_num		# 關鍵字global標記全局變量g_num
    print("\t---子線程2---work2函數---個g_num:%d" % g_num)


if __name__ == "__main__":
    print("啟動線程之前:g_num:%d" % g_num)

    t1 = Thread(target=work1)    # 創建t1子線程,分配任務work1
    t2 = Thread(target=work2)    # 創建t2子線程,分配任務work2
    t1.start()  	# 啟動t1線程

    time.sleep(1)	# 等待t1線程執行完畢,觀察t2線程中打印的全局變量是否發生改變
    t2.start() 		# 啟動t2線程

2.2.4、全局變量作為參數傳遞

將列表作為參數傳遞進來,在參數末尾追加元素

from threading import Thread
import time

g_list = [10, 20, 30]

def work1(list):
    for i in range(3):
        list.append(i)    # 更改參數的值,在列表末尾追加元素
        print("--子線程1--work1----num:", list)

def work2(list):
    print("\t--子線程2---work2---num:", list)

if __name__ == "__main__":
    print("主線程訪問g_list:" , g_list)

    t1 = Thread(target=work1, args=(g_list,))    # 創建線程t1 將g_list作為參數傳遞進去 執行函數work1
    t2 = Thread(target=work2, args=(g_list,))    # 創建線程t2 將g_list作為參數傳遞進去 執行函數work2
    t1.start()

    time.sleep(1)
    t2.start()

將列表作為參數傳遞進來,將參數重置

from threading import Thread
import time

g_list = [10, 20 ,30]

def work1(list):
    for i in range(3):
        # list.append(i)
        list = [1, 2, 3]	# 重置參數
        print("--子線程1--work1----num:", list)

def work2(list):
    print("\t--子線程2---work2---num:", list)

if __name__ == "__main__":
    print("主線程訪問g_list:" , g_list)

    t1 = Thread(target=work1, args=(g_list,))	# 創建線程t1 將g_list作為參數傳遞進去 執行函數work1
    t2 = Thread(target=work2, args=(g_list,))	# 創建線程t2 將g_list作為參數傳遞進去 執行函數work2
    t1.start()  

    time.sleep(1)
    t2.start()  

2.2.5、線程的鎖

​ 多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。

來看看多個線程同時操作一個變量怎么把內容給改亂了:

import time, threading

# 假定這是你的銀行存款:
balance = 0

def change_it(n):
    # 先存后取,結果應該為0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

if __name__ == "__main__":
	t1 = threading.Thread(target=run_thread, args=(5,))
	t2 = threading.Thread(target=run_thread, args=(8,))
	t1.start()
	t2.start()
    t1.join()
    t2.join()
	
	print(balance)

​ 每次執行的結果不一定是個啥,究其原因,是因為修改balance需要多條語句,而執行這幾條語句時,線程可能中斷,從而導致多個線程把同一個對象的內容改亂了。

​ 兩個線程同時一存一取,就可能導致余額不對,你肯定不希望你的銀行存款莫名其妙地變成了負數,所以,我們必須確保一個線程在修改balance的時候,別的線程一定不能改。

​ 如果我們要確保balance計算正確,就要給change_it()上一把鎖,當某個線程開始執行change_it()時,我們說,該線程因為獲得了鎖,因此其他線程不能同時執行change_it(),只能等待,直到鎖被釋放后,獲得該鎖以后才能改。由於鎖只有一個,無論多少線程,同一時刻最多只有一個線程持有該鎖,所以,不會造成修改的沖突。創建一個鎖就是通過threading.Lock()來實現:

#鎖的使用
mutex = threading.Lock()  #創建鎖
mutex.acquire([timeout])  #鎖定
mutex.release()  #釋放
import time, threading

balance = 0
lock = threading.Lock()

def change_it(n):
    # 先存后取,結果應該為0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        # 先要獲取鎖:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要釋放鎖:
            lock.release() 
            
if __name__ == "__main__":
	t1 = threading.Thread(target=run_thread, args=(5,))
	t2 = threading.Thread(target=run_thread, args=(8,))
	t1.start()
	t2.start()
    t1.join()
    t2.join()
	
	print(balance)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM