線程、進程、協程

進程是資源分配單位,系統會分配內存,屏幕,窗口。
線程是進程中真正執行的東西。


 python中的thread模塊是比較底層的模塊,python的threading模塊是對thread作了一些包裝的,能夠更加方便的被使用。python

一、多線程執行算法

 #coding=utf-8  import threading  import time  def saySorry(): print("親愛的,我錯了,我能吃飯了嗎?") time.sleep(1)  if __name__ == "__main__": for i in range(5): t = threading.Thread(target=saySorry) t.start() #啓動線程,即讓線程開始執行
二、自定義線程類
#coding=utf-8 import threading import time class MyThread(threading.Thread): def __init__(self, name1, age): super(MyThread, self).__init__() self.name1 = name1 self.age = age def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字 print(msg) if __name__ == '__main__': t = MyThread() t.start()
 

      python的threading.Thread類有一個run方法,用於定義線程的功能函數,能夠在本身的線程類中覆蓋該方法。而建立本身的線程實例後,經過Thread類的start方法,能夠啓動該線程,交給python虛擬機進行調度,當該線程得到執行的機會時,就會調用run方法執行線程。緩存

 

      多線程之間共享全局變量,優勢是方便在多個線程之間共享數據,缺點是線程是對全局變量隨意遂改可能形成多線程之間對全局變量的混亂(即線程非安全)。安全

      若是多個線程同時對同一個全局變量操做,會出現資源競爭問題,從而數據結果會不正確:服務器



測試結果:import threading import time g_num = 0 def work1(num): global g_num for i in range(num): g_num += 1 print("----in work1, g_num is %d---"%g_num) def work2(num): global g_num for i in range(num): g_num += 1 print("----in work2, g_num is %d---"%g_num) print("---線程建立以前g_num is %d---"%g_num) t1 = threading.Thread(target=work1, args=(1000000,)) t1.start() t2 = threading.Thread(target=work2, args=(1000000,)) t2.start() while len(threading.enumerate()) != 1: time.sleep(1) print("2個線程對同一個全局變量操做以後的最終結果是:%s" % g_num)
---線程建立以前g_num is 0--- ----in work1, g_num is 1088005--- ----in work2, g_num is 1286202--- 2個線程對同一個全局變量操做以後的最終結果是:1286202

同步:網絡

同步就是協同步調,按預約的前後次序進行運行多線程

互斥鎖:併發

某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲「鎖定」,其餘線程不能更改;直到該線程釋放資源,將資源的狀態變成「非鎖定」,其餘的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操做,從而保證了多線程狀況下數據的正確性。app

寫法:異步

# 建立鎖 mutex = threading.Lock() # 鎖定 mutex.acquire() # 釋放 mutex.release()

 

鎖的好處:

確保了某段關鍵代碼只能由一個線程從頭至尾完整地執行
鎖的壞處:
阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地降低了
因爲能夠存在多個鎖,不一樣的線程持有不一樣的鎖,並試圖獲取對方持有的鎖時,可能會形成死鎖

