詳解Python中的多線程
什么是多線程:
進(jìn)程:正在運(yùn)行的程序,QQ 360 ......
線程:就是進(jìn)程中一條執(zhí)行程序的執(zhí)行路徑,一個(gè)程序至少有一條執(zhí)行路徑。(360中的殺毒 電腦體檢 電腦清理 同時(shí)運(yùn)行的話就需要開(kāi)啟多條路徑)
每個(gè)線程都有自己需要運(yùn)行的內(nèi)容,而這些內(nèi)容可以稱為線程要執(zhí)行的任務(wù)。
開(kāi)啟多線程是為了同時(shí)運(yùn)行多部分代碼。
好處:解決了多部分需要同時(shí)運(yùn)行的問(wèn)題
弊端:如果線程過(guò)多,會(huì)導(dǎo)致效率很低(因?yàn)槌绦虻膱?zhí)行都是CPU做著隨機(jī) 快速切換來(lái)完成的)
- 線程與進(jìn)程的區(qū)別
- 線程共享內(nèi)存,進(jìn)程獨(dú)立內(nèi)存
- 線程啟動(dòng)速度塊,進(jìn)程啟動(dòng)速度慢,運(yùn)行時(shí)速度沒(méi)有可比性
- 同一個(gè)進(jìn)程的線程間可以直接交流,兩個(gè)進(jìn)程想通信,必須通過(guò)一個(gè)中間代理來(lái)實(shí)現(xiàn)
- 創(chuàng)建新線程很簡(jiǎn)單,創(chuàng)建新進(jìn)程需要對(duì)其父進(jìn)程進(jìn)行一次克隆
- 一個(gè)線程可以控制和操作同一線程里的其他線程,但是進(jìn)程只能操作子進(jìn)程
threading模塊
多線程的使用方式一:直接使用
# -*- coding:utf-8 -*- # 線程使用的方式一 import threading import time # 需要多線程運(yùn)行的函數(shù) def fun(args): print("我是線程%s" % args) time.sleep(2) print("線程%s運(yùn)行結(jié)束" % args) # 創(chuàng)建線程 t1 = threading.Thread(target=fun, args=(1,)) t2 = threading.Thread(target=fun, args=(2,)) start_time = time.time() t1.start() t2.start() end_time = time.time() print("兩個(gè)線程一共的運(yùn)行時(shí)間為:", end_time-start_time) print("主線程結(jié)束") """ 運(yùn)行結(jié)果: 我是線程1 我是線程2兩個(gè)線程一共的運(yùn)行時(shí)間為: 0.0010077953338623047 主線程結(jié)束 線程1運(yùn)行結(jié)束 線程2運(yùn)行結(jié)束 """
線程的第二種使用方式:繼承式調(diào)用
# 繼承式調(diào)用 import threading import time class MyThreading(threading.Thread): def __init__(self, name): super(MyThreading, self).__init__() self.name = name # 線程要運(yùn)行的代碼 def run(self): print("我是線程%s" % self.name) time.sleep(2) print("線程%s運(yùn)行結(jié)束" % self.name) t1 = MyThreading(1) t2 = MyThreading(2) start_time = time.time() t1.start() t2.start() end_time = time.time() print("兩個(gè)線程一共的運(yùn)行時(shí)間為:", end_time-start_time) print("主線程結(jié)束") """ 運(yùn)行結(jié)果: 我是線程1 我是線程2 兩個(gè)線程一共的運(yùn)行時(shí)間為: 0.0010724067687988281 主線程結(jié)束 線程2運(yùn)行結(jié)束 線程1運(yùn)行結(jié)束 """
守護(hù)線程與join方法
- 在Python多線程編程中,join方法的作用式線程同步。
- 守護(hù)線程,是為守護(hù)別人而存在的,當(dāng)設(shè)置為守護(hù)線程后,被守護(hù)的主線程不存在后,守護(hù)線程也自然不存在。
- 第一種:python多線程默認(rèn)情況
- Python多線程默認(rèn)情況(設(shè)置線程setDaemon(False)),主線程執(zhí)行完自己的任務(wù)后,就退出了,此時(shí)子線程會(huì)繼續(xù)執(zhí)行自己的任務(wù),直到子線程任務(wù)結(jié)束
- 代碼演示:threading中的兩個(gè)創(chuàng)建多線成的例子都是。
- 第二種:開(kāi)啟守護(hù)線程
- 開(kāi)啟線程的setDaemon(True)),設(shè)置子線程為守護(hù)線程,實(shí)現(xiàn)主程序結(jié)束,子程序立馬全部結(jié)束功能
- 代碼演示:
# 守護(hù)線程 import threading import time class MyThreading(threading.Thread): def __init__(self, name): super(MyThreading, self).__init__() self.name = name # 線程要運(yùn)行的代碼 def run(self): print("我是線程%s" % self.name) time.sleep(2) print("線程%s運(yùn)行結(jié)束" % self.name) t1 = MyThreading(1) t2 = MyThreading(2) start_time = time.time() t1.setDaemon(True) t1.start() t2.setDaemon(True) t2.start() end_time = time.time() print("兩個(gè)線程一共的運(yùn)行時(shí)間為:", end_time-start_time) print("主線程結(jié)束")
- 注意:如果要設(shè)置為守護(hù)線程,一定要在開(kāi)啟線程之前,將該線程設(shè)置為守護(hù)線程
- 結(jié)論:主線程結(jié)束后,無(wú)論子線程1,2是否運(yùn)行完成,都結(jié)束線程,不在繼續(xù)向下運(yùn)行
- 第三種:加入join方法設(shè)置同步
- 當(dāng)不給程序設(shè)置守護(hù)進(jìn)程時(shí),主程序?qū)⒁恢钡却映绦蛉窟\(yùn)行完成才結(jié)束
- 代碼演示:
# join:線程同步 import threading import time class MyThreading(threading.Thread): def __init__(self, name): super(MyThreading, self).__init__() self.name = name # 線程要運(yùn)行的代碼 def run(self): print("我是線程%s" % self.name) time.sleep(3) print("線程%s運(yùn)行結(jié)束" % self.name) threading_list = [] start_time = time.time() for x in range(50): t = MyThreading(x) t.start() threading_list.append(t) for x in threading_list: x.join() # 為線程開(kāi)啟同步 end_time = time.time() print("50個(gè)線程一共的運(yùn)行時(shí)間為:", end_time-start_time) print("主線程結(jié)束")
結(jié)論:主線程等待50個(gè)子線程全部執(zhí)行完成才結(jié)束。
線程鎖(互斥鎖Mutex)
- 一個(gè)進(jìn)程下可以啟用多個(gè)線程,多個(gè)線程共享父進(jìn)程的內(nèi)存空間,也就意味著每個(gè)線程可以訪問(wèn)同一份數(shù)據(jù),此時(shí)如果多個(gè)線程同時(shí)要修改一份數(shù)據(jù),會(huì)出現(xiàn)什么狀況?
- 代碼演示:
# -*- coding:utf8 -*- import threading import time num = 100 threading_list = [] def fun(): global num print("get num:", num) num += 1 time.sleep(1) for x in range(200): t = threading.Thread(target=fun) t.start() threading_list.append(t) for x in threading_list: x.join() print("nun:", num)
- 結(jié)論:運(yùn)行結(jié)果可能會(huì)出現(xiàn)num<300的情況
- 正常來(lái)講,這個(gè)num結(jié)果應(yīng)該是300, 但在python 2.7上多運(yùn)行幾次,會(huì)發(fā)現(xiàn),最后打印出來(lái)的num結(jié)果不總是300,為什么每次運(yùn)行的結(jié)果不一樣呢? 哈,很簡(jiǎn)單,假設(shè)你有A,B兩個(gè)線程,此時(shí)都 要對(duì)num 進(jìn)行加1操作, 由于2個(gè)線程是并發(fā)同時(shí)運(yùn)行的,所以2個(gè)線程很有可能同時(shí)拿走了num=100這個(gè)初始變量交給cpu去運(yùn)算,當(dāng)A線程去處完的結(jié)果是101,但此時(shí)B線程運(yùn)算完的結(jié)果也是101,兩個(gè)線程同時(shí)CPU運(yùn)算的結(jié)果再賦值給num變量后,結(jié)果就都是101。那怎么辦呢? 很簡(jiǎn)單,每個(gè)線程在要修改公共數(shù)據(jù)時(shí),為了避免自己在還沒(méi)改完的時(shí)候別人也來(lái)修改此數(shù)據(jù),可以給這個(gè)數(shù)據(jù)加一把鎖, 這樣其它線程想修改此數(shù)據(jù)時(shí)就必須等待你修改完畢并把鎖釋放掉后才能再訪問(wèn)此數(shù)據(jù)。
*注:不要在3.x上運(yùn)行,不知為什么,3.x上的結(jié)果總是正確的,可能是自動(dòng)加了鎖
加鎖版本:
import random import threading import time num = 100 threading_list = [] def fun(): global num time.sleep(random.random()) lock.acquire() # 加鎖 print("get num:", num, threading.current_thread()) num += 1 lock.release() # 釋放鎖 # 實(shí)例化鎖對(duì)象 lock = threading.Lock() for x in range(200): t = threading.Thread(target=fun) t.start() threading_list.append(t) for x in threading_list: x.join() print("num:", num)
GIL VS Lock
機(jī)智的同學(xué)可能會(huì)問(wèn)到這個(gè)問(wèn)題,就是既然你之前說(shuō)過(guò)了,Python已經(jīng)有一個(gè)GIL來(lái)保證同一時(shí)間只能有一個(gè)線程來(lái)執(zhí)行了,為什么這里還需要lock? 注意啦,這里的lock是用戶級(jí)的lock,跟那個(gè)GIL沒(méi)關(guān)系 ,具體我們通過(guò)下圖來(lái)看一下+配合我現(xiàn)場(chǎng)講給大家,就明白了。
那你又問(wèn)了, 既然用戶程序已經(jīng)自己有鎖了,那為什么C python還需要GIL呢?加入GIL主要的原因是為了降低程序的開(kāi)發(fā)的復(fù)雜度,比如現(xiàn)在的你寫(xiě)python不需要關(guān)心內(nèi)存回收的問(wèn)題,因?yàn)镻ython解釋器幫你自動(dòng)定期進(jìn)行內(nèi)存回收,你可以理解為python解釋器里有一個(gè)獨(dú)立的線程,每過(guò)一段時(shí)間它起wake up做一次全局輪詢看看哪些內(nèi)存數(shù)據(jù)是可以被清空的,此時(shí)你自己的程序 里的線程和 py解釋器自己的線程是并發(fā)運(yùn)行的,假設(shè)你的線程刪除了一個(gè)變量,py解釋器的垃圾回收線程在清空這個(gè)變量的過(guò)程中的clearing時(shí)刻,可能一個(gè)其它線程正好又重新給這個(gè)還沒(méi)來(lái)及得清空的內(nèi)存空間賦值了,結(jié)果就有可能新賦值的數(shù)據(jù)被刪除了,為了解決類(lèi)似的問(wèn)題,python解釋器簡(jiǎn)單粗暴的加了鎖,即當(dāng)一個(gè)線程運(yùn)行時(shí),其它人都不能動(dòng),這樣就解決了上述的問(wèn)題, 這可以說(shuō)是Python早期版本的遺留問(wèn)題。
RLock(遞歸鎖)
說(shuō)白了就是在一個(gè)大鎖中還要再包含子鎖
import threading, time def run1(): lock.acquire() print("grab the first part data") global num num += 1 lock.release() return num def run2(): lock.acquire() print("grab the second part data") global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----') res2 = run2() lock.release() print(res, res2) if __name__ == '__main__': num, num2 = 0, 0 lock = threading.RLock() for i in range(3): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num, num2)
在開(kāi)發(fā)的過(guò)程中要注意有些操作默認(rèn)都是 線程安全的(內(nèi)部集成了鎖的機(jī)制),我們?cè)谑褂玫臅r(shí)無(wú)需再通過(guò)鎖再處理,例如:
import threading data_list = [] lock_object = threading.RLock() def task(): print("開(kāi)始") for i in range(1000000): data_list.append(i) print(len(data_list)) for i in range(2): t = threading.Thread(target=task) t.start()
Semaphore(信號(hào)量)
- 互斥鎖同時(shí)只允許一個(gè)線程修改數(shù)據(jù),而Semaphore是同時(shí)允許一定數(shù)量的線程修改數(shù)據(jù),比如廁所有三個(gè)坑,那最多只允許三個(gè)人上廁所,后面的人只能等前面的人出來(lái)才能進(jìn)去。
- 代碼演示:
# -*- coding:GBK -*- import threading import time sum_1 = 0 def run(i): global sum_1 time.sleep(1) # lock.acquire() semaphore.acquire() sum_1 += 1 print("線程%s來(lái)了,并修改了sum_1的值為:%s" % (i, sum_1)) semaphore.release() # lock.release() # lock = threading.Lock() semaphore = threading.BoundedSemaphore(5) for x in range(10): t = threading.Thread(target=run, args=(x,)) t.start() while threading.active_count() != 1: pass print("程序結(jié)束")
Event(事件)
- 通過(guò)Event來(lái)實(shí)現(xiàn)兩個(gè)或多個(gè)線程間的交互,下面是一個(gè)紅綠燈的例子,即起動(dòng)一個(gè)線程做交通指揮燈,生成幾個(gè)線程做車(chē)輛,車(chē)輛行駛按紅燈停,綠燈行的規(guī)則。
- 四個(gè)常用方法
set() # 設(shè)置標(biāo)志位為 True
clear() # 清空標(biāo)志位(將標(biāo)志位改為false)
is_set() # 檢測(cè)標(biāo)志位,如果標(biāo)志位被設(shè)置,返回True,否則返回False
wait() # 等待標(biāo)志位被設(shè)置位True程序才繼續(xù)往下運(yùn)行
代碼演示:
# -*- coding:utf-8 -*- import threading import time def light(): count = 1 event.set() # 設(shè)置標(biāo)志位 True while True: if count <= 10: print("現(xiàn)在是綠燈") time.sleep(1) elif count <= 15: print("現(xiàn)在是紅燈") event.clear() # 清空標(biāo)志位(將標(biāo)志位改為false) time.sleep(1) else: count = 0 event.set() count += 1 def car(name): while True: if event.is_set(): print("----------%s在起飛-------------" % name) time.sleep(1) else: print("---------%s在等紅燈---------------" % name) event.wait() # 等待標(biāo)志位被設(shè)置位True程序才繼續(xù)往下運(yùn)行 event = threading.Event() light_1 = threading.Thread(target=light) light_1.start() for x in range(5): car_1 = threading.Thread(target=car, args=("馬自達(dá)"+str(x),)) car_1.start()
紅綠燈案例
Queue(隊(duì)列)
queue.Queue(maxsize=0) #隊(duì)列:先進(jìn)先出 maxsize:設(shè)置隊(duì)列的大小 queue.LifoQueue(maxsize=0) ##last in fisrt out maxsize:設(shè)置隊(duì)列的大小 queue.PriorityQueue(maxsize=0) #存儲(chǔ)數(shù)據(jù)時(shí)可設(shè)置優(yōu)先級(jí)的隊(duì)列,按優(yōu)先級(jí)順序(最低的先) maxsize:設(shè)置隊(duì)列的大小
exceptionqueue.
Empty
Exception raised when non-blocking get()(or get_nowait()) is called on a Queue object which is empty.
當(dāng)在一個(gè)空的隊(duì)列對(duì)象上調(diào)用非阻塞的get()(或get_nowait())時(shí),會(huì)產(chǎn)生異常。
exceptionqueue.
Full
Exception raised when non-blocking put() (or put_nowait() ) is called on a Queue object which is full.
當(dāng)非阻塞的put()(或put_nowait())被調(diào)用到一個(gè)已滿的隊(duì)列對(duì)象上時(shí)引發(fā)的異常。
import queue # 實(shí)例化隊(duì)列對(duì)象 q = queue.Queue(3) print(q.qsize()) # 獲取隊(duì)列內(nèi)數(shù)據(jù)的長(zhǎng)度 print(q.empty()) # 如果隊(duì)列是空的,返回True,否則返回False(不可靠?。? print(q.full()) # 如果隊(duì)列已滿,返回True,否則返回False(不可靠?。? """ Queue.put(item, block=True, timeout=None) 可以簡(jiǎn)寫(xiě):Queue.put(item, True, 5) 將項(xiàng)目放入隊(duì)列。 如果可選的args block為true(默認(rèn)值),并且timeout為None(默認(rèn)值),必要時(shí)進(jìn)行阻塞,直到有空閑的槽。 如果timeout是一個(gè)正數(shù),它最多阻斷timeout秒,如果在這段時(shí)間內(nèi)沒(méi)有空閑槽,則引發(fā)Full異常。 否則(block為false),如果有空閑的槽立即可用,就在隊(duì)列上放一個(gè)項(xiàng)目,否則就引發(fā)Full異常(在這種情況下忽略超時(shí))。 """ q.put(1) # 數(shù)據(jù)“1”進(jìn)入隊(duì)列 q.put("nihao") # 數(shù)據(jù)"nihao"進(jìn)入隊(duì)列 q.put("456ni", block=True, timeout=5) ''' 將一個(gè)項(xiàng)目放入隊(duì)列中,不進(jìn)行阻斷。 只有在有空閑位置的情況下才排隊(duì)。 否則會(huì)引發(fā)Full異常。 ''' # q.put_nowait(123) ''' Queue.get(block=True, timeout=None) 可以簡(jiǎn)寫(xiě):Queue.get(True, 3) 從隊(duì)列中刪除并返回一個(gè)項(xiàng)目。 如果可選的args'block'為T(mén)rue(默認(rèn)),'timeout'為無(wú)(默認(rèn))。 就會(huì)在必要時(shí)阻塞,直到有一個(gè)項(xiàng)目可用。 如果'timeout'是非負(fù)數(shù),它最多阻斷'timeout'秒,如果在這段時(shí)間內(nèi)沒(méi)有項(xiàng)目可用,則引發(fā)Empty異常。 否則('block'為False),如果有一個(gè)項(xiàng)目立即可用,則返回一個(gè)項(xiàng)目。 否則引發(fā)Empty異常('timeout'被忽略了在這種情況下)。 ''' print(q.get()) print(q.get()) print(q.get()) print(q.get(block=True, timeout=2)) ''' 從隊(duì)列中移除并返回一個(gè)項(xiàng)目,而不阻塞。 只有當(dāng)一個(gè)項(xiàng)目立即可用時(shí),才會(huì)得到一個(gè)項(xiàng)目。 否則引發(fā)Empty異常。 ''' # print(q.get_nowait())
生產(chǎn)者消費(fèi)者模型
在并發(fā)編程中使用生產(chǎn)者和消費(fèi)者模式能夠解決絕大多數(shù)并發(fā)問(wèn)題。該模式通過(guò)平衡生產(chǎn)線程和消費(fèi)線程的工作能力來(lái)提高程序的整體處理數(shù)據(jù)的速度。
為什么要使用生產(chǎn)者和消費(fèi)者模式
在線程世界里,生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的線程,消費(fèi)者就是消費(fèi)數(shù)據(jù)的線程。在多線程開(kāi)發(fā)當(dāng)中,如果生產(chǎn)者處理速度很快,而消費(fèi)者處理速度很慢,那么生產(chǎn)者就必須等待消費(fèi)者處理完,才能繼續(xù)生產(chǎn)數(shù)據(jù)。同樣的道理,如果消費(fèi)者的處理能力大于生產(chǎn)者,那么消費(fèi)者就必須等待生產(chǎn)者。為了解決這個(gè)問(wèn)題于是引入了生產(chǎn)者和消費(fèi)者模式。
什么是生產(chǎn)者消費(fèi)者模式
生產(chǎn)者消費(fèi)者模式是通過(guò)一個(gè)容器來(lái)解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問(wèn)題。生產(chǎn)者和消費(fèi)者彼此之間不直接通訊,而通過(guò)阻塞隊(duì)列來(lái)進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)者處理,直接扔給阻塞隊(duì)列,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊(duì)列里取,阻塞隊(duì)列就相當(dāng)于一個(gè)緩沖區(qū),平衡了生產(chǎn)者和消費(fèi)者的處理能力。
# 生產(chǎn)者/消費(fèi)者 import threading import queue import time # 生產(chǎn)者 def producer(name): count = 1 while True: p.put("{}骨頭{}".format(name, count)) print("骨頭{}被{}生產(chǎn)".format(count, name).center(60, "*")) count += 1 time.sleep(0.1) # 消費(fèi)者 def consumer(name): while True: print("{}被{}吃掉了".format(p.get(), name)) # 實(shí)例化隊(duì)列對(duì)象 p = queue.Queue(10) # 創(chuàng)建生產(chǎn)者線程 producer_threading1 = threading.Thread(target=producer, args=("飛某人",)) producer_threading2 = threading.Thread(target=producer, args=("Alex",)) # 創(chuàng)建消費(fèi)者線程 consumer_threading1 = threading.Thread(target=consumer, args=("張三",)) consumer_threading2 = threading.Thread(target=consumer, args=("李四",)) producer_threading1.start() producer_threading2.start() consumer_threading1.start() consumer_threading2.start()
線程池
Python3中官方才正式提供線程池。
線程不是開(kāi)的越多越好,開(kāi)的多了可能會(huì)導(dǎo)致系統(tǒng)的性能更低了,例如:如下的代碼是不推薦在項(xiàng)目開(kāi)發(fā)中編寫(xiě)。
import threading def task(video_url): pass url_list = ["www.xxxx-{}.com".format(i) for i in range(30000)] for url in url_list: t = threading.Thread(target=task, args=(url,)) t.start() # 這種每次都創(chuàng)建一個(gè)線程去操作,創(chuàng)建任務(wù)的太多,線程就會(huì)特別多,可能效率反倒降低了。
建議:使用線程池
import time from concurrent.futures import ThreadPoolExecutor # 并行期貨,線程池執(zhí)行者 """ pool = ThreadPoolExecutor(100) pool.submit(函數(shù)名,參數(shù)1,參數(shù)2,參數(shù)...) """ def task(video_url, num): print("開(kāi)始執(zhí)行任務(wù)", video_url, num) # 開(kāi)始執(zhí)行任務(wù) www.xxxx-299.com 3 time.sleep(1) # 創(chuàng)建線程池,最多維護(hù)10個(gè)線程 threadpool = ThreadPoolExecutor(10) # 生成300網(wǎng)址,并放入列表 url_list = ["www.xxxx-{}.com".format(i) for i in range(300)] for url in url_list: """ 在線程池中提交一個(gè)任務(wù),線程池如果有空閑線程,則分配一個(gè)線程去執(zhí)行,執(zhí)行完畢后在將線程交還給線程池, 如果沒(méi)有空閑線程,則等待。注意在等待時(shí),與主線程無(wú)關(guān),主線程依然在繼續(xù)執(zhí)行。 """ threadpool.submit(task, url, 3) print("等待線程池中的任務(wù)執(zhí)行完畢中······") threadpool.shutdown(True) # 等待線程池中的任務(wù)執(zhí)行完畢后,在繼續(xù)執(zhí)行 print("END")
任務(wù)執(zhí)行完任務(wù),再干點(diǎn)其他事:
"""線程池的回調(diào)""" import time import random from concurrent.futures import ThreadPoolExecutor def task(video_url): print("開(kāi)始執(zhí)行任務(wù)", video_url) time.sleep(1) return random.randint(0, 10) # 將結(jié)果封裝成一個(gè)Futuer對(duì)象,返回給線程池 def done(response): # response就是futuer對(duì)象,也就是task的返回值分裝的一個(gè)Futuer對(duì)象 print("任務(wù)執(zhí)行完后,回調(diào)的函數(shù)", response.result()) # 即Futuer.result():取出task的返回值 # 創(chuàng)建線程池 threadpool = ThreadPoolExecutor(10) url_list = ["www.xxxx-{}.com".format(i) for i in range(5)] for url in url_list: futuer = threadpool.submit(task, url) # futuer是由task返回的一個(gè)Future對(duì)象,里面有記錄task的返回值 futuer.add_done_callback(done) # 回調(diào)done函數(shù),執(zhí)行者依然是子線程 # 優(yōu)點(diǎn):可以做分工,例如:task專門(mén)下載,done專門(mén)將下載的數(shù)據(jù)寫(xiě)入本地文件。
到此這篇關(guān)于詳解Python中的多線程的文章就介紹到這了,更多相關(guān)Python多線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python爬蟲(chóng)之快速對(duì)js內(nèi)容進(jìn)行破解
這篇文章主要介紹了python爬蟲(chóng)之快速對(duì)js內(nèi)容進(jìn)行破解,到一般js破解有兩種方法,一種是用Python重寫(xiě)js邏輯,一種是利用第三方庫(kù)來(lái)調(diào)用js內(nèi)容獲取結(jié)果,這次我們就用第三方庫(kù)來(lái)進(jìn)行js破解,需要的朋友可以參考下2019-07-07python圖片剪裁代碼(圖片按四個(gè)點(diǎn)坐標(biāo)剪裁)
這篇文章主要介紹了python圖片剪裁代碼(圖片按四個(gè)點(diǎn)坐標(biāo)剪裁),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Python補(bǔ)齊字符串長(zhǎng)度的實(shí)例
今天小編就為大家分享一篇Python補(bǔ)齊字符串長(zhǎng)度的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-11-11Python相關(guān)庫(kù)設(shè)置技巧保護(hù)你的C盤(pán)
這篇文章主要為大家介紹了Python相關(guān)庫(kù)設(shè)置,保護(hù)你的C盤(pán)技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11