Python 第八篇:異常處理、Socket語法、SocketServer實現多併發、進程和線程、線程鎖、GIL、Event、信號量、進程間通信

本節內容:python

異常處理、Socket語法、SocketServer實現多併發、進程和線程、線程鎖、GIL、Event、信號量、進程間通信、生產者消費者模型、隊列Queue、multiprocess實例git

異常處理、紅綠燈、吃包子實例程序員

 

一:異常處理:github

異常是由於程序出現了錯誤而在正常的控制流覺得采起的行爲,當python檢測到一個異常的時候,解釋器就會支出當前流已經沒法繼續下去,這時候就出現了異常,從python 1.5開始,全部的標準異常都是實用類實現的, Python的異常處理能力是很強大的,可向用戶準確反饋出錯信息。在Python中,異常也是對象,可對它進行操做。全部異常都是基類Exception的成員。全部異常都從基類Exception繼承,並且都在exceptions模塊中定義。Python自動將全部異常名稱放在內建命名空間中,因此程序沒必要導入exceptions模塊便可使用異常。一旦引起並且沒有捕捉SystemExit異常,程序執行就會終止。若是交互式會話遇到一個未被捕捉的SystemExit異常,會話就會終止。數組

while True:
    try: #正常執行的代碼
        a = input("num1:")
        b = input("num2:")
        c = range(10)

        num1 = int(a)
        num2 = int(b)
        result = num1 + num2
        print(c[11])
    except ValueError as e: #執行捕獲的異常名稱 ValueError,並將拋出的錯誤信息保存到e以備調用:
        print("ValueError:",e)
    except IndentationError as e:
        print("index error:",2)
    except IndexError as e:
        print(c[4])
    except KeyboardInterrupt as e:
        print("ctrl + c")
    except Exception as e:
        print("捕獲到爲止異常:")
        print(e)
    finally:
        print("xxxx")

自定義異常和斷言:服務器

斷言:assert  必須知足的條件,知足繼續向下,不知足跳出,和if ---> else語句有點類似:數據結構

while True:
    try:
        a = input("num1:")
        b = input("num2:")
        c = range(10)

        num1 = int(a)
        num2 = int(b)
        assert  num1 > num2  #必須知足的條件,知足繼續向下,不知足跳出
        result = num1 + num2
        print(result)
        print(c[4])
    except ValueError as e: #值錯誤
        print("ValueError:",e)
    except IndexError as e: #索引錯誤,能夠更換索引
        print(c[4])
    except KeyboardInterrupt as e: #
        print("ctrl + c")

    except Exception as e:
        print("捕獲除ctrl+c/語法錯誤/等特殊異常之外的全部異常:")
        print(e)
    else:
        print("沒有出現異常即代碼執行成功3才執行")
    finally:
        print("無論有沒有異常都執行")

 舉幾個小例子,看一下異常捕獲怎麼使用的:多線程

#/usr/bin/env  python
# -*- coding:utf-8 -*-

str_input = 'jack'
number = int(str_input)
print(number)

執行結果:
Traceback (most recent call last):
  File "C:/Users/zhang/PycharmProjects/S12-python3/day8/test/test.py", line 5, in <module>
    number = int(str_input)
ValueError: invalid literal for int() with base 10: 'jack'
未使用異常捕獲舉例:
try:
    str_input = 'jack'
    number = int(str_input)
    print(number)
except Exception as e:
    print("\033[32;1m出現錯誤以下\033[0m")
    print(e)

執行結果:
出現錯誤以下
invalid literal for int() with base 10: 'jack'
使用一次捕獲舉例:
try:
    #正常邏輯代碼
    user_input = input("\033[32;1m請輸入數字:\033[0m")
    number = int(user_input)
except Exception as e:  #這個e是對象Exception類建立的!Exception這裏面封裝了你上面邏輯塊出現問題的全部錯誤
    #邏輯代碼出現錯誤,這裏的代碼塊就是若是上面的代碼出現問題以後執行這個代碼塊的內容
    print(e) #若是這個e你不想要,能夠本身定義
    #這裏也能夠記錄日誌,把錯誤的詳細記錄,錯誤詳細在e裏了!

執行結果1,輸入整數:
請輸入數字:123
123

