併發編程小結

1、到底什麼是線程?什麼是進程?

Python本身沒有這玩意,Python中調用的操做系統的線程和進程。git

2、Python多線程狀況下:

計算密集型操做:效率低,Python內置的一個全局解釋器鎖,鎖的做用就是保證同一時刻一個進程中只有一個線程能夠被cpu調度,多線程沒法利用多核優點,能夠經過多進程方式解決,可是比較浪費資源。
IO操做:效率高程序員

3、Python多進程的狀況下:

計算密集型操做:效率高(浪費資源),不得已而爲之。
IO操做:效率高(浪費資源)github

4、爲何有這把GIL鎖?

Python語言的創始人在開發這門語言時,目的快速把語言開發出來,若是加上GIL鎖(C語言加鎖),切換時按照100條字節指令來進行線程間的切換。web

5、Python中線程和進程(GIL鎖)

GIL鎖,全局解釋器鎖。用於限制一個進程中同一時刻只有一個線程被cpu調度。
擴展:默認GIL鎖在執行100個cpu指令(過時時間)。
查看GIL切換的指令個數多線程

import sys
v1 = sys。getcheckinterval()
print(v1)

6、爲何要建立線程?

因爲線程是cpu工做的最小單元,建立線程能夠利用多核優點實現並行操做(Java/C#)。
注意:線程是爲了工做。併發

7、爲何要建立進程?

進程和進程之間作數據隔離(Java/C#)。app

注意:進程是爲了提供環境讓線程工做。框架

8、進程和線程的區別?

進程是資源分配的最小單位,線程是程序執行的最小單位。異步

進程有本身的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,創建數據表來維護代碼段,堆棧段和數據段,這種操做很是昂貴。而線程是共享進程中的數據的,使用相同的地址空間,所以CPU切換一個線程的花費遠比進程小不少,同時建立一個線程的開銷比進程要小不少。

線程之間的通訊更方便,同一進程下的線程共享全局變量,靜態變量,而進程之間的通訊須要以通訊的方式(IPC)進行。不過如何處理好同步與互斥是編寫多線程程序的難點。

可是多進程程序更健壯,多線程程序只要有一個線程死掉,整個進程也死掉了,而一個進程死掉並不會對另一個進程形成影響,由於進程有本身獨立的地址空間。

也正是因爲GIL鎖的緣由:IO密集型操做可使用多線程,計算密集型可使用多進程。

9、線程建立的越多越好嗎?

很差。線程之間進行切換時,要作上下文管理。

10、生產者消費者模型解決了什麼問題?

不用一直等待的問題。

11、Lock和RLock的區別?

RLock能夠屢次加鎖。

12、進程和線程以及協程的區別?

進程是cpu資源分配的最小單元,一個進程中能夠有多個線程。

線程是cpu計算的最小單元。

對於Python來講他的進程和線程和其餘語言有差別,是有GIL鎖。

GIL鎖保證一個進程中同一時刻只有一個線程被cpu調度。

注意:IO密集型操做可使用多線程,計算密集型可使用多進程。

協程,是由程序員創造出來的一個不是真實存在的東西。

協程,是微線程,對一個線程進程分片,使得線程在代碼塊之間進行來回切換執行,而不是在原來逐行執行。

十3、IO多路複用做用?

檢測多個socket是否已經發生變化(是否已經鏈接成功/是否已經獲取數據)(可讀/可寫)IO多路複用做用?

檢測多個socket是否發生變化。

操做系統檢測socket是否發生變化,有三種模式:

  • select:最多1024個socket;循環去檢測。
  • poll:不限制監聽socket個數;循環去檢測(水平觸發)。
  • epoll:不限制監聽socket個數;回調方式(邊緣觸發)。

Python模塊:

  • select.select
  • select.epoll

十4、socket默認是不是阻塞的?阻塞體如今哪裏?

默認是阻塞,填在等待消息和鏈接

十5、什麼是異步非阻塞?

非阻塞,不等待。好比建立socket對某個地址進行connect、獲取接收數據recv時默認都會等待(鏈接成功或接收到數據),才執行後續操做。
若是設置setblocking(False),以上兩個過程就再也不等待,可是會報BlockingIOError的錯誤,只要捕獲便可。

異步,通知,執行完成以後自動執行回調函數或自動執行某些操做(通知)。好比作爬蟲中向某個地址baidu。com發送請求,當請求執行完成以後自執行回調函數。

十6、什麼是同步阻塞?

  • 阻塞:等
  • 同步:按照順序逐步執行

十7、什麼是協程?

協程也能夠稱爲「微線程」,就是開發者控制線程執行流程,控制先執行某段代碼而後再切換到另外函執行代碼...來回切換。

十8、協程能夠提升併發嗎?

協程本身自己沒法實現併發(甚至性能會下降)。
協程+IO切換性能提升。

十9、提升併發方案:

  • 多進程
  • 多線程
  • 單線程提供併發

二10、單線程提供併發

20.1 基於IO多路複用+socket非阻塞

實現併發請求(一個線程100個請求)

import socket
# 建立socket
client = socket.socket()
# 將原來阻塞的位置變成非阻塞(報錯)
client.setblocking(False)

# 百度建立鏈接: 阻塞
try:
    # 執行了但報錯了
    client.connect(('www.baidu.com',80))
except BlockingIOError as e:
    pass

# 檢測到已經鏈接成功

# 問百度我要什麼?
client.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')

# 我等着接收百度給個人回覆
chunk_list = []
while True:
    # 將原來阻塞的位置變成非阻塞(報錯)
    chunk = client.recv(8096) 
    if not chunk:
        break
    chunk_list.append(chunk)

body = b''.join(chunk_list)
print(body.decode('utf-8'))

20.2 協程+IO切換:gevent

from gevent import monkey
# 之後代碼中遇到IO都會自動執行greenlet的switch進行切換
monkey.patch_all() 
import requests
import gevent


def get_page1(url):
    ret = requests.get(url)
    print(url,ret.content)

def get_page2(url):
    ret = requests.get(url)
    print(url,ret.content)

def get_page3(url):
    ret = requests.get(url)
    print(url,ret.content)

gevent.joinall([
    gevent.spawn(get_page1, 'https://www.python.org/'), # 協程1
    gevent.spawn(get_page2, 'https://www.yahoo.com/'),  # 協程2
    gevent.spawn(get_page3, 'https://github.com/'),     # 協程3
])

20.3 基於事件循環的異步

執行完某我的物後自動調用我給他的函數,非阻塞

20.3.1 方法一

import socket
import select

# 百度建立鏈接:非阻塞
client1 = socket.socket()
client1.setblocking(False)
try:
    client1.connect(('www.baidu.com', 80))
except BlockingIOError as e:
    pass

# 搜狗建立鏈接:非阻塞
client2 = socket.socket()
client2.setblocking(False)
try:
    client2.connect(('www.sogou.com', 80))
except BlockingIOError as e:
    pass

# GitHub建立鏈接:非阻塞
client3 = socket.socket()
client3.setblocking(False)
try:
    client3.connect(('www.github.com', 80))
except BlockingIOError as e:
    pass

# 建立socket列表:socket_list
socket_list = [client1, client2, client3]
# 建立connect列表:conn_list
conn_list = [client1, client2, client3]

while True:
    rlist, wlist, elist = select.select(socket_list, conn_list, [], 0.005)
    # rlist中表示已近獲取數據的socket對象
    # wlist中表示已經鏈接成功的socket對象
    # elist中表示出現錯誤的socket對象
    for sk in wlist:
        if sk == client1:
            sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
        elif sk == client2:
            sk.sendall(b'GET /web?query=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n')
        else:
            sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n')
        conn_list.remove(sk)
    for sk in rlist:
        chunk_list = []
        while True:
            try:
                chunk = sk.recv(8096)
                if not chunk:
                    break
                chunk_list.append(chunk)
            except BlockingIOError as e:
                break
        body = b''.join(chunk_list)
        # print(body.decode('utf-8'))
        print('------------>', body)
        sk.close()
        socket_list.remove(sk)
    if not socket_list:
        break

20.3.2 方法二

import socket
import select

class Req(object):
    def __init__(self,sk,func):
        self.sock = sk
        self.func = func

    def fileno(self):
        return self.sock.fileno()


class Nb(object):

    def __init__(self):
        self.conn_list = []
        self.socket_list = []

    def add(self,url,func):
        # 建立socket客戶端
        client = socket.socket()
        # 非阻塞
        client.setblocking(False)
        try:
            # 建立鏈接
            client.connect((url, 80))
            # 異常處理
        except BlockingIOError as e:
            pass
        obj = Req(client,func)
        # 鏈接列表
        self.conn_list.append(obj)
        # socket列表
        self.socket_list.append(obj)

    def run(self):

        while True:
            rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005)
            # wlist中表示已經鏈接成功的req對象
            for sk in wlist:
                # 發生變換的req對象
                sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
                self.conn_list.remove(sk)
            for sk in rlist:
                chunk_list = []
                while True:
                    try:
                        chunk = sk.sock.recv(8096)
                        if not chunk:
                            break
                        chunk_list.append(chunk)
                    except BlockingIOError as e:
                        break
                body = b''.join(chunk_list)
                # print(body.decode('utf-8'))
                sk.func(body)
                sk.sock.close()
                self.socket_list.remove(sk)
            if not self.socket_list:
                break


def baidu_repsonse(body):
    print('百度下載結果:',body)

def sogou_repsonse(body):
    print('搜狗下載結果:', body)

def github_repsonse(body):
    print('GITHUB下載結果:', body)


t1 = Nb()
t1.add('www.baidu.com',baidu_repsonse)
t1.add('www.sogou.com',sogou_repsonse)
t1.add('www.github.com',oldboyedu_repsonse)
t1.run()

20.4 基於事件循環的異步非阻塞框架

如Twisted框架,scrapy框架(單線程完成併發)

相關文章
相關標籤/搜索