線程GIL鎖

1、多線程實現TCP的併發

TCP服務端實現併發
1.將不一樣的功能儘可能拆分紅不一樣的函數
   拆分出來的功能能夠被多個地方使用
2.將鏈接循環和通訊循環拆分紅不一樣的函數
3.將通訊循環作成多線程html

import socket
from threading import Thread
import time

"""
服務端
    1.要有固定的IP和PORT
    2.24小時不間斷提供服務
    3.可以支持併發
"""

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:break
            print(data.decode('utf-8'))
            time.sleep(10) #開啓一個客戶端的時候10s打印一個結果,多個客戶端就會有併發效果,開10個感受上10s打印10個
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

while True:
    conn, addr = server.accept()  # 監聽 等待客戶端的鏈接  阻塞態
    print(addr)
    t = Thread(target=talk,args=(conn,))
    t.start()
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    client.send(b'hello')
    data = client.recv(1024)
    print(data.decode('utf-8'))
客戶端

 

1、GIL(global interpreter lock)介紹(******)

1.GIL基本認識

1.  只有python有GIL嗎?python

GIL全局解釋器鎖目前是全部解釋型語言的通病不可調和,必須加鎖,編譯型語言在編譯的時候已經管理好了多線程運行的問題
有人給處理的數據加鎖,雖然能夠,可是那樣就會有不少鎖,出現大量的串行,給解釋器加鎖雖然也是串行,可是至少還能夠實現併發

web

2.  GIL全局解釋器鎖的存在是python的問題麼?
  GIL並非Python的特性,Python徹底能夠不依賴於GIL,只是Cpython的內存管理不是線程安全,就引入這一個概念,JPython就不存在安全

 

內存管理(垃圾回收機制)
        引用計數:  值與變量的綁定關係的個數
        標記清除:  當內存快要滿的時候 會自動中止程序的運行 檢測全部的變量與值的綁定關係
               給沒有綁定關係的值打上標記,最後一次性清除
        分代回收:  (垃圾回收機制也是須要消耗資源的,而正常一個程序的運行內部會使用到不少變量與值
                而且有一部分相似於常量,減小垃圾回收消耗的時間,應該對變量與值的綁定關係作一個分類)
                    新生代(5S)》》》青春代(10s)》》》老年代(20s)
                       垃圾回收機制掃描必定次數發現關係還在,會將該對關係移至下一代
                          隨着代數的遞增 掃描頻率是下降的多線程

3. 做用原理:併發

      將併發運行變成串行,犧牲效率來提升數據的安全(全部互斥鎖的本質)
      控制同一時間內共享數據只能被一個線程所修改(不能並行可是可以實現併發)app

4.緣由詳解

一個進程中必帶一個解釋器和一個垃圾回收線程
    一個進程下的多個線程都須要運行,就必須去調解釋器,垃圾回收線程也要用解釋器,
若是不加限制,若是回收機制和其餘線程同時使用解釋器,會同時執行,回收機制就可能誤刪掉那些剛建立還沒來得及綁定的變量資源
所以必須給解釋器加鎖,只能容許一個線程使用,我幹活的時候你滾一邊去,不要干擾我,這樣纔不會衝突

 二、多核單核與多線程

進程能夠利用多核,可是開銷大,python的多線程開銷小,但卻沒法利用多核優點,難道說說python多線程沒用了?dom

同一個進程下的多個線程雖不能實現並行,可是可以實現併發
多個進程下的線程可以實現並行,發揮多核優點socket

對計算來講,cpu越多越好,可是對於I/O來講,再多的cpu也沒用ide

固然對運行一個程序來講,隨着cpu的增多執行效率確定會有所提升(無論提升幅度多大,總會有所提升)

這是由於一個程序基本上不會是純計算或者純I/O

因此咱們只能相對的去看一個程序究竟是計算密集型仍是I/O密集型,從而進一步分析python的多線程到底有無用武之地

#分析:
咱們有四個任務須要處理,處理方式確定是要玩出併發的效果,解決方案能夠是:
方案一:開啓四個進程
方案二:一個進程下,開啓四個線程

#單核狀況下: 
  若是四個任務是計算密集型,沒有多核來並行計算,方案一徒增了建立進程的開銷,開線程牛逼   若是四個任務是I/O密集型,建立進程的開銷大,且進程的切換速度遠不如線程,開線程牛逼 #多核狀況下:
  若是四個任務是計算密集型,多核意味着並行計算,在python中一個進程中同一時刻只有一個線程執行用不上多核,開進程牛逼   若是四個任務是I/O密集型,再多的核去開再多進程仍是要一個個等IO的時間,也解決不了I/O問題,多線程節約了資源還交互進行提升效率,開線程牛逼