執行結果2,輸入非法的字符串:
請輸入數字:1as
invalid literal for int() with base 10: '1as'
再次舉例異常捕獲

 

2,socket語法知識:併發

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #獲取要鏈接的對端主機地址
sk.bind(address) #s.bind(address) 將套接字綁定到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。

sk.listen(backlog) #開始監聽傳入鏈接。backlog指定在拒絕鏈接以前,能夠掛起的最大鏈接數量,backlog等於5,表示內核已經接到了鏈接請求,但服務器尚未調用accept進行處理的鏈接個數最大爲5,這個值不能無限大,由於要在內核中維護鏈接隊列

sk.setblocking(bool) #是否阻塞(默認True),若是設置False,那麼accept和recv時一旦無數據,則報錯。

sk.accept() #接受鏈接並返回(conn,address),其中conn是新的套接字對象,能夠用來接收和發送數據。address是鏈接客戶端的地址,接收TCP 客戶的鏈接(阻塞式)等待鏈接的到來

sk.connect(address) #鏈接到address處的套接字。通常,address的格式爲元組(hostname,port),若是鏈接出錯,返回socket.error錯誤。

sk.connect_ex(address) #同上,只不過會有返回值,鏈接成功時返回 0 ,鏈接失敗時候返回編碼,例如:10061

sk.close() #關閉套接字

sk.recv(bufsize[,flag]) #接受套接字的數據。數據以字符串形式返回,bufsize指定最多能夠接收的數量。flag提供有關消息的其餘信息,一般能夠忽略。

sk.recvfrom(bufsize[.flag]) #與recv()相似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。

sk.send(string[,flag]) #將string中的數據發送到鏈接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。即:可能未將指定內容所有發送。

sk.sendall(string[,flag]) #將string中的數據發送到鏈接的套接字,但在返回以前會嘗試發送全部數據。成功返回None,失敗則拋出異常,內部經過遞歸調用send,將全部內容發送出去。

sk.sendto(string[,flag],address) #將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。該函數主要用於UDP協議。

sk.settimeout(timeout) #設置套接字操做的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。通常,超時期應該在剛建立套接字時設置,由於它們可能用於鏈接的操做(如 client 鏈接最多等待5s )

sk.getpeername() #返回鏈接套接字的遠程地址。返回值一般是元組(ipaddr,port)。

sk.getsockname() #返回套接字本身的地址。一般是一個元組(ipaddr,port)

sk.fileno() #套接字的文件描述符

socket.sendfile(file, offset=0, count=None) #發送文件 ,但目前多數狀況下並沒有什麼卵用
socket 基礎語法回顧

 

3,socketserver 實現多併發:在server端啓動多線程進行監聽,有客戶請求就隨時啓動多線程與用戶創建鏈接,socketserver內部使用 IO多路複用 以及 「多線程」 和 「多進程」 ,從而實現併發處理多個客戶端請求的Socket服務端。即:每一個客戶端請求鏈接到服務器時,Socket服務端都會在服務器是建立一個「線程」或者「進 程」 專門負責處理當前客戶端的全部請求。app

socket server 和 select & epoll 仍是不太同樣他的本質是:客戶端第一次連接的時候,只要一進來,我服務端有個while循環爲你建立一個
線程和進程,客戶端就和服務端直接建立通訊,之後傳送數據什麼的就不會經過server端了,直接他倆經過線程或者進程通訊就能夠了!

若是在多進程的時候,client1和client2他們同時傳輸10G的文件都是互相不影響!
若是在多線程的時候,python中的多線程,在同一時間只有一個線程在工做,他底層會自動進行上下文切換,client1傳一點,client2傳一點。

知識回顧:
python中的多線程,有一個GIL在同一時間只有一個線程在工做,他底層會自動進行上下文切換.
這樣會致使python的多線程效率會很低,也就是人們常常說的python多線程問題
socket server 簡明介紹

舉例:

import  socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print("New Conn:",self.client_address)
        while True:
            data = self.request.recv(1024)
            if not  data:
                break
            print("Client Says:",data.decode())
            self.request.send(data)
            self.request.send(bytes("本次數發送完畢","utf8"))


