並發編程之多線程
閱讀目錄
一: 什麽是線程
二: 多線程的特點,為什麽用多線程
三: 開啟線程,threading模塊
四: python GIL
什麽是線程
在傳統操作系統中,每個進程有一個地址空間,而且默認就有一個控制線程
線程顧名思義,就是一條流水線工作的過程,一條流水線必須屬於一個車間,一個車間的工作過程是一個進程
車間負責把資源整合到一起,是一個資源單位,而一個車間內至少有一個流水線
流水線的工作需要電源,電源就相當於cpu
所以,進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上的執行單位。
多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間,相當於一個車間內有多條流水線,都共用一個車間的資源。
例如,北京地鐵與上海地鐵是不同的進程,而北京地鐵裏的13號線是一個線程,北京地鐵所有的線路共享北京地鐵所有的資源,比如所有的乘客可以被所有線路拉。
線程的特點,為什麽用多線程
多線程指的是,在一個進程中開啟多個線程,簡單的講:如果多個任務共用一塊地址空間,那麽必須在一個進程內開啟多個線程。詳細的講分為4點:
1. 多線程共享一個進程的地址空間
2. 線程比進程更輕量級,線程比進程更容易創建和撤銷,在許多操作系統中,創建一個線程比創建一個進程要快10-100倍,在有大量線程需要動態和快速修改時,這一特性很有用
3. 若多個線程都是cpu密集型的,那麽並不能獲得性能上的增強,但是如果存在大量的計算和大量的I/O處理,擁有多個線程允許這些活動彼此重疊運行,從而會加快程序執行的速度。
4. 在多cpu系統中,為了最大限度的利用多核,可以開啟多個線程,比開進程開銷要小的多。(這一條並不適用於python)
開啟線程 threading模塊
1.開啟線程的兩種方式
#方式一 from threading import Thread import time def sayhi(name): time.sleep(2) print(‘%s say hello‘ %name) if __name__ == ‘__main__‘: t=Thread(target=sayhi,args=(‘egon‘,)) t.start()方式一print(‘主線程‘)
#方式二 from threading import Thread import time class Sayhi(Thread): #繼承父類Thread def __init__(self,name): #可以定義自己的初始屬性 super().__init__() #繼承父類的__init__方法 self.name=name def run(self): #開啟線程執行run裏的內容 time.sleep(2) print(‘%s say hello‘ % self.name) if __name__ == ‘__main__‘: t = Sayhi(‘egon‘) t.start() print(‘主線程‘)方式二
2.在一個進程下開啟多個線程與在一個進程下開啟多個子進程的區別
1-開啟線程的速度更快
2-在主進程下開啟多個線程,每個線程都跟主進程的pid一樣
from threading import Thread from multiprocessing import Process import os def work(): print(‘hello‘,os.getpid()) if __name__ == ‘__main__‘: #part1:在主進程下開啟多個線程,每個線程都跟主進程的pid一樣 t1=Thread(target=work) t2=Thread(target=work) t1.start() t2.start() print(‘主線程/主進程pid‘,os.getpid()) #part2:開多個進程,每個進程都有不同的pid p1=Process(target=work) p2=Process(target=work) p1.start() p2.start() print(‘主線程/主進程pid‘,os.getpid())pid的不同
3-同一進程內的線程共享該進程的數據
from threading import Thread from multiprocessing import Process import os def work(): global n n=0 if __name__ == ‘__main__‘: # n=100 # p=Process(target=work) # p.start() # p.join() # print(‘主‘,n) #毫無疑問子進程p已經將自己的全局的n改成了0,但改的僅僅是它自己的,查看父進程的n仍然為100 n=1 t=Thread(target=work) t.start() t.join() print(‘主‘,n) #查看結果為0,因為同一進程內的線程之間共享進程內的數據同一進程內的線程共享該進程的數據
3.實例
1-實例一 :多線程並發的套接字通訊
#_*_coding:utf-8_*_ #!/usr/bin/env python import multiprocessing import threading import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind((‘127.0.0.1‘,8080)) s.listen(5) def action(conn): while True: data=conn.recv(1024) print(data) conn.send(data.upper()) if __name__ == ‘__main__‘: while True: conn,addr=s.accept() p=threading.Thread(target=action,args=(conn,)) p.start()多線程並發的socket服務端
#_*_coding:utf-8_*_ #!/usr/bin/env python import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((‘127.0.0.1‘,8080)) while True: msg=input(‘>>: ‘).strip() if not msg:continue s.send(msg.encode(‘utf-8‘)) data=s.recv(1024) print(data)客戶端
2-實例二 :三個任務,一個接收用戶輸入,一個將用戶輸入的內容格式化成大寫,一個將格式化後的結果存入文件
from threading import Thread msg_l=[] format_l=[] def talk(): while True: msg=input(‘>>: ‘).strip() if not msg:continue msg_l.append(msg) def format_msg(): while True: if msg_l: res=msg_l.pop() format_l.append(res.upper()) def save(): while True: if format_l: with open(‘db.txt‘,‘a‘,encoding=‘utf-8‘) as f: res=format_l.pop() f.write(‘%s\n‘ %res) if __name__ == ‘__main__‘: t1=Thread(target=talk) t2=Thread(target=format_msg) t3=Thread(target=save) t1.start() t2.start() t3.start()View Code
4.線程相關的其他方法
Thread實例對象的方法 # isAlive(): 返回線程是否活動的。 # getName(): 返回線程名。 # setName(): 設置線程名。 threading模塊提供的一些方法: # threading.currentThread(): 返回當前的線程變量。 # threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啟動後、結束前,不包括啟動前和終止後的線程。 # threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果。
from threading import Thread,current_thread,enumerate,activeCount import os,time n=100 def work(): print(‘%s is ruuning‘ %current_thread().getName()) if __name__ == ‘__main__‘: t=Thread(target=work) t.start() t.join() print(t.is_alive()) #判斷線程是否存貨 print(t.getName()) #獲得線程名字 print(current_thread().getName()) #獲取主線程名字 print(enumerate()) #返回一個列表,有哪些活躍的線程 print(activeCount()) #活躍線程的數量 print(‘主線程‘,n)示例
主線程等待子線程結束
from threading import Thread import time def sayhi(name): time.sleep(2) print(‘%s say hello‘ %name) if __name__ == ‘__main__‘: t=Thread(target=sayhi,args=(‘egon‘,)) t.start() t.join() print(‘主線程‘) print(t.is_alive()) ‘‘‘ egon say hello 主線程 False ‘‘‘View Code
5.守護線程
無論是進程還是線程,都遵循:守護xxx會等待主xxx運行完畢後被銷毀
需要強調的是:運行完畢並非終止運行
#1.對主進程來說,運行完畢指的是主進程代碼運行完畢 #2.對主線程來說,運行完畢指的是主線程所在的進程內所有非守護線程統統運行完畢,主線程才算運行完畢
詳細解釋:
#1 主進程在其代碼結束後就已經算運行完畢了(守護進程在此時就被回收),然後主進程會一直等非守護的子進程都運行完畢後回收子進程的資源(否則會產生僵屍進程),才會結束, #2 主線程在其他非守護線程運行完畢後才算運行完畢(守護線程在此時就被回收)。因為主線程的結束意味著進程的結束,進程整體的資源都將被回收,而進程必須保證非守護線程都運行完畢後才能結束。
from threading import Thread import time def sayhi(name): time.sleep(2) print(‘%s say hello‘ %name) if __name__ == ‘__main__‘: t=Thread(target=sayhi,args=(‘egon‘,)) t.daemon = True #必須在t.start()之前設置 t.start() print(‘主線程‘) print(t.is_alive()) ‘‘‘ 主線程 True ‘‘‘
from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") t1=Thread(target=foo) #設置線程 t2=Thread(target=bar) #設置線程 t1.daemon=True #把t1設置成守護線程 t1.start() #開啟線程 t2.start() #開啟線程 print("main-------") # 打印結果 # 123 # 456 # main------- # end123 #主線程必須在所有非守護線程結束後才結束,主線程運行完後就不再等守護線程結束,因為主線程結束時守護線程已經結束,所以先打印end123,再打印end456 # end456迷惑人的例子
6.Python GIL(Global Interpreter Lock)
>>>詳細介紹
並發編程之多線程