進程隊列補充、socket實現服務器併發、線程完結

1.隊列補充

隊列內部是管道+鎖(數據在隊列中是阻塞的)面試

2.關於python併發與並行的補充

解釋型語言單個進程下多個線程不能夠並行,可是向C語言等其餘語言中在多核狀況下是能夠實現並行的,全部語言在單核下都是沒法實現並行的,只能併發。數據庫

3.TCP服務端實現併發

#服務端
import socket
from threading import Thread



server = socket.socket()
server.bind(('127.0.0.1',6666))
server.listen(5)
def serv(conn,addr):
    while True:
        try:
            print(addr)
            rec_data = conn.recv(1024).decode('utf-8')
            print(rec_data)
            send_data = rec_data.upper()
            conn.send(send_data.encode('utf-8'))
        except Exception as e:
            print(e)
            break

while True:
    conn,addr = server.accept()
    t = Thread(target=serv,args=(conn,addr))
    t.start()
#客戶端
import socket
import time

client = socket.socket()
client.connect(('127.0.0.1',6666))
while True:
    client.send(b'hello')
    data = client.recv(1024)
    print(data)
    time.sleep(1)

4.GIL全局解釋器鎖

在CPython中,全局解釋器鎖(即GIL)是一個互斥鎖,能夠防止一個進程中的多個線程同時(並行)執行。 鎖定是必要的,主要是由於CPython的內存管理不是線程安全的。GIL的存在就是爲了保證線程安全安全

什麼是保證線程安全呢?

同一進程的全部線程都運行在一個進程內,毫無疑問這些線程具備如下幾個特色:多線程

一、全部數據都是共享的,這其中,代碼做爲一種數據也是被全部線程共享的(test.py的全部代碼以及Cpython解釋器的全部代碼)
例如:test.py定義一個函數work(代碼內容以下圖),在進程內全部線程都能訪問到work的代碼,因而咱們能夠開啓三個線程而後target都指向該代碼,能訪問到意味着就是能夠執行。
二、全部線程的任務,都須要將任務的代碼當作參數傳給解釋器的代碼去執行,即全部的線程要想運行本身的任務,首先須要解決的是可以訪問到解釋器的代碼。
綜上:併發

若是多個線程的target=work,那麼執行流程是:app

多個線程先訪問到解釋器的代碼,即拿到執行權限,而後將target的代碼交給解釋器的代碼去執行socket

解釋器的代碼是全部線程共享的,因此垃圾回收線程也可能訪問到解釋器的代碼而去執行,這就致使了一個問題:對於同一個數據100,可能線程1執行x=100的同時,而垃圾回收執行的是回收100的操做,解決這種問題沒有什麼高明的方法,就是加鎖處理,以下圖的GIL,保證python解釋器同一時間只能執行一個任務的代碼。tcp

GIL與Lock

機智的同窗可能會問到這個問題:Python已經有一個GIL來保證同一時間只能有一個線程來執行了,爲何這裏還須要lock?函數

首先,咱們須要達成共識:鎖的目的是爲了保護共享的數據,同一時間只能有一個線程來修改共享的數據

而後,咱們能夠得出結論:保護不一樣的數據就應該加不一樣的鎖。

最後,問題就很明朗了,GIL 與Lock是兩把鎖,保護的數據不同,前者是解釋器級別的(固然保護的就是解釋器級別的數據,好比垃圾回收的數據),後者是保護用戶本身開發的應用程序的數據,很明顯GIL不負責這件事,只能用戶自定義加鎖處理,即Lock,以下圖

注意:多個線程過來執行,一旦遇到IO操做就會立馬釋放GIL解釋器鎖,交個下一個先進來的線程。

在純計算程序中GIL鎖起到鎖定python解釋器的做用,就是一個線程搶到解釋器後不會再有其餘線程搶到解釋器的使用權(直到這個程序遇到IO操做,這時GIL會釋放解釋器的使用權)。

import time
from threading import Thread,current_thread


number = 100

def task():
    global number
    number2 = number
    number = number2 - 1
    print(number,current_thread().name)

for line in range(100):
    t = Thread(target=task)
    t.start()
    
99 Thread-1
98 Thread-2
97 Thread-3
96 Thread-4
95 Thread-5
94 Thread-6
93 Thread-7
92 Thread-8
91 Thread-9
90 Thread-10
。
。
。

給這個程序加上IO操做看下打印結果:

