進程間的數據共享、進程池的回調函數和線程初識、守護線程

複製代碼
1、進程的數據共享
進程間數據是獨立的,能夠藉助於隊列或管道實現通訊,兩者都是基於消息傳遞的
雖然進程間數據獨立,但能夠經過Manager實現數據共享。

把全部實現了數據共享的比較便捷的類都從新又封裝了一遍,而且在原有的multiprocessing基礎上增長了新的機制 list dict等

數據共享的機制
    支持數據類型很是有限
    list dict都不是數據安全的,你須要本身加鎖來保證數據安全

Manager用法:
Manager().dict()  # 建立共享的字典
Manager().list()  # 建立共享的列表
    
用代碼看看:
簡單解釋一下with:
with能夠自動關閉文件、線程鎖的自動獲取和釋放等過後清理工做。
緊跟with後面的語句會執行對象的 __enter__() 方法,這個方法的返回值將被賦值給as後面的變量。
當with後面的代碼塊所有被執行完以後,將調用對象的 __exit__()方法。

from multiprocessing import Manager,Process,Lock
def work(dic,lock):
    with lock:   # 多個進程對數據進行修改,不加鎖的話會致使數據不安全
        dic['count'] -= 1   # 每一個進程都對dic進行修改

if __name__ == '__main__':
    lock = Lock()
    with Manager() as m:
        dic = m.dict({'count':100})  # 建立共享的字典
        p_lst = []
        for i in range(100):
            p = Process(target=work,args=(dic,lock))
            p_lst.append(p)
            p.start()
        for p in p_lst:
            p.join()
        print(dic)





2、進程池的回調函數(同步提交apply沒有回調函數)
場景:
子進程有大量的計算要去作,回調函數對結果作簡單處理。
咱們能夠把耗時間(阻塞)的任務放到進程池中,而後指定回調函數(主進程負責執行),這樣主進程在執行回調函數時就省去了I/O的過程,直接拿到的是任務的結果。

經過例子瞭解一下:
import os
from multiprocessing import Pool

def func(i):
    print('子進程:',os.getpid())
    return i

def call_back(res):
    print('回調函數:',os.getpid())
    print('res--->',res)

if __name__ == '__main__':
    p = Pool()
    print('主進程:',os.getpid())
    p.apply_async(func,args=(1,),callback=call_back)  # callback關鍵字傳參,參數是回調函數
    p.close()
    p.join()

結果:
主進程: 4732
子進程: 10552
回調函數: 4732
res---> 1

從結果能夠看出:
    子進程func執行完畢以後纔去執行callback回調函數
    子進程func的返回值會做爲回調函數的參數
    回調函數是在主進程中執行的


應用實例:

url_lst = [
    'http://www.baidu.com',
    'http://www.4399.com',
    'http://www.163.com',
    'http://www.hao123.com',
    'http://www.sina.com'
]

import re
from urllib.request import urlopen
from multiprocessing import Pool

def get_url(url):
    res = urlopen(url) # 打開連接
    web_name = re.search('www\.(.*)\.com',url) # 網站名
    print('%s finish' %web_name.group(1))
    return web_name.group(1),res.read()  # 返回網站名和鏈接的內容

def call_back(content):  # 把連接的內容寫入文件
    web_name,con = content
    with open(web_name+'.html','wb') as f:
        f.write(con)

if __name__ == '__main__':
    p = Pool()
    for url in url_lst:
        p.apply_async(get_url,args=(url,),callback=call_back)
    p.close()
    p.join()




3、線程的理論知識
1、進程概念
程序並不能單獨運行,只有將程序裝載到內存中,系統爲它分配資源才能運行,而這種執行的程序就稱之爲進程。
程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。
在多道編程中,咱們容許多個程序同時加載到內存中,在操做系統的調度下,能夠實現併發地執行。
這樣的設計,大大提升了CPU的利用率。進程的出現讓每一個用戶感受到本身獨享CPU,所以,進程就是爲了在CPU上實現多道編程而提出的

2、進程的缺點
進程只能在一個時間作一件事,不能同時作兩件事或多件事
進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行

3、線程的概念
60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨着計算機技術的發展,進程出現了不少弊端,一是因爲進程是資源擁有者,建立、撤消與切換存在較大的時空開銷,
所以須要引入輕型進程;二是因爲對稱多處理機(SMP)出現,能夠知足多個運行單位,而多個進程並行開銷過大。
    所以在80年代,出現了能獨立運行的基本單位——線程(Threads)。
        注意:進程是資源分配的最小單位,線程是CPU調度的最小單位.
                     每個進程中至少有一個線程。