死鎖解決辦法(

看門狗思想:過一一段時間就執行一次特殊的某行代碼,若是長時間不執行,系統就自動重啓

2.銀行家算法

互斥鎖:線程可以同步保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲鎖定狀態,其餘線程不能更改,直到該線程釋放資源。

當建立一個線程以後,函數裏面全部的內存空間是這個線程獨有的,在建立一個時,會從新建立一個內存空間。各人是各人的。函數裏面的代碼各人是各人的,不會共享。

非全局變量不須要加鎖。

死鎖:在線程間共享多個資源的時候,若是兩個線程分別佔有一部分資源而且同時等待對方的資源時,就會形成死鎖。

同步:同步就是協調步調,按照預約的前後次序進行運行  三把鎖一環扣一環
異步:不一樣步

生產者與消費者模式:
1。隊列 :進程中的隊列和線程中的隊列不是一個概念。隊列就是用來給生產者和消費者解耦的。
2。棧
fifo: frist in frist out =>Queue
filo: frist in last out =>

一個函數想獲得另外一個函數的值,要麼return 返回值,要麼經過全局變量。

一、使用全局字典的方法
二、ThreadLocal:不用傳參數,用一個全局變量,能過完成線程裏邊的全部的數據的傳遞,不會由於多個線程對參數的修改對程序產生影響。

孤兒進程:父進程先結束,子進程還沒結束
殭屍進程:若是一個子進程死了,父進程沒有收屍,在收屍前的整個期間,子進程就稱爲殭屍進程。

線程之間共享全局變量。

原子操做(原子性):要麼不作,要作就作完。

線程安全問題:可能在一句代碼還沒執行完,操做系統就中止了代碼的運行。

輪詢:是一種CPU決策如何提供周邊設備服務的方式,又稱程控輸出入

多任務UDP聊天器:

import socket import threading # 定義發送數據的函數 def send_data(udp_socket): # 定義要發送的內容 send_content = input("請輸入要發送的內容:") # 請輸入IP地址 ipddr = input("請輸入IP地址,格式爲:xxx.xxx.xxx.xxx :") # 請輸入端口號 port = int(input("請輸入端口號:")) # 把要發送的數據轉換爲 二進制 send_data = send_content.encode("utf-8") # 發送數據 udp_socket.sendto(send_data, (ipddr, port)) # 測試 # 定義接收數據的函數 def recvData(udp_socket): while True: # 接收數據 recv_data = udp_socket.recvfrom(1024) # 若是數據存在,則解析數據 if recv_data: # 拆包,獲得內容 msg, list_port = recv_data # 拆包,獲得msg msg = msg.decode("gbk") # 打印內容 print(msg, list_port) else: # 數據不存在中止循環 break # 定義主入口函數 def main(): # 定義套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 綁定服務器客戶端端口 # udp_socket.bind(("", 7878)) # 定義子線程而且開啓 t1 = threading.Thread(target=recvData, args=(udp_socket, )) # 設置守護線程 t1.setDaemon(True) t1.start() while True: print("-------------------") print("-- 一、發送數據 --") print("-- 二、退出系統 --") print("-------------------") num = int(input("請選擇功能【1/2/3】:")) if num < 1 or num > 3: print("輸入不合法!,請從新輸入") else: if num == 1: # 發送數據 send_data(udp_socket) if num == 2: print("正在退出系統...") print("系統已退出") break if __name__ == '__main__': main()

===========================================================================

進程:操做系統中的算法包括:時間片輪轉、優先級調度、


併發:看上去一塊兒執行。當前的任務數量大於核數。
並行:真正的一塊兒執行。當前任務數小於核數。

調度算法:什麼樣的狀況下按照什麼樣的規則讓誰去執行。
編寫完畢的代碼,在沒有運行的時候稱之爲程序,在運行的時候稱之爲進程。

一、fork()創造子線程
import os
fork():能夠在python程序中建立子進程。
ret = os.fork()
在fork()中,主進程想要結束,不會由於子進程沒有結束而等待。只要子進程產生,子進程的執行順序和執行過程和主進程同樣,就是衆所周知的代碼執行的過程。


2.pid值:
getpid():獲取當前進程的pid值。
pid值:在操做系統當中,當進程運行起來時,操做系統都會給這個進程分配一個獨一無二的值,即pid值。processID
父進程中fork的返回值,就是剛剛建立出來的子進程的id。
getppid():獲取父進程的pid值。
pid值小於等於65535


3.Process()創造子線程:

import multiprocessing import time # 定義函數 def work1(): for i in range(10): print("work1----", i) time.sleep(0.5) if __name__ == '__main__': # 建立進程 # 1. 導入 multiprocessing 模塊 # 2. multiprocessing.Process() 建立子進程 # 3. start() 方法啓動進程 p1 = multiprocessing.Process(group=None, target=work1) p1.start() for i in range(10): print("這是主進程", i) time.sleep(0.5)

 

 

    p.join()#加了join以後,主進程會等子進程執行完代碼以後,再開始執行join下面的代碼

    join([timeout])#堵塞:主進程等待子進程結束以後才結束。timeout表示操做時間。
    terminate():無論任務是否完成,當即終止。

    因爲process的跨平臺更好,之後不用fork,而是用process

 

 

Process語法結構以下:

Process([group [, target [, name [, args [, kwargs]]]]])
target:若是傳遞了函數的引用,能夠任務這個子進程就執行這裏的代碼
args:給target指定的函數傳遞的參數,以元組的方式傳遞
kwargs:給target指定的函數傳遞命名參數
name:給進程設定一個名字,能夠不設定

group:指定進程組,大多數狀況下用不到

Process建立的實例對象的經常使用方法:
start():啓動子進程實例(建立子進程)
is_alive():判斷進程子進程是否還在活着
join([timeout]):是否等待子進程執行結束,或等待多少秒

terminate():無論任務是否完成,當即終止子進程

Process建立的實例對象的經常使用屬性:
name:當前進程的別名,默認爲Process-N,N爲從1開始遞增的整數

pid:當前進程的pid(進程號)

 

進程間不一樣享全局變量

進程線程對比:

進程,可以完成多任務,好比 在一臺電腦上可以同時運行多個QQ

線程,可以完成多任務,好比 一個QQ中的多個聊天窗口

進程是系統進行資源分配和調度的一個獨立單位.

 

線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源.
區別:
一個程序至少有一個進程,一個進程至少有一個線程.
線程的劃分尺度小於進程(資源比進程少),使得多線程程序的併發性高。
進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率 
線線程不可以獨立執行,必須依存在進程中
能夠將進程理解爲工廠中的一條流水線,而其中的線程就是這個流水線上的工人 
優缺點:

線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。

 

4.進程池Pool建立子線程

import multiprocessing import time def copy_work(): print("拷貝中....",multiprocessing.current_process().pid) time.sleep(0.3) if __name__ == '__main__': # 建立進程池 # Pool(3) 表示建立容量爲3個進程的進程池 pool = multiprocessing.Pool(3) for i in range(10): # 利用進程池同步拷貝文件,進程池中的進程會必須等上一個進程退出才能執行下一個進程 # pool.apply(copy_work) pool.apply_async(copy_work) pool.close() # 注意:若是使用異步方式執行copy_work任務,主線程再也不等待子線程執行完畢再退出! pool.join()

 

 

進程池Pool:主進程通常用來等待,真正的任務都在子進程中執行。

 

multiprocessing.Pool經常使用函數解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式調用func(並行執行,堵塞方式必須等待上一個進程退出才能執行下一個進程),args爲傳遞給func的參數列表,kwds爲傳遞給func的關鍵字參數列表;
close():關閉Pool,使其再也不接受新的任務;
terminate():無論任務是否完成,當即終止;

join():主進程阻塞,等待子進程的退出, 必須在close或terminate以後使用;

 

進程池中的Queue
若是要使用Pool建立進程,就須要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),不然會獲得一條以下的錯誤信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.