import time
from threading import Thread,current_thread


number = 100

def task():
    global number
    number2 = number
    #print(number)
    time.sleep(1)
    number = number2 - 1
    #time.sleep(1)#若是sleep放在這裏,則會打印結果都是0,這是由於線程間數據是共享的
    print(number,current_thread().name)

for line in range(100):
    t = Thread(target=task)
    t.start()
    
99 Thread-24
99 Thread-23
99 Thread-22
99 Thread-20
99 Thread-18
99 Thread-17
99 Thread-15
99 Thread-21
99 Thread-14
.
.
.

5.驗證多線程的做用

何時使用多線程,何時使用多進程,多線程和多進程各有什麼優缺點?

結論:在計算密集型程序中使用多進程,這時可以充分發揮計算機多核的優點;在IO密集型的程序中使用多線程,這時可以充分發揮多線程對CPU高校利用率的優點。高效執行多個進程,內有多個IO密集型程序,要使用多進程+多線程。

對結論的驗證:

運算密集型操做驗證:

from threading import Thread
from multiprocessing import Process
import os,time

#計算密集型多進程下運行
def work1():
    number = 0
    for line in range(50000000):
        number += 1


if __name__ == '__main__':
    #測試計算密集型
    print(os.cpu_count())#打印CPU有幾個內核
    start_time = time.time()
    list1 = []
    for line in range(4):
        p = Process(target=work1)
        list1.append(p)
        p.start()
        #p.join()#join若是放在這裏比在列表裏執行速度慢,是由於在列表裏是等全部的進程都起來以後再告訴系統要加join,而在第一個for循環裏面則是每起一個進程都會告訴系統一次,這個過程須要時間。

    for p in list1:
        p.join()
    end_time = time.time()
    print(f'程序執行的時間{end_time - start_time}')
#12.24461030960083


#多線程下運行
from threading import Thread
from multiprocessing import Process
import os,time

#計算密集型
def work1():
    number = 0
    for line in range(50000000):
        number += 1
#IO密集型
def work2():
    time.sleep(1)

if __name__ == '__main__':
    #測試計算密集型
    print(os.cpu_count())#打印CPU有幾個內核
    start_time = time.time()
    list1 = []
    for line in range(4):
        p = Thread(target=work1)
        list1.append(p)
        p.start()
        #p.join()#4.407487869262695

    for p in list1:
        p.join()
    end_time = time.time()
    print(f'程序執行的時間{end_time - start_time}')
#16.305360794067383

這裏本人的測試結果是在計算的數據比較大時開啓多進程纔會有優點,若是運算數據比較小,開啓多線程運算速度反而比開啓多進程快得多。

IO密集型操做驗證:

#多進程測試

from threading import Thread
from multiprocessing import Process
import os,time


#IO密集型
def work2():
    time.sleep(1)

if __name__ == '__main__':
    #測試IO密集型
    print(os.cpu_count())#打印CPU有幾個內核
    start_time = time.time()
    list1 = []
    for line in range(4):
        p = Process(target=work2)
        list1.append(p)
        p.start()
        #p.join()#4.407487869262695

    for p in list1:
        p.join()
    end_time = time.time()
    print(f'程序執行的時間{end_time - start_time}')
#2.642327070236206


#多線程測試
from threading import Thread
from multiprocessing import Process
import os,time


#IO密集型
def work2():
    time.sleep(1)

if __name__ == '__main__':
    #測試IO密集型
    print(os.cpu_count())#打印CPU有幾個內核
    start_time = time.time()
    list1 = []
    for line in range(4):
        p = Thread(target=work2)
        list1.append(p)
        p.start()
        #p.join()#4.407487869262695

    for p in list1:
        p.join()
    end_time = time.time()
    print(f'程序執行的時間{end_time - start_time}')
#1.0189073085784912

能夠看出在IO密集型操做中,開相同數量的程要比開相同數量的線程執行速度慢

6.死鎖現象

所謂死鎖:是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程,以下就是死鎖。

from threading import Lock,Thread,current_thread
import time