目前計算機都是多核,因此:

計算密集型:多線程還不如串行(沒有大量切換),多核多進程牛逼
IO密集型: 多線程明顯提升效率,多核沒屌用
多線程和多進程都有本身的優勢,要根據項目需求合理選擇
目前大多數軟件都是IO密集型:socket 爬蟲 web
也有計算密集型的:金融分析

 

# IO密集型
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2) #睡覺也是典型的IO操做


if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #獲取本機cpu數量
    start=time.time()
    for i in range(300):
        # p=Process(target=work) #用進程跑 耗時12.69s
        p=Thread(target=work) #用線程跑 耗時2.03s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))
IO密集型進程線程運行比較

 

# 計算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == '__main__':
    l=[]
    print(os.cpu_count())  # 本機爲4核
    start=time.time()
    for i in range(8):
        p=Process(target=work) #耗時24.21s
        # p=Thread(target=work) #耗時43.61s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))
計算密集型進程線程運行比較

 

2、GIL與普通互斥鎖 

對於不一樣的數據,要想保證安全,須要加不一樣的鎖處理
GIL並不能保證數據的安全,它是對Cpython解釋器加鎖,針對的是線程
保證的是同一個進程下多個線程之間的安全

 

#GIL內置存在,只容許一個線程經過,但數據依然不安全

from threading import Thread
import time
n = 100

def task():
    global n
    tmp = n
    time.sleep(0.1)
# 在睡覺的狀況下,輸出99,1拿到全局鎖遇到IO去睡覺時,GIL必須交出來,2搶到,1還沒運行完,0.1s足夠你們能搶一遍GIL,因此都拿到100去睡覺了
# 不睡覺的狀況下,輸出0,1搶到GIL沒有IO會一直拿着直到輸出後釋放,釋放後2才能搶到GIL才能繼續搞
    n = tmp -1

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()
print(n)

 

#加自定義鎖保證數據安全

from threading import Thread,Lock
import time
n = 100 mutex = Lock() def task():
    global n
    tmp = n  #99 放在這裏,tmp受到GIL控制,GIL尚未出計算結果就釋放,0.1s的睡眠時間GIL夠你們搞一遍了,也就是都獲得了tmp=100
    mutex.acquire()
    # tmp = n  #0 放在這裏,tmp受到自定義鎖控制,自定義鎖必須在運行結束才能釋放,你們拿到的n都是上次結果-1
    time.sleep(0.1)
    n = tmp -1 mutex.release()

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

print(n)

 

3、死鎖、遞歸鎖現象

進程也有死鎖與遞歸鎖,在進程那裏忘記說了,放到這裏一切說了額

1.只要類加括號實例化對象
不管傳入的參數是否同樣生成的對象確定不同,不信你打印id
單例模式除外
mutexA = Lock() mutexB = Lock()
mutexA 和 mutexB 可不是同樣東西
2.鏈式賦值出來的對象那但是一毛同樣 mutexA = mutexB = RLock() # A B如今是同一把鎖

 

1. lock 死鎖現象

lock鎖 一次acquire必須對應一次release,不能連續acquire

所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,(你拿了我想要的鎖,我拿了你想要的鎖)

若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程

以下就是死鎖:

from threading import Thread,Lock,current_thread,RLock
import time
"""
自定義鎖一次acquire必須對應一次release,不能連續acquire 
本身千萬不要輕易的處理鎖的問題  
"""
mutexA = Lock()
mutexB = Lock()