下面的實例演示了進程池中的進程如何通訊:

import multiprocessing import time def write_queue(queue): # 循環寫入數據 for i in range(10): if queue.full(): print("隊列已滿!") break # 向隊列中放入消息 queue.put(i) time.sleep(0.5) def read_queue(queue): # 循環讀取隊列消息 while True: # 隊列爲空,中止讀取 if queue.empty(): print("---隊列已空---") break # 讀取消息並輸出 result = queue.get() print(result) if __name__ == '__main__': # 建立消息隊列 queue = multiprocessing.Queue(3) # 建立子進程 p1 = multiprocessing.Process(target=write_queue, args=(queue,)) p1.start() # 等待p1寫數據進程執行結束後,再往下執行 p1.join() p1 = multiprocessing.Process(target=read_queue, args=(queue,)) p1.start()

q.put():存數據  q.get():取數據  q.full():判斷數據是不是滿的  q.empty()判斷數據是否爲空  q.get_nowait():當即存數據不等待   q.put_nowait():當即取數據不等待


進程間通訊的方式:命名管道  無名管道 共享內存 隊列  網絡功能

 

fork ()是最底層的方法。
pool = Pool(3)
pool.apply_async(xx)
pool 中,主進程通常不幹活,主要是建立的子進程幹活,join()方法用來等待。


apply()=>堵塞式


進程共享數據,寫實拷貝。


主進程的pid 是運行程序的那個軟件的pid值

案例:文件夾copy器