if __name__ == "__main__":
    Host,Port = "localhost",9003
    server = socketserver.ThreadingTCPServer((Host,Port),MyTCPHandler)
    server.serve_forever()
socketserver服務器端
import  socket
ip_port = ("127.0.0.1",9003)
sk = socket.socket()
sk.connect(ip_port)

while True:
    aaa = input(">>:")
    sk.send(bytes(aaa,"utf8"))
    server_reply = sk.recv(4096)
    print(server_reply.decode())
    data = sk.recv(1024)
    print(data.decode())
socketserver 客戶端

客戶端和服務器端第一次鏈接後,數據通信就經過線程或進程進行數據交換(紅色箭頭)

內部調用流程爲:

  • 啓動服務端程序
  • 執行 TCPServer.__init__ 方法,建立服務端Socket對象並綁定 IP 和 端口
  • 執行 BaseServer.__init__ 方法,將自定義的繼承自SocketServer.BaseRequestHandler 的類 MyRequestHandle賦值給self.RequestHandlerClass
  • 執行 BaseServer.server_forever 方法,While 循環一直監聽是否有客戶端請求到達 ...
  • 當客戶端鏈接到達服務器
  • 執行 ThreadingMixIn.process_request 方法,建立一個 「線程」 用來處理請求
  • 執行 ThreadingMixIn.process_request_thread 方法
  • 執行 BaseServer.finish_request 方法,執行 self.RequestHandlerClass()  即:執行 自定義 MyRequestHandler 的構造方法(自動調用基類BaseRequestHandler的構造方法,在該構造方法中又會調用 MyRequestHandler的handle方法)

 

 4.進程和線程:

 ThreadingTCPServer實現的Soket服務器內部會爲每一個client建立一個 「線程」,該線程用來和客戶端進行交互:

一、ThreadingTCPServer基礎

使用ThreadingTCPServer:

  • 建立一個繼承自 SocketServer.BaseRequestHandler 的類
  • 類中必須定義一個名稱爲 handle 的方法
  • 啓動ThreadingTCPServer

什麼是線程(thread)?

線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務

A thread is an execution context, which is all the information a CPU needs to execute a stream of instructions.

Suppose you're reading a book, and you want to take a break right now, but you want to be able to come back and resume reading from the exact point where you stopped. One way to achieve that is by jotting down the page number, line number, and word number. So your execution context for reading a book is these 3 numbers.

If you have a roommate, and she's using the same technique, she can take the book while you're not using it, and resume reading from where she stopped. Then you can take it back, and resume it from where you were.

Threads work in the same way. A CPU is giving you the illusion that it's doing multiple computations at the same time. It does that by spending a bit of time on each computation. It can do that because it has an execution context for each computation. Just like you can share a book with your friend, many tasks can share a CPU.

On a more technical level, an execution context (therefore a thread) consists of the values of the CPU's registers.

Last: threads are different from processes. A thread is a context of execution, while a process is a bunch of resources associated with a computation. A process can have one or many threads.

Clarification: the resources associated with a process include memory pages (all the threads in a process have the same view of the memory), file descriptors (e.g., open sockets), and security credentials (e.g., the ID of the user who started the process).
thread 英文解釋

什麼是進程(process)?

一個程序的執行實例被稱爲進程。

An executing instance of a program is called a process.

Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread, but can create additional threads from any of its threads.
進程和線程的區別

進程與線程的區別?

Threads share the address space of the process that created it; processes have their own address space.
Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
New threads are easily created; new processes require duplication of the parent process.
Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
進程和線程的區別

Python GIL(Global Interpreter Lock):

上面的核心意思就是,不管你啓多少個線程,你有多少個cpu, Python在執行的時候會淡定的在同一時刻只容許一個線程運行,擦。。。,那這還叫什麼多線程呀?

首先須要明確的一點是GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就比如C++是一套語言(語法)標準,可是能夠用不一樣的編譯器來編譯成可執行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也同樣,一樣一段代碼能夠經過CPython,PyPy,Psyco等不一樣的Python執行環境來執行。像其中的JPython就沒有GIL。然而由於CPython是大部分環境下默認的Python執行環境。因此在不少人的概念裏CPython就是Python,也就想固然的把GIL歸結爲Python語言的缺陷。因此這裏要先明確一點:GIL並非Python的特性,Python徹底能夠不依賴於GIL。

