1. 程式人生 > 其它 >TREEQN AND ATREEC: DIFFERENTIABLE TREE-STRUCTURED MODELS FOR DEEP REINFORCEMENT LEARNING

TREEQN AND ATREEC: DIFFERENTIABLE TREE-STRUCTURED MODELS FOR DEEP REINFORCEMENT LEARNING

內容概要

  • 訊息佇列
  • IPC機制(程序間通訊)
  • 生產者消費者模型
  • 執行緒理論*
  • 開設執行緒的兩種方法
  • 執行緒實現TCP服務併發
  • 執行緒的join方法
  • 執行緒間的資料共享
  • 守護執行緒
  • GIL全域性直譯器

訊息佇列

# 目前我們先不直接學習訊息佇列 先來學習內建的佇列
"""
佇列: 先進先出(使用的頻率較高)
堆疊: 先進後出(在特定的情況下使用)
"""
# 訊息佇列並不需要什麼都要自己寫,我們可以直接使用別人封裝好的
"可以實現各種資料傳輸"

from multiprocessing import Queue

q = Queue(5)  # 自定義佇列的長度
# put 朝佇列中存放資料
q.put(111)
q.put(222)
q.put(333)
# full 判斷佇列是否滿了
print(q.full())  # False  
q.put(444)
q.put(555)
print(q.full())  # True
# q.put(666)  # 超出最大長度 原地阻塞等待佇列中出現空位
# get 獲取佇列中的值
print(q.get())  # 111
print(q.get())  # 222
print(q.empty())  # False  判斷佇列是否空了
print(q.get())  # 333
print(q.get())  # 444
print(q.get())  # 555
print(q.empty())  # True
print(q.get())  # 佇列中沒有值 繼續獲取則阻塞等待佇列中給值
# print(q.get_nowait())  # 佇列中如果沒有值 直接報錯

"""
    full()
    empty()
    get_nowait()
上述方法能否在併發的場景下精準使用???
    不能用!!!
    
之所以介紹佇列是因為它可以支援程序間資料通訊
"""

IPC機制(程序間通訊)

"""
IPC機制可實現:
	1.主程序與子程序資料互動
	2.兩個子程序間資料互動
本質:不同記憶體空間的程序資料互動
"""
from multiprocessing import Process, Queue

def producer(q):
    print('子程序producer從佇列中取值>>>:', q.get())  # 取主程序往佇列中存的值
    q.put('子程序producer往佇列中新增值')  # 往佇列裡存值

def consumer(q):
    print('子程序consumer從佇列中取值>>>:', q.get())  # 從佇列中取值

if __name__ == '__main__':
    q = Queue()  # 訊息佇列預設長度
    p = Process(target=producer, args=(q, ))
    p1 = Process(target=consumer, args=(q,))
    p.start()  # 建立程序
    p1.start()  # 建立程序
    q.put(123)  # 主程序往佇列中存放資料123
    print('主程序')

生產者消費者模型

# 生產者
	負責生產、製作資料
# 消費者
	負責消費、處理資料
"""
如在爬蟲領域中:
	先通過程式碼爬取網頁資料(爬取資料的程式碼就可以稱為生產者)
	之後針對網頁資料進行篩選處理(處理網頁的程式碼就可以稱為消費者)
如使用程序演示:
	除了需要至少兩個程序外還需要一個媒介(訊息佇列)

以後遇到該模型需要考慮的問題其實就是供需平衡的問題
	生產力與消費力要均衡
"""
from multiprocessing import Process, Queue, JoinableQueue
import time
import random


def producer(name, food, q):
    for i in range(1,6):
        data = f'{name}生產了{food}{i}'
        print(data)
        time.sleep(random.randint(1, 3))  # 模擬產生過程
        q.put(data)  # 存入佇列


def consumer(name, q):
    while True:
        food = q.get()  # 從佇列中獲取
        # if food == None:
        #     print('完蛋了 沒得吃了 要餓死人了')
        #     break
        time.sleep(random.random())
        print(f'{name}吃了{food}')
        q.task_done()  # 每次去完資料必須給佇列一個反饋


if __name__ == '__main__':
    # q = Queue()
    q = JoinableQueue()
    p1 = Process(target=producer, args=('大廚1', '韭菜炒蛋', q))
    p2 = Process(target=producer, args=('主廚', '祕製小漢堡', q))
    c1 = Process(target=consumer, args=('客人1', q))
    c2 = Process(target=consumer, args=('客人2', q))
    c1.daemon = True
    c2.daemon = True
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    # 生產者生產完所有資料之後 往佇列中新增結束的訊號
    p1.join()
    p2.join()
    # q.put(None)  # 結束訊號的個數要跟消費者個數一致才可以
    # q.put(None)
    """佇列中其實已經自己加了鎖 所以多程序取值也不會衝突 並且取走了就沒了"""
    q.join()  # 等待佇列中資料全部被取出(一定要讓生產者全部結束才能判斷正確)
    """執行完上述的join方法表示消費者也已經消費完資料了"""
    print('主執行緒')
'''
JoinableQueue 的例項p除了與Queue物件相同的方法之外,還具有以下方法
    q.task_done() 
    使用者使用此方法發出訊號,表示q.get()返回的專案已經被處理。
    如果呼叫此方法的次數大於從佇列中刪除的專案數量,將引發ValueError異常。

    q.join() 
    生產者將使用此方法進行阻塞,直到佇列中所有專案均被處理。
    阻塞將持續到為佇列中的每個專案均呼叫q.task_done()方法為止。 
'''