import multiprocessing import os # file_name 文件名 # source_dir 源文件目錄 # dest_dir 目標文件目錄 def copy_work(file_name, source_dir, dest_dir): # 拼接路徑 source_path = source_dir+"/"+file_name dest_path = dest_dir+"/"+file_name print(source_path, "----->", dest_path) # 打開源文件、建立目標文件 with open(source_path,"rb") as source_file: with open(dest_path,"wb") as dest_file: while True: # 循環讀取數據 file_data = source_file.read(1024) if file_data: # 循環寫入到目標文件 dest_file.write(file_data) else: break if __name__ == '__main__': # 一、定義源文件目錄和目標文件夾的目錄 source_dir = "test" dest_dir = "/home/teahcer/桌面/test" try: # 二、建立目標文件夾目錄 os.mkdir(dest_dir) except: print("目標文件夾已經存在,未建立~") # 三、列表獲得全部的源文件中的文件 file_list = os.listdir(source_dir) print(file_list) # 四、建立進程池 pool = multiprocessing.Pool(3) # 五、for 循環,依次拷貝每一個文件 for file_name in file_list: # copy_work(file_name, source_dir, dest_dir) pool.apply_async(copy_work, args=(file_name, source_dir, dest_dir)) # 六、關閉進程池 pool.close() # 七、設置主進程等待子進程執行結束再退出 pool.join()

============================================================================

協程:

迭代器(iterator):

迭代是訪問集合元素的一種方式。

迭代器是一個能夠記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。

迭代器只能往前不會後退。

能夠對list、tuple、str等類型的數據使用for...in...的循環語法從其中依次拿到數據進行使用,這樣的過程稱爲遍歷,也叫迭代。

可迭代對象(Iterable):能夠經過for...in...這類語句迭代讀取一條數據供咱們使用的對象。列表元組字典都是  可迭代對象。

可迭代對象經過__iter__方法向咱們提供一個迭代器,咱們在迭代一個可迭代對象的時候,實際上就是先獲取該對象提供的一個迭代器,而後經過這個迭代器來依次獲取對象中的每個數據.

一個具有了__iter__方法的對象,就是一個可迭代對象。

一個實現了__iter__方法和__next__方法的對象,就是迭代器。

iter()函數與next()函數
list、tuple等都是可迭代對象,咱們能夠經過iter()函數獲取這些可迭代對象的迭代器。而後咱們能夠對獲取到的迭代器不斷使用next()函數來獲取下一條數據。iter()函數實際上就是調用了可迭代對象的__iter__方法。

 
  1.  
    >>> li = [11, 22, 33, 44, 55]
  2.  
    >>> li_iter = iter(li)
  3.  
    >>> next(li_iter)
  4.  
    11
  5.  
    >>> next(li_iter)
  6.  
    22
  7.  
    >>> next(li_iter)
  8.  
    33
  9.  
    >>> next(li_iter)
  10.  
    44
  11.  
    >>> next(li_iter)
  12.  
    55
  13.  
    >>> next(li_iter)
  14.  
    Traceback (most recent call last):
  15.  
    File "<stdin>", line 1, in <module>
  16.  
    StopIteration
  17.  
    >>>

可使用 isinstance() 判斷一個對象是不是 Iterator 對象.

一個實現了__iter__方法和__next__方法的對象,就是迭代器。

自定義迭代器

from collections import Iterable from collections import Iterator class StudentList(object): def __init__(self): # 建立列表 self.items = list() def addItem(self,item): # 追加元素到列表中 self.items.append(item) def __iter__(self): # 建立迭代器對象 studentIterator = StudentIterator(self.items) # 返回迭代器對象 return studentIterator # 定義迭代器 class StudentIterator(object): # 定義構造方法 # 1)完成 索引下標定義和初始化 # 2)接收要遍歷的列表值 def __init__(self, items): self.items = items self.current_index = 0 def __iter__(self): return self def __next__(self): # 判斷位置是否合法 if self.current_index < len(self.items): # 根據current_index 返回列表值 item = self.items[self.current_index] # 讓 下標+1 self.current_index += 1 # 返回元素內容 return item else: # 中止迭代 # 主動拋出異常,迭代器沒有更多的值(到了迭代器末尾) raise StopIteration # 實例化對象 stulist = StudentList() stulist.addItem("張三") stulist.addItem("李四") stulist.addItem("C羅") # 檢查是不是可迭代對象 result = isinstance(stulist, Iterable) print(result) for value in stulist: print(value)