這篇文章透徹的剖析了GIL對python多線程的影響,強烈推薦看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

 

5,threading實例,有兩種調用方式,以下:

import threading
import time
 
def sayhi(num): #定義每一個線程要運行的函數
    print("running on number:%s" %num)
    time.sleep(3)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一個線程實例
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另外一個線程實例
    t1.start() #啓動線程
    t2.start() #啓動另外一個線程

    print(t1.getName()) #獲取線程名
    print(t2.getName())
方式一:直接調用
import threading
import time
 
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
 
    def run(self):#定義每一個線程要運行的函數
        print("running on number:%s" %self.num)
        time.sleep(3)
 
if __name__ == '__main__':
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()
方式二:繼承式調用

 

更多線程操做方法:

  • start       線程準備就緒,等待CPU調度
  • setName     爲線程設置名稱
  • getName     獲取線程名稱
  • setDaemon   設置爲後臺線程或前臺線程(默認)
  •             若是是後臺線程,主線程執行過程當中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,均中止
  •             若是是前臺線程,主線程執行過程當中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序中止
  • join        逐個執行每一個線程,執行完畢後繼續往下執行,該方法使得多線程變得無心義
  • run         線程被cpu調度後執行Thread類對象的run方法

6,線程鎖、GIL、Event、信號量:

線程鎖(互斥鎖Mutex)

一個進程下能夠啓動多個線程,多個線程共享父進程的內存空間,也就意味着每一個線程能夠訪問同一份數據,此時,若是2個線程同時要修改同一份數據,會出現什麼情況?

import time
import threading
 
def addNum():
    global num #在每一個線程中都獲取這個全局變量
    print('--get num:',num )
    time.sleep(1)
    num  -=1 #對此公共變量進行-1操做
 
num = 100  #設定一個共享變量
thread_list = []
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)
 
for t in thread_list: #等待全部線程執行完畢
    t.join()
 
 
print('final num:', num )
Python 2 未加鎖

正常來說,這個num結果應該是0, 但在python 2.7上多運行幾回,會發現,最後打印出來的num結果不老是0,爲何每次運行的結果不同呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操做, 因爲2個線程是併發同時運行的,因此2個線程頗有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再賦值給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每一個線程在要修改公共數據時,爲了不本身在還沒改完的時候別人也來修改此數據,能夠給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此數據。 

*注:不要在3.x上運行,不知爲何,3.x上的結果老是正確的,多是自動加了鎖

加鎖版本:

import time
import threading
 
def addNum():
    global num #在每一個線程中都獲取這個全局變量
    print('--get num:',num )
    time.sleep(1)
    lock.acquire() #修改數據前加鎖
    num  -=1 #對此公共變量進行-1操做
    lock.release() #修改後釋放
 
num = 100  #設定一個共享變量
thread_list = []
lock = threading.Lock() #生成全局鎖
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)
 
for t in thread_list: #等待全部線程執行完畢
    t.join()
 
print('final num:', num )
Python 2加鎖版本

RLock(遞歸鎖)

說白了就是在一個大鎖中還要再包含子鎖:

import threading,time
 
def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num +=1
    lock.release()
    return num
def run2():
    print("grab the second part data")
    lock.acquire()
    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(10):
        t = threading.Thread(target=run3)
        t.start()
 
while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num,num2)
Rlock:遞歸鎖

Semaphore(信號量)

互斥鎖 同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 ,好比廁全部3個坑,那最多隻容許3我的上廁所,後面的人只能等裏面有人出來了才能再進去。

import threading,time

def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" %n)
    semaphore.release()

if __name__ == '__main__':

    num= 0
    semaphore  = threading.BoundedSemaphore(2) #最多容許5個線程同時運行
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()

while threading.active_count() != 1:
    pass #print threading.active_count()
else:
    print('----all threads done---')
    print(num)
每次啓動兩個線程

event

他的做用就是:用主線程控制子線程合適執行,他可讓子線程停下來,也可讓線程繼續:

經過Event來實現兩個或多個線程間的交互,下面是一個紅綠燈的例子,即起動一個線程作交通指揮燈,生成幾個線程作車輛,車輛行駛按紅燈停,綠燈行的規則。