class MyThread(Thread):
    def run(self):  # 建立線程自動觸發run方法 run方法內調用func1 func2至關於也是自動觸發
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s搶到了A鎖'%self.name)  # self.name等價於current_thread().name
        mutexB.acquire()
        print('%s搶到了B鎖'%self.name)
        mutexB.release()
        print('%s釋放了B鎖'%self.name)
        mutexA.release()
        print('%s釋放了A鎖'%self.name)

    def func2(self):
        mutexB.acquire()
        print('%s搶到了B鎖'%self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s搶到了A鎖' % self.name)
        mutexA.release()
        print('%s釋放了A鎖' % self.name)
        mutexB.release()
        print('%s釋放了B鎖' % self.name)

for i in range(3):
    t = MyThread()
    t.start()
'''
Thread-1搶到了A鎖
Thread-1搶到了B鎖
Thread-1釋放了B鎖
Thread-1釋放了A鎖
Thread-1搶到了B鎖
Thread-2搶到了A鎖
#到這裏以後就出現死鎖了,你想要的個人鎖我想要你的鎖
'''
通常的lock 死鎖現象

2.Rlock遞歸鎖  解決死鎖

解決死鎖方法,遞歸鎖,在Python中爲了支持在同一線程中屢次請求同一資源,python提供了可重入鎖RLock。

這個RLock內部維護着一個Lock和一個counter變量,counter記錄了acquire的次數,從而使得資源能夠被屢次require。直到一個線程全部的acquire都被release,其餘的線程才能得到資源。上面的例子若是使用RLock代替Lock,則不會發生死鎖:

Rlock能夠被第一個搶到鎖的人連續的acquire和release
每acquire一次鎖身上的計數加1
每release一次鎖身上的計數減1
只要鎖的計數不爲0 其餘人都不能搶
from threading import Thread,Lock,current_thread,RLock
import time
"""
Rlock能夠被第一個搶到鎖的人連續的acquire和release
每acquire一次鎖身上的計數加1
每release一次鎖身上的計數減1
只要鎖的計數不爲0 其餘人都不能搶

"""
mutexA = mutexB = RLock()  # A B如今是同一把鎖,搶鎖以後會有一個計數 搶一次計數加一 針對的是第一個搶到個人人
print(id(mutexB)) #35379080
print(id(mutexA)) #35379080

class MyThread(Thread):
    def run(self):  # 建立線程自動觸發run方法 run方法內調用func1 func2至關於也是自動觸發
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s搶到了A鎖'%self.name)  # self.name等價於current_thread().name
        mutexB.acquire()
        print('%s搶到了B鎖'%self.name)
        mutexB.release()
        print('%s釋放了B鎖'%self.name)
        mutexA.release()
        print('%s釋放了A鎖'%self.name)

    def func2(self):
        mutexB.acquire()
        print('%s搶到了B鎖'%self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s搶到了A鎖' % self.name)
        mutexA.release()
        print('%s釋放了A鎖' % self.name)
        mutexB.release()
        print('%s釋放了B鎖' % self.name)

for i in range(3):
    t = MyThread()
    t.start()
Rlock 遞歸鎖

4、Semaphore  信號量 

信號量可能在不一樣的領域中 對應不一樣的知識點

互斥鎖:一個廁所(一個坑位)
信號量:公共廁所(多個坑位)

from threading import Semaphore,Thread
import time
import random


sm = Semaphore(3)  # 造了一個含有五個的坑位的公共廁所

def task(name):
    sm.acquire()
    print('%s佔了一個坑位'%name)
    time.sleep(random.randint(1,30))
    print('%s放出一個坑位' % name)
    sm.release()

for i in range(5):
    t = Thread(target=task,args=(i,))
    t.start()
'''
0佔了一個坑位
1佔了一個坑位
2佔了一個坑位

2放出一個坑位
3佔了一個坑位
0放出一個坑位
4佔了一個坑位
1放出一個坑位
3放出一個坑位
4放出一個坑位
'''
Semaphore 搶廁所

5、event事件  子等子

from threading import Event,Thread
import time

# 先生成一個event對象
e = Event()
def light():
    print('紅燈正亮着')
    time.sleep(3)
    e.set()  # 發信號
    print('綠燈亮了')

def car(name):
    print('%s正在等紅燈'%name)
    e.wait()  # 等待信號
    print('%s加油門飆車了'%name)

t = Thread(target=light)
t.start()

for i in range(3):
    t = Thread(target=car,args=('傘兵%s'%i,))
    t.start()
'''
紅燈正亮着
傘兵0正在等紅燈
傘兵1正在等紅燈
傘兵2正在等紅燈
綠燈亮了
傘兵1加油門飆車了
傘兵2加油門飆車了
傘兵0加油門飆車了

'''
event set wait 等紅綠燈

6、堆棧(先進後出)、自定義優先級隊列

同一個進程下的多個線程原本就是數據共享 爲何還要用隊列

由於隊列是管道+鎖 使用隊列你就不須要本身手動操做鎖的問題

由於鎖操做的很差極容易產生死鎖現象
1.正常隊列 先進先出
q = queue.Queue()
q.put('hahha')
print(q.get())

2.先進後出
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())  #3

3.自定義優先級 越小越優先
q = queue.PriorityQueue()
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())  #(-10, 'yyyy')
相關文章
相關標籤/搜索