4、進程和線程的關係
1)地址空間和其它資源(如打開文件):進程間相互獨立,同一進程的各線程間共享。某進程內的線程在其它進程不可見。
2)通訊:進程間通訊須要IPC(隊列,管道等),同一個進程中的全部線程的資源是共享的
3)調度和切換:線程上下文切換比進程上下文切換要快得多。
4)在多線程操做系統中,進程不是一個可執行的實體



5、線程使用場景
當某個進程確定須要作不止一件事情的時候,好比你用QQ,你能夠同時跟不少人聊天,而QQ只是一個進程,並非說你跟一我的聊天就開一個進程這樣,
由於聊天這些任務操做的都是同一塊數據,於是不能用多進程。應該是你開了QQ這個進程,跟別人聊天的時候在這個進程裏開啓多個線程跟別人聊天。



6、線程的理解
多個線程共享同一個進程的地址空間中的資源,是對一臺計算機上多個進程的模擬,有時也稱線程爲輕量級的進程。
同一臺計算機上多個進程,則共享這臺計算機的物理內存、磁盤、打印機等其餘物理資源。多線程的運行跟多進程的運行相似,是cpu在多個線程之間的快速切換。
不一樣的進程之間是充滿敵意的,彼此是搶佔、競爭cpu的關係,好比QQ和迅雷搶資源。而同一個進程是由一個程序員的程序建立,因此同一進程內的線程是合做關係,一個線程能夠訪問另一個線程的內存地址,你們都是共享的。
相似於進程,每一個線程也有本身的堆棧,不一樣於進程,線程庫沒法利用時鐘中斷強制線程讓出CPU,能夠調用thread_yield運行線程自動放棄cpu,讓另一個線程運行。


7、用戶級線程和內核級線程
用戶級線程
內核的切換由用戶態程序本身控制內核切換,不須要內核干涉,少了進出內核態的消耗,但不能很好的利用多核Cpu。
在用戶空間模擬操做系統對進程的調度,來調用一個進程中的線程,每一個進程中都會有一個運行時系統,用來調度線程。此時當該進程獲取cpu時,進程內再調度出一個線程去執行,同一時刻只有一個線程執行。


內核級線程
切換由內核控制,當線程進行切換的時候,由用戶態轉化爲內核態。切換完畢要從內核態返回用戶態;能夠很好的利用多核cpu,windows線程就是這樣的。


混合實現
用戶級與內核級的多路複用,內核同一調度內核線程,每一個內核線程對應n個用戶線程



8、python中的線程
全局解釋器鎖GIL
一個進程中的多個線程可以並行麼?
    在java  c++  c# 等語言中是能夠的
    可是在python中是不能夠的
    python是一個解釋型語言,全部的解釋型語言都不行

爲何不行?
Cpython解釋器內部有一把全局解釋器鎖 GIL
同一時刻用一個進程中的線程只有一個能被CPU執行
因此線程不能充分的利用多核

GIL鎖是爲了保證數據的安全性,雖然確實是限制了你的程序效率
但實際上GIL鎖是目前可以幫助你在線程的切換中提升效率的手段



9、總結
進程是
    計算機中最小的資源分配單位
    進程對於操做系統來講仍是有必定負擔
    建立一個進程 操做系統要分配的資源大體有 :
        代碼
        數據
        文件

爲何要有線程
    輕量級的概念
    他沒有屬於本身的進程資源:
        一條線程只負責執行代碼,沒有本身獨立的代碼、變量、文件資源
        
什麼是線程
    線程是計算機中被CPU調度的最小單位
    你的計算機當中的cpu都是執行的線程中的代碼
    
線程和進程之間的關係
    每個進程中都有至少一條線程在工做
    
線程的特色
    同一個進程中的線程共享這個線程的全部資源
    輕量級 沒有本身的資源
    
進程和線程之間的區別
    佔用的資源
    調度的效率
    資源是否共享
    
通用的問題
    一個進程中的多個線程可以並行麼?
    在java c++ c# 等語言中是能夠的

python中的線程
    在python中一個進程中的多個線程可以並行麼? 不行
    python是一個解釋型語言,全部的解釋型語言都不行
        爲何不行?
        Cpython解釋器 內部有一把全局解釋器鎖 GIL
            因此線程不能充分的利用多核
            同一時刻用一個進程中的線程只有一個能被CPU執行
        GIL鎖是爲了保證數據的安全性,雖然確實是限制了你的程序效率
        但實際上GIL鎖是目前可以幫助你在線程的切換中提升效率的手段
        GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。