import  threading,time,random

def light():
    if not  event.isSet():
        event.set() #設置爲綠燈
    count = 0
    while True:
        if count < 10:
            print("\033[42;1m=====綠燈=====\033[0m")
        elif count < 13:
            print("\033[43;1m======黃燈======\033[0m")
        elif count < 20:
            if event.isSet():
                event.clear()
            print("\033[44;0m=======紅燈=====\033[0m")
        else:
            count = 0
            event.set()
        time.sleep(1)
        count += 1

def car(n):
    while True:
        time.sleep(random.randrange(12)) 
        if event.isSet(): #lvdeg
            print("car %s is runing" % n)
        else:
            print("car %s is wating for the red light" % n)

if __name__ == "__main__":
    event = threading.Event()
    Light = threading.Thread(target=light)
    Light.start()
    for i in range(3):
        t = threading.Thread(target=car,args=[i,])
        t.start()

 六、Python進程:

from multiprocessing import Process
import threading
import time
  
def foo(i):
    print 'say hi',i
  
for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()

注意:因爲進程之間的數據須要各自持有一份,因此建立進程須要的很是大的開銷。而且python不能再Windows下建立進程!

而且在使用多進程的時候,最好是建立多少個進程?:和CPU核數相等

默認的進程之間相互是獨立,若是想讓進程之間數據共享,就得有個特殊的數據結構,這個數據結構就能夠理解爲他有穿牆的功能
若是你能穿牆的話兩邊就均可以使用了
使用了3種方法:

默認的進程沒法進行數據共享:

#!/usr/bin/env python
#coding:utf-8
#author Zhang Shijie
from multiprocessing import Process
from multiprocessing import Manager

import time

li = []

def foo(i):
    li.append(i)
    print('say hi',li)

for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()

print('ending',li)

使用特殊的數據類型,來進行穿牆:

默認的進程之間相互是獨立,若是想讓進程之間數據共享,就得有個特殊的數據結構,這個數據結構就能夠理解爲他有穿牆的功能
若是你能穿牆的話兩邊就均可以使用了
使用了3種方法


第一種方法:

#經過特殊的數據結構:數組(Array)

from multiprocessing import Process,Array

#建立一個只包含數字類型的數組(python中叫列表)
#而且數組是不可變的,在C,或其餘語言中,數組是不可變的,以後再python中數組(列表)是能夠變得
#固然其餘語言中也提供可變的數組
#在C語言中數組和字符串是同樣的,若是定義一個列表,若是能夠增長,那麼我須要在你內存地址後面再開闢一塊空間,那我給你預留多少呢?
#在python中的list可能用鏈表來作的,我記錄了你前面和後面是誰。   列表不是連續的,數組是連續的

'''
上面不是列表是「數組"數組是不可變的,附加內容是爲了更好的理解數組!
'''

temp = Array('i', [11,22,33,44]) #這裏的i是C語言中的數據結構,經過他來定義你要共享的內容的類型!點進去看~
 
def Foo(i):
    temp[i] = 100+i
    for item in temp:
        print i,'----->',item
 
for i in range(2):
    p = Process(target=Foo,args=(i,))
    p.start()
    
第二種方法:
#方法二:manage.dict()共享數據
from multiprocessing import Process,Manager  #這個特殊的數據類型Manager
 
manage = Manager()
dic = manage.dict() #這裏調用的時候,使用字典,這個字典和我們python使用方法是同樣的!
 
def Foo(i):
    dic[i] = 100+i
    print dic.values()
 
for i in range(2):
    p = Process(target=Foo,args=(i,))
    p.start()
    p.join()

OK那麼問題來了,既然進程之間能夠進行共享數據,若是多個進程同時修改這個數據是否是就會形成髒數據?是否是就得須要鎖!

進程的鎖和線程的鎖使用方式是很是同樣的知識他們是用的類是在不一樣地方的

 

 進程池

進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,若是進程池序列中沒有可供使用的進進程,那麼程序就會等待,直到進程池中有可用進程爲止。

進程池中有兩個方法:

  • apply
  • apply_async
from  multiprocessing import Process,Pool
import time

def Foo(i):
    time.sleep(2)
    return i+100

def Bar(arg):
    print(arg)