for...in...循環的本質
for item in Iterable 循環的本質就是先經過iter()函數獲取可迭代對象Iterable的迭代器,而後對獲取到的迭代器不斷調用next()方法來獲取下一個值並將其賦值給item,當遇到StopIteration的異常後循環結束。

 

8. 迭代器的應用場景

咱們發現迭代器最核心的功能就是能夠經過next()函數的調用來返回下一個數據值。若是每次返回的數據值不是在一個已有的數據集合中讀取的,而是經過程序按照必定的規律計算生成的,那麼也就意味着能夠不用再依賴一個已有的數據集合,也就是說不用再將全部要迭代的數據都一次性緩存下來供後續依次讀取,這樣能夠節省大量的存儲(內存)空間。

斐波那契數:

class Fibonacci(): def __init__(self, num): # 經過構造方法,保存num到類的成員屬性中 self.num = num # 定義變量保存斐波那契數列前兩個值 self.a = 0 self.b = 1 # 記錄當前的變量值 self.current_index = 0 def __iter__(self): # 返回迭代器,因自身就是迭代器,故能夠返回本身 return self def __next__(self): # 判斷是否生成完畢 if self.current_index < self.num: # 返回 result = self.a # 交換兩個變量值 self.a, self.b = self.b, self.a+self.b self.current_index += 1 return result else: # 中止迭代 raise StopIteration if __name__ == '__main__': # 建立迭代器 fib_iterator = Fibonacci(5) # 使用迭代器,輸出斐波那契數列值 for value in fib_iterator: print(value, end=" ")

並非只有for循環能接收可迭代對象,除了for循環能接收可迭代對象,list、tuple等也能接收。

  1.  
    li = list(FibIterator( 15))
  2.  
    print(li)
  3.  
    tp = tuple(FibIterator( 6))
  4.  
    print(tp)

生成器:

 

利用迭代器,咱們能夠在每次迭代獲取數據(經過next()方法)時按照特定的規律進行生成。可是咱們在實現一個迭代器時,關於當前迭代到的狀態須要咱們本身記錄,進而才能根據當前狀態生成下一個數據。爲了達到記錄當前狀態,並配合next()函數進行迭代使用,咱們能夠採用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器。
建立生成器方法1
要建立一個生成器,有不少種方法。第一種方法很簡單,只要把一個列表生成式的 [ ] 改爲 ( )。

In [15]: L = [ x*2 for x in range(5)] In [16]: L Out[16]: [0, 2, 4, 6, 8] In [17]: G = ( x*2 for x in range(5)) In [18]: G Out[18]: <generator object <genexpr> at 0x7f626c132db0>

 

 建立生成器方法2
generator很是強大。若是推算的算法比較複雜,用相似列表生成式的 for 循環沒法實現的時候,還能夠用函數來實現。

 

使用了yield關鍵字的函數再也不是函數,而是生成器。(使用了yield的函數就是生成器)
yield關鍵字有兩點做用:
保存當前運行狀態(斷點),而後暫停執行,即將生成器(函數)掛起
將yield關鍵字後面表達式的值做爲返回值返回,此時能夠理解爲起到了return的做用
可使用next()函數讓生成器從斷點處繼續執行,即喚醒生成器(函數)

Python3中的生成器可使用return返回最終運行的返回值,而Python2中的生成器不容許使用return返回一個返回值(便可以使用return從生成器中退出,但return後不能有任何表達式)。

 

使用send喚醒

咱們除了可使用next()函數來喚醒生成器繼續執行外,還可使用send()函數來喚醒執行。使用send()函數的一個好處是能夠在喚醒的同時向斷點處傳入一個附加數據。

例子:執行到yield時,gen函數做用暫時保存,返回i的值; temp接收下次c.send("python"),send發送過來的值,c.next()等價c.send(None)

協程:

又稱微線程。通俗的理解:在一個線程中的某個函數,能夠在任何地方保存當前函數的一些臨時變量等信息,而後切換到另一個函數中執行,注意不是經過調用函數的方式作到的,而且切換的次數以及何時再切換到原來的函數都由開發者本身肯定

 

協程和線程差別:

在實現多任務時, 線程切換從系統層面遠不止保存和恢復 CPU上下文這麼簡單。 操做系統爲了程序運行的高效性每一個線程都有本身緩存Cache等等數據,操做系統還會幫你作這些數據的恢復操做。 因此線程的切換很是耗性能。可是協程的切換隻是單純的操做CPU的上下文(能夠理解爲方法之間的切換),因此一秒鐘切換個上百萬次系統都抗的住。簡單說,就是比線程佔用資源更少。

用yield()和next()實現簡單協程:

import time def work1(): while True: print("----work1---") yield time.sleep(0.5) def work2(): while True: print("----work2---") yield time.sleep(0.5) def main(): w1 = work1() w2 = work2() while True: next(w1) next(w2) if __name__ == "__main__": main()

用greenlet實現協程:

greenlet 至關於集成了yield()和next(),使用的時候自動調用這兩個方法。

先裝包:sudo pip3 install preenlet

#coding=utf-8 from greenlet import greenlet import time def test1(): while True: print "---A--" gr2.switch() time.sleep(0.5) def test2(): while True: print "---B--" gr1.switch() time.sleep(0.5) gr1 = greenlet(test1) gr2 = greenlet(test2) #切換到gr1中運行 gr1.switch()

用gevent實現協程:

gevent比greenlet更厲害,直不用調用switch方法,能夠自動切換任務。

gevent的原理:當一個greenlet遇到IO(指的是input output 輸入輸出,好比網絡、文件操做等)操做時,好比訪問網絡,就自動切換到其餘的greenlet,等到IO操做完成,再在適當的時候切換回來繼續執行。

因爲IO操做很是耗時,常常使程序處於等待狀態,有了gevent爲咱們自動切換協程,就保證總有greenlet在運行,而不是等待IO。

安裝:pip install gevent

import time import gevent def work1(): for i in range(5): print("work1 -----1") time.sleep(0.5) def work2(): for i in range(5): print("work2 -----2") time.sleep(0.5) # 建立攜程並指派任務 g1 = gevent.spawn(work1) g2 = gevent.spawn(work2) # 等待協程執行完成再關閉主線程 g1.join() g2.join()

注意:上面代碼中的time方法用的是gevent包中的。

用gevent時,只要加入堵塞的方法都須要gevent裏面的,全部的延時堵塞方法都要用gevent裏面的。

這樣使用時就不是很方便,可能會不知道哪些方法須要用gevent裏面的 。所以須要導入monkey。並在代碼的最前面寫一句:

monkey .patch_all()#至關於monkey把代碼中的全部延時操做都改爲用gevent裏面的。

 

協程併發下載器:

# 導入urllib模塊 import urllib.request import gevent def download_img(img_url, filename): try: # 打開url response = urllib.request.urlopen(img_url) # 建立文件 with open(filename, "wb") as img_file: # 經過循環不斷讀取數據 while True: # 將讀取到的數據保存到變量中 img_data = response.read(1024) # 若是讀取成功,則寫數據到文件中 if img_data: # 寫數據 img_file.write(img_data) else: break except Exception as e: print("下載圖片出現錯誤~!",e) else: print("圖片 %s 下載完成!" % filename) def main(): # 定義變量保存要下載的圖片地址 img_url1 = "http://img.mp.itc.cn/upload/20170716/8e1b835f198242caa85034f6391bc27f.jpg" img_url2 = "http://pic1.wed114.cn/allimg/180227/1023303521-1.gif" img_url3 = "http://image.uczzd.cn/11867042470350090334.gif?id=0&from=export" # 開啓協程 調用下載方法 gevent.joinall([ gevent.spawn(download_img, img_url1, "1.gif"), gevent.spawn(download_img, img_url2, "2.gif"), gevent.spawn(download_img, img_url3, "3.gif") ]) # 主入口 if __name__ == '__main__': main()

 

進程線程協程之間的區別:

進程是資源分配的單位線程是操做系統調度的單位進程切換須要的資源很最大,效率很低線程切換須要的資源通常,效率通常(固然了在不考慮GIL的狀況下)協程切換任務資源很小,效率高多進程、多線程根據cpu核數不同多是並行的,可是協程是在一個線程中 因此是併發

相關文章
相關標籤/搜索