mutex_a = Lock()#實例化一把鎖
mutex_b = Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutex_a.acquire()
        print(f'用戶{self.name}搶到了鎖a')
        mutex_b.acquire()
        print(f'用戶{self.name}搶到了鎖b')
        mutex_b.release()
        print(f'用戶{self.name}釋放了鎖b')
        mutex_a.release()
        print(f'用戶{self.name}釋放了鎖a')

    def func2(self):
        mutex_b.acquire()
        print(f'用戶{self.name}搶到了鎖b')
        time.sleep(1)
        mutex_a.acquire()
        print(f'用戶{self.name}搶到了鎖a')
        mutex_a.release()
        print(f'用戶{self.name}釋放了鎖a')
        mutex_b.release()
        print(f'用戶{self.name}釋放鎖b')

for line in range(10):
    t = MyThread()
    t.start()
用戶Thread-1搶到了鎖a
用戶Thread-1搶到了鎖b
用戶Thread-1釋放了鎖b
用戶Thread-1釋放了鎖a
用戶Thread-1搶到了鎖b
用戶Thread-2搶到了鎖a

當線程1搶到了b而線程2搶到了a時,線程1要執行強a的任務,而線程2要執行搶b的任務,而兩把鎖都沒有釋放手中的鎖,因此就形成了死鎖的現象。

7.遞歸鎖

遞歸鎖用於解決死鎖問題,遞歸鎖能夠被多個線程使用,當第一個線程使用時,遇到幾把鎖,它的引用計數就爲幾,只有當它的引用計數爲零時纔會給第二個線程使用。這樣擁有遞歸鎖的那個線程就能夠將本身的鎖裏的內容執行完而後,其餘使用遞歸鎖的線程才能夠執行。也就是可是第一個使用這把鎖的線程會對這把鎖加一個引用計數,只有引用計數爲零時才能真正釋放該鎖。

from threading import Lock,Thread,RLock
import time


mutex_a = mutex_b = RLock()#實例化一把遞歸鎖
class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutex_a.acquire()
        print(f'用戶{self.name}搶到了鎖a')
        mutex_b.acquire()
        print(f'用戶{self.name}搶到了鎖b')
        mutex_b.release()
        print(f'用戶{self.name}釋放了鎖b')
        mutex_a.release()
        print(f'用戶{self.name}釋放了鎖a')

    def func2(self):
        mutex_b.acquire()
        print(f'用戶{self.name}搶到了鎖b')
        time.sleep(0.1)
        mutex_a.acquire()
        print(f'用戶{self.name}搶到了鎖a')
        mutex_a.release()
        print(f'用戶{self.name}釋放了鎖a')
        mutex_b.release()
        print(f'用戶{self.name}釋放鎖b')

for line in range(10):
    t = MyThread()
    t.start()

8.信號量(瞭解)

互斥鎖比喻成一個家用馬桶,同一時間只能一我的用;信號量比喻成一個公廁,同一時間能夠有多我的用。

from threading import Semaphore,Lock,current_thread,Thread
import time

sm = Semaphore(5)#5個馬桶
#mutex = Lock()#一個馬桶
def task():
    sm.acquire()
    print(f'{current_thread().name}執行任務')
    time.sleep(1)
    sm.release()

for line in range(20):
    t = Thread(target=task)
    t.start()
#這段代碼的功能是每次讓五個線程併發執行

9.線程隊列

線程Q:線程隊列 FIFO(先進先出)就是隊列,面試會問。

queue 是python解釋器自帶的模塊,進程中的Queue是python解釋器自帶的模塊multiprocessing裏面的一個類。

普通隊列(FIFO):先進先出
特殊隊列(LIFO):後進先出

import queue
q = queue.Queue()#先進先出
q.put(1)
q.put(2)
print('q',q.get())

q1 = queue.LifoQueue()#先進後出
q1.put(1)
q1.put(2)
print('q1',q1.get())

q 1
q1 2

優先級隊列
優先級根據數字來判斷,數字爲幾優先級就爲幾,1的優先級最高
若參數中傳的是元組,優先級以第一個元素的數字大小爲準,若是元組的第一個元素都爲字母則以字母的ASCII碼爲準,若是第一個元素的優先級相同就判斷第二個元素的順序(漢字也會判斷順序)可是若是同一列的元素既有數字又有字母會報錯。

import queue
q2 = queue.PriorityQueue()#優先級隊列

q2.put((1,2,3))
q2.put((2,2,3))
q2.put((3,2,3))
print(q2.get())

(1, 2, 3)

句柄

句柄(handle),有多種意義,第一種解釋:句柄是一種特殊的智能指針。當一個應用程序要引用其餘系統(如數據庫、操做系統)所管理的內存塊或對象時,就要使用句柄。

相關文章
相關標籤/搜索