Python-多工總結
阿新 • • 發佈:2021-01-09
文章目錄
Python-多工
多工
什麼是多工
- 多工就是可以同時做多件事情就叫多工
多工理解
- 併發:CPU小於當前的執行的任務,是假的多執行緒
- 並行:CPU大於當前的執行的任務,是真的多執行緒
執行緒
執行緒介紹
- 執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程序之中,是程序中的實際運作單位
- 匯入:
import threading
執行緒方法
start
: 啟動執行緒- 該執行緒創建出來為主執行緒
- 使用start會等子執行緒執行結束,然後主執行緒才結束
setDaemon
: 守護執行緒- 不會等子執行緒執行結束
- 當主執行緒結束就直接結束
join
: 等待子執行緒執行結束- 放在start後使用
- 當任務中存在多執行緒呼叫時,會先等子執行緒執行結束在去執行另外一個子執行緒
threading.enumerate
: 檢視建立的執行緒- 注意:主執行緒也算在內
- 是在start之後創建出來的子執行緒
使用執行緒完成多工
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2020/12/28
"""
總結如下:
執行緒是在start執行之後建立
start : 啟動執行緒
target : 目標
setDaemon : 守護執行緒
join : 執行緒等待
"""
import threading
import time
def sing():
for i in range(5):
print('我正在唱歌')
time. sleep(1)
def dance(): # 子執行緒
for i in range(5):
print('我正在跳舞')
time.sleep(1)
# def demo():
# print('Hello world')
if __name__ == '__main__':
# target: 目標,呼叫的物件,
th = threading.Thread(target=sing)
th1 = threading.Thread(target=dance)
# print(threading.enumerate())
# setDaemon: 守護執行緒,不會等子執行緒執行結束
# th.setDaemon(True)
# th1.setDaemon(True)
# 啟動執行緒
th.start() # 主執行緒,主執行緒會等到子執行緒執行結束
th1.start()
print(threading.enumerate())
# 等待子執行緒執行結束
# th.join()
# th1.join()
# 檢視執行緒數量
# 為什麼結果卻為3個呢
# 因為還有一個主執行緒在內,存在兩個子執行緒
print(threading.enumerate())
類的方式建立執行緒
- 當想用類的方式建立的時候,需要在類中即成
thread.Thread
(看匯入的情況而定) - 如果想進行傳參的話,需要使用到
super
函式來而定 - 當想在類中使用多執行緒,要在類中的
run
方法中實現 - 通過
start
來啟動多執行緒
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2020/12/28
import threading
import time
class Animal(threading.Thread):
def run(self) -> None:
print('this is animal')
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
r = Animal()
r.start()
print(threading.enumerate())
多執行緒共享全域性變數
- 執行緒之間可以共享全域性變數,但是會出現計算出錯。可通過鎖來解決這麼一系列問題
- 互斥鎖保證了每次只有一個執行緒進行寫入操作,從而保證了多執行緒情況下資料的正確性
- 多執行緒之間傳參可通過args來進行
mutex = threading.Lock()
: 建立鎖mutex.acquire()
: 上鎖操作mutex.release()
: 解鎖操作
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2020/12/29
import threading
import time
import dis
num = 100
"""
首先要知道,Python中的多執行緒並非真正的多執行緒,
Python中的執行緒也並非採用單個執行來執行多執行緒,
而Python中的多執行緒採用的是時間,輪轉來執行,什麼意思呢?
意思就是當第一個執行緒執行的差不多了,然後把一個執行緒扔出去,讓第一個執行緒等著,
在把第二個執行緒繼續執行,執行的差不多的時候,在把第二個執行緒扔出去,讓第二個執行緒等著,
所以這就出現了執行緒之間的共享全域性變數的一個問題,如何解決這個問題呢。
可以通過鎖來解決這麼一個問題的出現
"""
import threading
# 建立鎖
mutex = threading.Lock()
def demo(nums):
global num
# 上鎖操作
mutex.acquire()
for i in range(nums):
num += 1
# 解鎖操作
mutex.release()
print(f'demo--{num}')
def demo1(nums):
global num
# 上鎖操作
mutex.acquire()
for i in range(nums):
num += 1
mutex.release()
print(f'demo1--{num}')
if __name__ == '__main__':
# 多執行緒之間傳參,可以通過args
# 傳入多引數要以元祖的形式
t = threading.Thread(target=demo, args=(1000000, ))
t1 = threading.Thread(target=demo1, args=(1000000,))
t.start()
t1.start()
time.sleep(2)
print(f'main--{num}')
# def funt(a):
# a += 1
# print(a)
#
# print(dis.dis(funt))
執行緒同步
- 執行緒同步我們需要使用
threading.Condition()
完成執行緒同步
# 執行緒同步
cond = threading.Condition()
# 等待
cond.wait()
# 喚醒
cond.notify()
實現效果:
- 天貓精靈:小愛同學
- 小愛同學:在
- 天貓精靈:現在幾點了?
- 小愛同學:你猜猜現在幾點了
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2020/12/29
import threading
"""
天貓精靈:小愛同學
小愛同學:在
天貓精靈:現在幾點了?
小愛同學:你猜猜現在幾點了
"""
# 執行緒同步
cond = threading.Condition()
class Tmall(threading.Thread):
def __init__(self, name):
super(Tmall, self).__init__(name=name)
def run(self) -> None:
cond.acquire() # 上鎖操作
print(f'{self.name} : 小愛同學')
cond.wait()
print(f'{self.name} : 現在幾點了?')
cond.notify()
# 解鎖操作
cond.release()
class love(threading.Thread):
def __init__(self, name):
super(love, self).__init__(name=name)
def run(self) -> None:
cond.acquire()
print(f'{self.name} : 在')
cond.notify()
cond.wait()
print(f'{self.name} : 你猜猜現在幾點了')
cond.release()
if __name__ == '__main__':
tianmao = Tmall(name='天貓精靈')
xiaoai = love(name='小愛同學')
tianmao.start()
xiaoai.start()
程序
程序定義
- 程序是計算機中的程式關於某資料集合上的一次運動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。並且程序是執行緒的容器。
程序概念
- 程序是一個實體。每一個程序都有它自己的地址空間。
- 程序是一個執行中的程式
- 程序是作業系統中最基本、重要的概念
- 任務排程和執行的基本單位
- 多個程序同時執行的順序是隨機的
程序與程式區別
- 程序:正在執行的程式。動態的,暫時的
- 程式:沒有執行的程式碼,是一個靜態的,永久的
程序方法
- 通過
multiprocessing.Process
模組 group
: 引數未使用,預設值為Nonetarget
: 表示呼叫物件,即子執行緒要執行的任務args
: 表示呼叫的位置引數元祖kwargs
: 表示呼叫物件的字典name
: 子程序名稱
使用程序完成多工
- 注意: 多執行緒同時執行的順序是隨機的
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2020/12/31
"""
總結如下:
什麼是多執行緒?
多執行緒就是相當於就是作業系統執行多個任務,
程序是執行緒的容器,一個程序最少要有一個執行緒
程序是正在執行的程式,動態的
"""
# 匯入程序
import multiprocessing
# 當想匯入程序佇列時,需要從multiprocessing匯入
# 安全佇列
from multiprocessing import queues
import time
def test():
while True:
print('1')
time.sleep(1)
def test1():
while True:
print('2')
time.sleep(1)
def test2():
while True:
print('3')
time.sleep(1)
if __name__ == '__main__':
# Process : 建立程序的方法
t = multiprocessing.Process(target=test)
t1 = multiprocessing.Process(target=test1)
t2 = multiprocessing.Process(target=test2)
# 啟動多執行緒
t.start()
t1.start()
t2.start()
print(4)
通過繼承Process類建立程序
class Demo(multiprocessing.Process):
def run(self):
while True:
print("--1--")
time.sleep(1)
if __name__ == '__main__':
p1 = Demo()
p1.start()
多程序共享全域性變數
- 注意⚠️: 多程序之間是不能進行共享全域性變數的,但想通過共享全域性變數,只能通過多程序的queue佇列(注意:這裡說的是多程序的佇列,而不是普通的佇列)
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2021/1/5
import multiprocessing
import time
"""
總結如下:
多程序之間是不能進行共享全域性變數的
"""
num = 100
def num1():
global num
num += 100
print(f'num1 for {num}') # 200
def num2():
print(f'num2 for {num}') # 100
if __name__ == '__main__':
m1 = multiprocessing.Process(target=num1)
m2 = multiprocessing.Process(target=num2)
# 啟動多程序
m1.start()
m2.start()
多程序共享全域性變數+多程序佇列
- 使用多程序佇列可以解決這麼個問題
q = multiprocessing.Queue()
: 建立程序佇列q.get()
: 從佇列取值q.put()
: 將一個數據存入佇列q.qsize()
: 返回佇列的大小q.empty()
: 判斷佇列是否為空q.full()
: 判斷佇列是否滿了
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2021/1/5
import multiprocessing
import time
"""
總結如下:
多程序之間是不能進行共享全域性變數的
當想進行共享全域性變數
"""
num = 100
def num1(q):
global num
num += 100
q.put(num) # 200
print(f'num1 for {num}') # 200
def num2(q):
# print(f'num2 for {num}') # 100
data = q.get() # 200
print(f'num2 for {data}')
if __name__ == '__main__':
q = multiprocessing.Queue()
m1 = multiprocessing.Process(target=num1, args=(q, ))
m2 = multiprocessing.Process(target=num2, args=(q, ))
# 啟動多程序
m1.start()
m2.start()
程序池
- 當需要建立的子程序數量不多時,可以直接利用
multiprocessing
中的Process動態生成多個程序,但是如果是上百甚至上千個目標,手動的去建立的程序的工作量巨大,此時就可以用到multiprocessing
模組提供的Pool
方法,也就是程序池。 apply_asyn
: 非阻塞,非同步形式close
: 關閉程序池,使其不在接受新的任務join
: 主程序阻塞,等待子程序的退出,join方法要在close後使用- 程序池之間通訊使用的是:
q = multiprocessing.Manager().Queue()
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2021/1/5
import multiprocessing
def demo1(q):
q.put("a")
def demo2(q):
data = q.get("a")
print(data)
if __name__ == '__main__':
# 當使用程序池的時候並不能使用程序使用的佇列
# 需要使用到程序池的佇列
q = multiprocessing.Manager().Queue()
# 建立程序池
po = multiprocessing.Pool(2)
# 非同步啟動,非阻塞
po.apply_async(demo1, args=(q, ))
po.apply_async(demo2, args=(q, ))
# 同步啟動,阻塞
# po.apply()
# 關閉程序池
po.close()
# 等待子程序池結束
# 呼叫join之前,先呼叫close函式,否則會出錯
# 執行完close後不會有新的程序加入到pool,join函式等待所有子程序結束
po.join()
多工資料夾複製
# -*- coding: utf-8 -*-
# @Author: Small-J
# @Date : 2021/1/5
"""
獲取使用者要複製的資料夾名字
建立一個新的資料夾
獲取資料夾所有待拷貝的檔名字
建立程序池
新增拷貝任務
"""
import multiprocessing
import os
import time
def copy_file(old_folder_name, new_folder_name, file_name, q):
"""複製檔案內容"""
with open(old_folder_name + '/' + file_name, 'rb')as f:
content = f.read()
with open(new_folder_name + '/' + file_name, 'wb')as f1:
f1.write(content)
def main():
# 獲取使用者要複製的資料夾名字
old_folder_name = input('請輸入你要建立的資料夾名字:')
# 建立一個新的資料夾
new_folder_name = old_folder_name + '[附件]'
if not os.path.exists(new_folder_name):
os.mkdir(new_folder_name)
# 獲取資料夾中所有的內容名字
file_name_list = os.listdir(old_folder_name)
# 建立程序池佇列
q = multiprocessing.Manager().Queue()
# 建立程序池
po = multiprocessing.Pool(2)
# 新增拷貝任務
for file_name in file_name_list:
po.apply_async(copy_file, args=(old_folder_name, new_folder_name, file_name, q))
# 關閉程序池
po.close()
# 等待子程序執行結束
po.join()
all_file_num = len(file_name_list)
copy_ok_num = 1
while True:
copy_ok_num += 1
time.sleep(1)
print("\r複製的進度為:%.2f %%" % (copy_ok_num * 100 / all_file_num), end='')
if copy_ok_num >= all_file_num:
break
if __name__ == '__main__':
main()
總結如下:
- 也存在著執行緒池和程序池
- IO密集型適用於多執行緒
- 計算密集型適用多程序
- 多執行緒無法利用多核CPU發揮優勢,會輪流使用CPU來發揮優勢
- 多程序是利用多核CPU發揮優勢
程序與執行緒區別
- 根本區別
- 程序: 作業系統資源分配的基本單位
- 執行緒: 任務呼叫和執行的基本單位
- 開銷
- 程序: 通過複製程式碼+資源建立子程序,每個程序都有獨立的程式碼和資料空間,程式之間的切換會有較大的開銷
- 執行緒: 在同一份程式碼裡,建立執行緒,共享記憶體,開銷較小。
- 分配記憶體
- 程序:系統在執行的時候為每個程序分配不同的記憶體空間
- 執行緒: 執行緒所使用的資源是它所屬的程序的資源
- 包含關係
- 程序: 一個程序可以擁有多個執行緒
- 執行緒: 執行緒是程序的一部分
協程
- 協程,又稱為微執行緒, 它是實現多工的另一種方式,只不過是比現場更小的執行單位。因為它自帶CPU的上下文,這樣只要在合適的時機,我們就可以把一個協程切換到另一個協程。
協程與執行緒差異
- 執行緒:每個執行緒都有自己換成Cache等等資料,作業系統還會做這些資料的恢復操作,所以執行緒的切換非常消耗效能
- 協程:單純的操作CPU的上下文,所以一秒切換上百萬次系統都能扛住。說喲完成多工效率比執行緒和程序都高
# 使用yield來實現協程
import time
def task():
while True:
print('--1--')
time.sleep(1)
yield 'task'
def task1():
while True:
print('--2--')
time.sleep(1)
yield 'task2'
def main():
t1 = task()
t2 = task1()
# print(t2) # 物件地址值
# print(t1)
# next(t1)
# next(t2)
while True:
next(t1)
next(t2)
if __name__ == '__main__':
main()
greenlet的使用
- greenlet也是實現協程的一種方式,但是它實現協程並不會手動的去切換,還需要手動的實現。
- 安裝:
pip install greenlet
# -*- coding: utf-8 -*-
# Author : Small-J
# 2021/1/8 11:44 上午
from greenlet import greenlet
import time
def demo1():
while True:
print("demo1")
gr2.switch()
time.sleep(0.5)
def demo2():
while True:
print("demo2")
# 通過switch到其他協程地方,該協程會被掛起
gr1.switch()
time.sleep(0.5)
if __name__ == '__main__':
gr1 = greenlet(demo1)
gr2 = greenlet(demo2)
gr1.switch()
gevent的使用
- 比greenlet更強大的並且能夠主動切換任務的模組gevent
- 安裝:
pip install gevent
- 原理:當一個greenlet遇到IO操作時,比如訪問網路,就主動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行
- 由於IO操作非常耗時,經常使程式處於等待狀態,有了genvet為我們自動切換協程,就保證總有greenlet在執行
- 在爬取圖片等操作的時候適用於領域
# -*- coding: utf-8 -*-
# Author : Small-J
# 2021/1/8 11:55 上午
import gevent
import time
# 破解time.sleep延遲補丁,除了time,各種延遲都適配
from gevent import monkey
# 補丁
monkey.patch_all()
"""
總結如下:
如果使用time.sleep就會變成同步的形式去執行,先是f1執行完之後在執行
但如果想time.sleep被識別的話,就需要匯入monkey模組
需要使用gevent.sleep來實現,為什麼需要呢?
因為需要一種延遲的效果來展示,如網路請求延遲也可以
"""
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
# gevent.sleep(0.5)
time.sleep(0.5)
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
# gevent.sleep(0.5)
time.sleep(0.5)
if __name__ == '__main__':
# 建立協程
# g1 = gevent.spawn(f1, 5)
# g2 = gevent.spawn(f2, 5)
# g1.join()
# g2.join()
# 簡化上面的操作
# greenlets, timeout=None, raise_error=False, count=None
gevent.joinall([
gevent.spawn(f1, 5),
gevent.spawn(f2, 5)
])
最後總結
- 程序是資源分配的單位
- 執行緒是作業系統排程的單位
- 程序切換需要的資源最大,效率很低
- 執行緒切換需要的資源一般,效率一般
- 協程切換任務資源很小,效率高
- 多執行緒、多程序根據CPU核數不一樣可能是並行的,但是協程是在一個執行緒中,所以是併發