線程有:
IO密集型線程和計算密集型線程
cpython解釋器適合IO密集型線程(web 爬蟲 金融分析)

若是要寫計算密集型的線程:
要麼換解釋器,要麼用多進程






4、threading模塊
multiprocess模塊徹底模仿了threading模塊的接口,兩者在使用層面,有很大的類似性
1、線程的建立方式
方式一:
from threading import Thread
import time

def sleep_boy(name):
    time.sleep(1)
    print('%s is sleeping' %name)

t = Thread(target=sleep_boy,args=('xiaoming',))  # 這裏能夠不須要main,由於如今只是在一個進程內操做,不須要導入進程就不會import主進程了
t.start()
print('主線程')



方式二:
from threading import Thread
import time

class Sleep_boy(Thread):
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        time.sleep(1)
        print('%s is sleeping' % self.name)

t = Sleep_boy('xiaoming')
t.start()
print('主線程')


2、併發性(注意:在主進程下開啓多個線程,每一個線程的pid都跟主進程的pid同樣)
import os
import time
from threading import Thread

def func(i):
    time.sleep(0.5)
    print('子線程:',i,os.getpid())

print('主進程:',os.getpid())
for i in range(10):
    t = Thread(target=func,args=(i,))
    t.start()


3、線程共享進程的資源
from threading import Thread

num = 100  # 全局變量

def func():
    global num
    num -= 1

t_lst = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)
for t in t_lst:
    t.join()
print('num:',num)


4、其餘方法
Thread實例對象的方法
    isAlive(): 返回線程是否活動的。
    is_alive(): 返回線程是否活動的。
    getName(): 返回線程名。
    setName(): 設置線程名。



from threading import Thread
import time
def func():
    time.sleep(0.2)
    print('hello')
t = Thread(target=func)
t.start()
print(t.isAlive())  # True
print(t.is_alive()) # True
print(t.getName())  # Thread-1
t.setName('t1')
print(t.getName())  # t1



threading模塊提供的一些方法:
  threading.currentThread(): 返回當前線程的對象(經過這個對象能夠查看線程的一些屬性,好比線程id:ident,線程的名字:getName等)
  threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。
  threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果。



from threading import currentThread,enumerate,activeCount,Thread
import time
def func():
    print('子線程:',currentThread().ident)     # 子線程: 6076
    print('子線程:',currentThread().getName()) # 子線程: Thread-1
    time.sleep(3)

print('主線程:',currentThread().ident)      # 主線程: 1156
print('主線程:',currentThread().getName())  # 主線程: MainThread
t = Thread(target=func)
t.start()
print(enumerate())  # [<_MainThread(MainThread, started 1156)>, <Thread(Thread-1, started 6076)>]
print(len(enumerate())) # 2
print(activeCount())    # 2 5、守護線程
守護進程和守護線程的區別:
    1 主進程在其代碼結束後就已經算運行完畢了(守護進程在此時就被回收),而後主進程會一直等非守護的子進程都運行完畢後回收子進程的資源(不然會產生殭屍進程),纔會結束
    2 主線程在其餘非守護線程運行完畢後纔算運行完畢(守護線程在此時就被回收)。由於主線程的結束意味着進程的結束,因此主線程結束了以後,守護線程隨着主進程的結束天然結束了
  
 
import time
from threading import Thread
def func1():
    while True:
        time.sleep(0.5)
        print('func1')

def func2():
    print('func2 start')
    time.sleep(3)
    print('func2 end')

t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.setDaemon(True)  # 設置守護線程
# t1.daemon = True t1 = Thread(target=func1,daemon = True) 這兩種方式也是開啓守護線程的方法 t1.start() t2.start()
print('主線程代碼結束了')

守護進程:只能用 p.daemon = True 這種方式設置守護進程,由於daemon只是Process類對象的屬性,
而守護線程,可使用 t1 = Thread(target=func1,daemon = True)和t1.daemon = True這兩種方式和t1.setDaemon(True)這種方法設置守護線程
由於daemon是Thread類對象的屬性和對象的默認參數


守護進程(一種方式)
p = Process(target=func)
p.daemon = True # daemon是Process的屬性

守護線程(三種方式)
t1 = Thread(target=func1,daemon = True)
t1.daemon = True
t1.setDaemon(True)



進程:



線程:

 
  
複製代碼
相關文章
相關標籤/搜索