进程,线程,协程
目錄
- 進程
- 線程
- 協程
進程
進程的定義
- 進程是資源分配的最小單位
- 一個運行起來的程序就是一個進程
進程的注意點
多道編程:在計算機內存中同時存放幾道相互獨立的程序,共享系統資源,相互穿插運行
單道編程:計算機內存中只允許一個程序運行
進程并發性:
- 在一個系統中,同時會存在多個進程被加載到內存中,同處于開始到結束之間的狀態
- 對于一個單CPU系統來說,程序同時處于運行狀態只是一種宏觀上的概念,他們雖然都已經開始運行,但就微觀而言,任意時刻,CPU上運行的程序只有一個
- 由于操作系統分時,讓每個進程都覺得自己獨占CPU等資源
注:如果是多核CPU(處理器)實際上是可以實現真正意義的同一時間點有多個線程同時運行
注意:進程具有獨立的內存空間,所以互相之間不能訪問對方數據
進程間互相訪問數據的四種方式:
- 利用Queues實現父進程到子進程的數據傳遞(父子進程通信)
- 使用管道pipe實現同一程序下兩個進程通信
- Managers實現同一程序下多個進程通信
- 借助redis,Rabbit MQ中間件進行數據傳遞(不同進程間通信)
進程池
為什么需要進程池:
- 一次性開啟指定數量的進程
- 防止進程開啟數量過多導致服務器壓力過大
進程和程序的區別
- 程序是一個機器代碼指令和數據的集合,所以程序是一個靜態的實體
- 進程是程序運行在數據集上的動態過程
- 進程是系統進行資源分配和調度的一個獨立單位
- 一個程序對應多個進程,一個進程為多個程序服務
- 一個程序執行在不同的數據集上就成為不同的進程,可以用進程控制塊來唯一標識每個進程
有了進程為什么還要線程?
進程優點:
提供了多道編程,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機的利用率
進程的兩個重要缺點
- 進程只能在一個時間干一件事,如果想同時干兩件事或多件事,進程就無能為力了。
- 進程在執行的過程中如果阻塞,即使進程中有些工作不依賴于輸入的數據,也將無法執行(例如等待輸入,整個進程就會掛起)。
線程
線程的定義
- 線程是操作系統的調度的最小單位
- 線程是進程的真正的執行者,是一些指令的集合(進程資源的擁有者)
- 同一進程下的讀多個線程共享內存空間,數據直接訪問(數據共享)
- 為了保證數據安全,必須使用線程鎖
線程的注意點
GIL全局解釋器鎖
-
在python全局解釋器下,保證同一時間僅有一個線程對資源操作
-
防止多個線程都修改數據
線程鎖(互斥鎖)
- 當一個線程對某個資源進行CPU計算的操作時加一個線程鎖,只有當前線程計算完成主動釋放鎖,其他線程才能對其操作
- 可以解決還未計算完成,釋放GIL鎖后其他線程對這個資源操作導致混亂的問題
- 線程鎖本質把線程中的數據加了一把互斥鎖
- mysql中共享鎖 & 互斥鎖
- mysql共享鎖:共享鎖,所有線程都能讀,而不能寫
- mysql排它鎖:排它,任何線程讀取這個這個數據的權利都沒有
- 加上線程鎖之后所有其他線程,讀都不能讀這個數據
線程和進程的區別
- 進程包含線程
- 線程共享內存空間
- 進程內存是獨立的(不可相互訪問)
- 進程可以生成子進程,子進程之間不能互相訪問
- 線程可以幫助應用程序同時做幾件事情
for循環同時啟動多個線程
import threading import time def sayhi(num): #定義每個線程要運行的函數print("running on number:%s" %num)time.sleep(3) for i in range(10):t = threading.Thread(target=sayhi,args=('t-%s'%i,))t.start()join()實現所有線程都執行結束后再執行主線程
import threading import time start_time = time.time()def sayhi(num): #定義每個線程要運行的函數print("running on number:%s" %num)time.sleep(3) t_objs = [] #將進程實例對象存儲在這個列表中 for i in range(50):t = threading.Thread(target=sayhi,args=('t-%s'%i,))t.start() #啟動一個線程,程序不會阻塞t_objs.append(t) print(threading.active_count()) #打印當前活躍進程數量 for t in t_objs: #利用for循環等待上面50個進程全部結束t.join() #阻塞某個程序 print(threading.current_thread()) #打印執行這個命令進程print("----------------all threads has finished.....") print(threading.active_count()) print('cost time:',time.time() - start_time)setDaemon()守護線程,主線程退出時,需要子線程隨主線程退出
import threading import time start_time = time.time() def sayhi(num): #定義每個線程要運行的函數print("running on number:%s" %num)time.sleep(3) for i in range(50):t = threading.Thread(target=sayhi,args=('t-%s'%i,))t.setDaemon(True) #把當前線程變成守護線程,必須在t.start()前設置t.start() #啟動一個線程,程序不會阻塞 print('cost time:',time.time() - start_time)線程實現并發
import requests from concurrent.futures import ThreadPoolExecutordef fetch_request(url):result = requests.get(url)print(result.text)url_list = ['https://www.baidu.com','https://www.google.com/', #google頁面會卡住,知道頁面超時后這個進程才結束'http://dig.chouti.com/', #chouti頁面內容會直接返回,不會等待Google頁面的返回 ]pool = ThreadPoolExecutor(10) # 創建一個線程池,最多開10個線程 for url in url_list:pool.submit(fetch_request,url) # 去線程池中獲取一個線程,線程去執行fetch_request方法pool.shutdown(True)協程
協程的定義
- 協程又稱微線程,是一種用戶狀態的輕量級線程
- 協程的本質是個單線程
- 協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧
- 協程主要作用是在單線程的條件下實現并發的效果,但實際上還是串行
協程的缺點(無法利用多核資源)
- 無法利用多核資源,協程的本質是個單線程,不能同時將單個CPU的多個核上,協程需要和進程配合才能運行在多CPU上
- 線程阻塞(Blocking)操作(如IO時)會阻塞掉整個程序
協程的優點
- 可以處理高并發
- 節省資源
- 無需線程上下文切換的開銷(可以理解為協程切換就是在不同函數間切換,不用像線程那樣切換上下文CPU)
- 用法: 最簡單的方法是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的性能。
協程為何能處理大并發
- greeenlet遇到I/O手動切換
協程遇到I/O操作就切換,其實Gevent模塊僅僅是對greenlet的封裝,將I/O的手動切換變成自動切換
greeenlet遇到I/O手動切換
from greenlet import greenletdef test1():print(12) #4 gr1會調用test1()先打印12gr2.switch() #5 然后gr2.switch()就會切換到gr2這個協程print(34) #8 由于在test2()切換到了gr1,所以gr1又從上次停止的位置開始執行gr2.switch() #9 在這里又切換到gr2,會再次切換到test2()中執行def test2():print(56) #6 啟動gr2后會調用test2()打印56gr1.switch() #7 然后又切換到gr1print(78) #10 切換到gr2后會接著上次執行,打印78gr1 = greenlet(test1) #1 啟動一個協程gr1 gr2 = greenlet(test2) #2 啟動第二個協程gr2 gr1.switch() #3 首先gr1.switch() 就會去執行gr1這個協程- Gevent遇到I/O自動切換
Gevent是一個第三方庫,可以通過gevent實現并發同步或異步編程,Gevent原理是只要是遇到I/O操作就自動切換下一個協程
使用Gevent實現并發下載網頁與串行下載網頁時間比較
from urllib import request import gevent,time from gevent import monkey monkey.patch_all() #把當前程序所有的I/O操作給我單獨做上標記def f(url):print('GET: %s' % url)resp = request.urlopen(url)data = resp.read()print('%d bytes received from %s.' % (len(data), url))#1 并發執行部分 time_binxing = time.time() gevent.joinall([gevent.spawn(f, 'https://www.python.org/'),gevent.spawn(f, 'https://www.yahoo.com/'),gevent.spawn(f, 'https://github.com/'), ]) print("并行時間:",time.time()-time_binxing)#2 串行部分 time_chuanxing = time.time() urls = ['https://www.python.org/','https://www.yahoo.com/','https://github.com/',] for url in urls:f(url) print("串行時間:",time.time()-time_chuanxing)# 注:為什么要在文件開通使用monkey.patch_all() # 1. 因為有很多模塊在使用I / O操作時Gevent是無法捕獲的,所以為了使Gevent能夠識別出程序中的I / O操作。 # 2. 就必須使用Gevent模塊的monkey模塊,把當前程序所有的I / O操作給我單獨做上標記 # 3.使用monkey做標記僅用兩步即可:第一步(導入monkey模塊): from gevent import monkey第二步(聲明做標記) : monkey.patch_all()Gevent實現簡單的自動切換小例子
注:在Gevent模仿I/O切換的時候,只要遇到I/O就會切換,哪怕gevent.sleep(0)也要切換一次
import geventdef func1():print('\033[31;1m第一次打印\033[0m')gevent.sleep(2) # 為什么用gevent.sleep()而不是time.sleep()因為是為了模仿I/Oprint('\033[31;1m第六次打印\033[0m')def func2():print('\033[32;1m第二次打印\033[0m')gevent.sleep(1)print('\033[32;1m第四次打印\033[0m')def func3():print('\033[32;1m第三次打印\033[0m')gevent.sleep(1)print('\033[32;1m第五次打印\033[0m')gevent.joinall([ # 將要啟動的多個協程放到event.joinall的列表中,即可實現自動切換gevent.spawn(func1), # gevent.spawn(func1)啟動這個協程gevent.spawn(func2),gevent.spawn(func3), ])# 運行結果: # 第一次打印 # 第二次打印 # 第三次打印 # 第四次打印 # 第五次打印 # 第六次打印總結
- 上一篇: 时间戳服务——信息安全(二)
- 下一篇: 装饰器,生成器,迭代器