pool = Pool(5) #建立一個進程池
#print pool.apply(Foo,(1,))#去進程池裏去申請一個進程去執行Foo方法
#print pool.apply_async(func =Foo, args=(1,)).get()

for i in range(10):
    pool.apply_async(func=Foo, args=(i,),callback=Bar)

print('end')
pool.close()
pool.join()#進程池中進程執行完畢後再關閉,若是註釋,那麼程序直接關閉。

'''
apply 主動的去執行
pool.apply_async(func=Foo, args=(i,),callback=Bar) 至關於異步,當申請一個線程以後,執行FOO方法就無論了,執行完以後就在執行callback ,當你執行完以後,在執行一個方法告訴我執行完了
callback 有個函數,這個函數就是操做的Foo函數的返回值!
'''

 

協程:

首先要明確,線程和進程都是系統幫我們開闢的,無論是thread仍是process他內部都是調用的系統的API
而對於協程來講它和系統毫無關係!
他就和程序員有關係,對於線程和進程來講,調度是由CPU來決定調度的!
對於協程來講,程序員就是上帝,你想讓誰執行到哪裏他就執行到哪裏

協程存在的意義:對於多線程應用,CPU經過切片的方式來切換線程間的執行,線程切換時須要耗時(保存狀態,下次繼續)。協程,則只使用一個線程,在一個線程中規定某個代碼塊執行順序。

適用場景:其實在其餘語言中,協程的實際上是意義不大的多線程便可已解決I/O的問題,可是在python由於他有GIL(Global Interpreter Lock 全局解釋器鎖 )在同一時間只有一個線程在工做,因此:若是一個線程裏面I/O操做特別多,協程就比較適用

greenlet:

收先要明確,線程和進程都是系統幫我們開闢的,無論是thread仍是process他內部都是調用的系統的API
而對於協程來講它和系統毫無關係!
他就和程序員有關係,對於線程和進程來講,是否是有CPU來決定調度的!
對於協程來講,程序員就是上帝,你想讓誰執行到哪裏他就執行到哪裏

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
 
from greenlet import greenlet
 
 
def test1():
    print 12
    gr2.switch()#切換到協程2執行
    print 34 #2切回來以後,在這裏和yield相似
    gr2.switch() 
 
 
def test2():
    print 56
    gr1.switch()#上面執行了一句,在切換到協程1裏去了
    print 78
 
gr1 = greenlet(test1) #建立了一個協程
gr2 = greenlet(test2)

gr1.switch() #執行test1 

'''
比I/O操做,若是10個I/O,我程序從上往下執行,若是同時發出去了10個I/O操做,那麼返回的結果若是同時回來了2個
,是否是就節省了不少時間?

若是一個線程裏面I/O操做特別多,使用協程是否是就很是適用了!

若是一個線程訪問URL經過協程來作,協程告訴它你去請求吧,而後繼續執行,可是若是不用協程就得等待第一個請求完畢以後返回以後才
繼續下一個請求。

協程:把一個線程分紅了多個協程操做,每一個協程作操做
多線程:是把每個操做,分爲多個線程作操做,可是python中,在同一時刻只能有一個線程操做,而且有上下文切換。可是若是上下文切換很是頻繁的話
是很是耗時的,但對於協程切換就很是輕便了~
greenlet

協程就是對線程的分片,上面的例子須要手動操做可能用處不是很大瞭解原理,看下面的例子:

上面的greenlet是須要認爲的制定調度順序的,因此又出了一個gevent他是對greenlet功能進行封裝

遇到I/O自動切換:

from gevent import monkey; monkey.patch_all()
import gevent
import urllib2

def f(url):
    print('GET: %s' % url)
    resp = urllib2.urlopen(url) #當遇到I/O操做的時候就會調用協程操做,而後繼續往下走,而後這個協程就卡在這裏等待數據的返回
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),  #這裏的f是調用這個方法,第二個是調用方的參數
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
]) 

'''
gevent.spawn(f, 'https://www.python.org/'),  #這裏的f是調用這個方法,第二個是調用方的參數

當函數f裏的代碼遇到I/O操做的時候,函數就卡在哪裏等待數據的返回,可是協程不會等待而是繼續操做!
'''
相關文章
相關標籤/搜索