執行緒理論*

# 什麼是執行緒
	程序:資源單位
	執行緒:執行單位(真正幹活的)
        程序相當於車間(一個個空間),執行緒相當於車間裡面的流水線(真正幹活的)
'''一個程序中至少有一個執行緒'''
"""
程序僅僅是在記憶體中開闢一塊空間地址(提供執行緒工作所需的資源)
執行緒真正被CPU執行,執行緒需要的資源跟所在的程序要
"""
# 為什麼要有執行緒
	開設執行緒的消耗遠遠比程序要小的多
    開程序:
    	1.申請記憶體空間
    	2.拷貝程式碼
    開執行緒:
        一個程序內可以開設多個執行緒 無需申請記憶體空間、拷貝程式碼
        一個程序內的多個執行緒是可以實現資料共享的
        
"""
開發一個文字編輯器
	獲取使用者輸入並實時展示到螢幕上
	並實時儲存到硬碟中
多種功能應該開設多執行緒而不是多程序
"""

開設執行緒的兩種方式

"""程序與執行緒的程式碼實操幾乎是一樣的"""
from threading import Thread  # 匯入執行緒模組
import time

# 方式一
def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is over')

# 建立執行緒無需在__main__下面編寫 但是為了統一 還是習慣在子程式碼中寫
t = Thread(target=task, args=('task', ))
t.start()  # 建立執行緒的開銷極小 幾乎是一瞬間就可以建立
print('主執行緒')

# 方式二
class MyThread(Thread):
    def __init__(self, username):
        super().__init__()
        self.username = username
    def run(self):
        print(f'{self.username} jason is running')
        time.sleep(3)
        print(f'{self.username} is over')

t = MyThread('task')
t.start()
print('主執行緒')

執行緒實現TCP服務端的併發

服務端

"""仔細體會開設程序和執行緒的本質區別"""
import socket
from threading import Thread

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

def talk(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())

while True:
    sock, addr = server.accept()
    # 每類一個客戶端就建立一個執行緒做資料互動
    t = Thread(target=talk, args=(sock,))
    t.start()

客戶端

"""
客戶端可以多開幾個 模擬併發
"""
import socket

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


while True:
    client.send(b'hello baby')
    data = client.recv(1024)
    print(data.decode('utf8'))

執行緒的join方法

from threading import Thread
import time

def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is over')

t = Thread(target=task, args=('task', ))
t.start()
t.join()  # 主執行緒程式碼等待子執行緒程式碼執行完畢之後再往下執行
print('主執行緒')
"""
主執行緒為什麼要等著子執行緒結束才會結束整個程序  
    因為主執行緒結束也就標誌著整個程序的結束 要確保子執行緒執行過程中所需的各項資源
"""

同一個程序內的多個執行緒資料共享

from threading import Thread

money = 10000000000
def task():
    global money
    money = 1

t = Thread(target=task)
t.start()
t.join()
print(money)

執行緒物件屬性和方法

1.驗證一個程序下的多個執行緒是否真的處於一個程序
主執行緒獲取的程序號 與子程序獲取的程序號相同
'''驗證確實如此 即程序下的多個執行緒是處於一個程序'''
2.統計程序下活躍的執行緒數
	active_count()  # 注意主執行緒也算!!!
3.獲取執行緒的名字
	1.current_thread().name
  	MainThread  				 主執行緒
    Thread-1、Thread-2		子執行緒
  2.self.name

守護執行緒

from threading import Thread
import time


def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is over')

t1 = Thread(target=task, args=('task1',))
t2 = Thread(target=task, args=('task2',))
t1.daemon = True
t1.start()
t2.start()
print('主執行緒')
"""
主執行緒要等待所有非守護執行緒結束才可以結束
"""

GIL全域性直譯器鎖

"""純理論 不影響程式設計 只不過面試的時候可能會被問到"""
# 官方文件
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly 
because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
"""
1.回顧
	python直譯器的類別有很多
		Cpython Jpython Ppython
	垃圾回收機制
		應用計數、標記清除、分代回收
    
GIL只存在於CPython直譯器中,不是python的特徵
GIL是一把互斥鎖用於阻止同一個程序下的多個執行緒同時執行
原因是因為CPython直譯器中的垃圾回收機制不是執行緒安全的

反向驗證GIL的存在 如果不存在會產生垃圾回收機制與正常執行緒之間資料錯亂
GIL是加在CPython直譯器上面的互斥鎖
同一個程序下的多個執行緒要想執行必須先搶GIL鎖 所以同一個程序下多個執行緒肯定不能同時執行 即無法利用多核優勢


強調:同一個程序下的多個執行緒不能同時執行即不能利用多核優勢
	很多不懂python的程式設計師會噴python是垃圾 速度太慢 有多核都不能用

反駁:雖然用一個程序下的多個執行緒不能利用多核優勢 但是還可以開設多程序!!!

再次強調:python的多執行緒就是垃圾!!!

反駁:要結合實際情況 
	如果多個任務都是IO密集型的 那麼多執行緒更有優勢(消耗的資源更少)
		多道技術:切換+儲存狀態
	如果多個任務都是計算密集型 那麼多執行緒確實沒有優勢 但是可以用多程序
		CPU越多越好
	
以後用python就可以多程序下面開設多執行緒從而達到效率最大化
"""
1.所有的解釋型語言都無法做到同一個程序下多個執行緒利用多核優勢
2.GIL在實際程式設計中其實不用考慮