多線程、多進程和線程池編程

GIL (global interpreter lock)全局解釋器鎖

基於cpython的介紹:
python中的一個線程是對應於c語言中的一個線程,python在早期的時候爲了簡單,會在解釋器上加一把非常大的鎖,允許我們同一時刻只有一個線程運行在一個cpu上執行字節碼。在某種程度上保證了我們的線程是安全的。它無法將多個線程映射到多個cpu上執行,使得我們無法利用多核優勢。
gil使得同一時刻只有一個線程在一個cpu上執行字節碼

python現在正在努力的去掉gil,像pypy是去gil的.因爲cpython當中的許多第三方包使用gil的,所以短期類gil是不太可能被去掉的。

那麼是不是說在一個時刻只有一個線程運行在我們的cpu上,就意味着我們在使用多線程編碼的時候就是安全的呢?就不用去考慮線程之間的同步呢?實際上不是的。因爲gil在某些情況下是會被釋放的。gil的釋放在python2和python3還有區別的。

gil的釋放:
1.gil會根據執行的字節碼行數以及時間片釋放gil
2.gil還會在遇到io操作的時候主動釋放

所以python在io操作的時候,多線程編程就變得非常重要了。



# import dis
# def  add(a):
#     a=a+1
#     return a
# #我們可以對它進行反編譯的
# print(dis.dis(add))
'''
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter11/pythonGIL.py
  3           0 LOAD_FAST                0 (a)
              2 LOAD_CONST               1 (1)
              4 BINARY_ADD
              6 STORE_FAST               0 (a)

  4           8 LOAD_FAST                0 (a)
             10 RETURN_VALUE
None
'''
total=0
def add():
    global total
    for i in range(1000000):
        total+=1
def  desc():
    global  total
    for i in range(1000000):
        total-=1
import threading
thread1=threading.Thread(target=add)
thread2=threading.Thread(target=desc)
thread1.start()
thread2.start()


thread1.join()
thread2.join()
print(total)



多線程編程

操作系統能夠調度的最小單元是線程,在最開始的時候操作系統能夠調度的最小單元實際上是進程,但是因爲進程對於系統的資源消耗非常大,所以後期就演變出了線程。線程實際上是依賴於進程的。如在windows任務管理器中會看到許多的進程,如下圖:
在這裏插入圖片描述

對於操作系統來說能夠調度的做小單元是線程。

多線程編程:
1.通過Thread類實例化

# 對於io操作來說,多線程和多進程差別不大
import time
import threading
#1.通過Thread類實例化

def get_detail_html(url):
    print("get detail html started")
    time.sleep(2)
    print("get detail html end")
def get_detail_url(url):
    print("get detail url started")
    time.sleep(4)
    print("get detail url end")
if __name__=="__main__":
    thread1=threading.Thread(target=get_detail_html,args={"",})
    thread2 = threading.Thread(target=get_detail_url, args={"",})
    #設置子進程爲守護線程  當主線程退出的時候,子線程也會被關掉。
    # thread1.setDaemon(True)
    thread2.setDaemon(True)

    start_time=time.time()
    thread1.start()
    thread2.start()
    #那麼如何使得在兩個線程運行完成之後再執行主線程呢
    thread1.join()
    thread2.join()
    '''
    H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter11/python_thread.py
    get detail html started
    get detail url started
    get detail html end
    get detail url end
    last time: 4.0012288093566895
    在加入線程阻塞之後,主線程會等待兩個子線程執行完成之後纔開始執行,而兩個子線程
    運行所用的時間並不是順序運行的時間,而是在一定程度的並行運行,因此是花費了線程
    2所用的時間。
    '''


    '''
    雖然在這個python腳本當中創建了2個線程,但是實際上一共有3個線程
    第三個線程就是我們的python程序,即所說的主線程。
    
    '''
    print("last time: {}".format(time.time()-start_time))

2.通過繼承Thread來實現多線程 適用於複雜的場景

import time
import threading
class GetDetailHtml(threading.Thread):
    #重載init方法
    def __init__(self,name):
        super().__init__(name=name)

    #重載run方法
    def  run(self):
        print("get detail html started")
        time.sleep(2)
        print("get detail html end")
class GetDetailUrl(threading.Thread):
    # 重載init方法
    def __init__(self,name):
        super().__init__(name=name)

    # 重載run方法
    def run(self):
        print("get detail url started")
        time.sleep(4)
        print("get detail url end")
#通過這兩個類的實例完成我們的多線程
if __name__=="__main__":
    thread1=GetDetailHtml("getdetailhtml")
    thread2= GetDetailUrl("getdetailurl")
    # thread1.setDaemon(True)
    thread2.setDaemon(True)

    start_time=time.time()
    thread1.start()
    thread2.start()
    #那麼如何使得在兩個線程運行完成之後再執行主線程呢
    thread1.join()
    thread2.join()
    print("last time: {}".format(time.time() - start_time))

線程間通信

1.線程間共享變量

import time
import threading
from pythonHight.chapter11.variables import detail_url_list
from pythonHight.chapter11 import variables


#1.通過Thread類實例化


#爬取文章詳情頁
def get_detail_html():
    detail_url_list=variables.detail_url_list
    # while True:
    if len(detail_url_list):
        # global  detail_url_list
        url=detail_url_list.pop()
        # for url in detail_url_list:
        print("get detail html started")
        time.sleep(0.5)
        print("get detail html end")

#爬取文章列表頁
def get_detail_url():
    # global  detail_url_list
    detail_url_list = variables.detail_url_list
    # while True:
    print("get detail url started")
    time.sleep(1)
    for  i in range(20):
        detail_url_list.append("http://www.baidu.com/{id}".format(id=i))
    print("get detail url end")

#1.線程間的通信方式-共享變量
'''
線程間的變量共享涉及到很多的線程安全性問題,因爲gil的原因會導致
線程在處理數據的時候沒有達到預期的效果,可能會造成一定的混亂。
所以並不推薦使用共享變量進行通信,除非大家對鎖比較瞭解

'''
#2.

if __name__=="__main__":
    thread_detail_url=threading.Thread(target=get_detail_url,args=())
    '''
    在真正爬取url列表頁面的時候是很快的,而爬取每個url裏的詳情速度是很慢的
    如果這兩個爬取方式只是分別之構建一個線程,則沒有做到高併發,那麼就可以
    考慮構建多個爬取詳情頁的線程來增加併發,提高運行效率
    '''
    '''
    直觀的做法:
    thread_detail_html1 = threading.Thread(target=get_detail_html, args={"", })
    thread_detail_html2 = threading.Thread(target=get_detail_html, args={"", })
    thread_detail_html3 = threading.Thread(target=get_detail_html, args={"", })
    thread_detail_html4 = threading.Thread(target=get_detail_html, args={"", })
    thread_detail_html5 = threading.Thread(target=get_detail_html, args={"", })
    thread2 = threading.Thread(target=get_detail_url, args={"",})
    
    thread_detail_url.start()
    thread_detail_html1.start()
    thread_detail_html2.start()
    thread_detail_html3.start()
    thread_detail_html4.start()
    thread_detail_html5.start()
    thread_detail_url.join()
    thread_detail_html1.join()
    thread_detail_html2.join()
    thread_detail_html3.join()
    thread_detail_html4.join()
    thread_detail_html5.join()
    '''
    #其實可以使用循環來做
    for i in range(10):
        detail_html_thread=threading.Thread(target=get_detail_html,args=())
    #設置子進程爲守護線程  當主線程退出的時候,子線程也會被關掉。
    # thread_detail_url.setDaemon(True)
    # detail_html_thread.setDaemon(True)

    # start_time=time.time()
    thread_detail_url.start()
    detail_html_thread.start()

    #那麼如何使得在兩個線程運行完成之後再執行主線程呢
    thread_detail_url.join()
    detail_html_thread.join()


2.通過queue進行線程間同步

#   示意代碼,不可運行。
#通過queue的方式來進行線程間的同步的
import time
import threading

from queue import  Queue
#這裏是線程安全的,爲什麼呢,是因爲queue本身就是一個線程安全的
#在queue.get()在進行get操作的時候,他不會使得我們多個我線程去、
#操作同一個queue的時候造成數據的錯誤

'''
Queue是如何做到線程安全的呢,如裏面的get方法是線程安全的,底層用到了
deque雙端隊列,它是線程安全的
'''

# 爬取文章詳情頁
def get_detail_html(queue):
        #get方法是個阻塞的方法 如果隊列爲空,他會一直停在這
        url = queue.get()
        # for url in detail_url_list:
        print("get detail html started")
        time.sleep(0.5)
        print("get detail html end")


# 爬取文章列表頁
def get_detail_url():
    while True:
        print("get detail url started")
        time.sleep(1)
        for i in range(20):
            Queue.put("http://www.baidu.com/{id}".format(id=i))
        print("get detail url end")


if __name__ == "__main__":
    detail_url_queue=Queue(maxsize=1000)

    thread_detail_url = threading.Thread(target=get_detail_url, args=(detail_url_queue,))
    for i in range(10):
        detail_html_thread = threading.Thread(target=get_detail_html, args=(detail_url_queue,))
    # 設置子進程爲守護線程  當主線程退出的時候,子線程也會被關掉。
    # thread_detail_url.setDaemon(True)
    # detail_html_thread.setDaemon(True)

    # start_time=time.time()
    thread_detail_url.start()
    detail_html_thread.start()

    # 那麼如何使得在兩個線程運行完成之後再執行主線程呢
    # thread_detail_url.join()
    # detail_html_thread.join()

    detail_url_queue.task_done()
    detail_url_queue.join()


線程同步(Lock,RLock,Semaphores、Condition)

python中爲什麼需要線程同步,以及同步是什麼意思?

import threading
from threading import Lock


total=0

lock=Lock()
def add():
    global total
    global lock
    for i in range(1000000):
        #獲取一把鎖
        lock.acquire()
        lock.acquire()
        #不能在一把鎖沒有釋放的情況下,又獲取鎖
        #造成死鎖
        total+=1
        #釋放鎖

        lock.release()
def  desc():
    global  total
    for i in range(1000000):
        #獲取一把鎖
        lock.acquire()
        total-=1
        #釋放鎖
        lock.release()
thread1=threading.Thread(target=add)
thread2=threading.Thread(target=desc)
thread1.start()
thread2.start()

'''
線程同步機制:
1.

'''

# def add1(a):
#     a+=1
# def desc1(a):
#     a-=1
# import dis
# print(dis.dis(add1))
# print(dis.dis(desc1))
'''
add
1.load a     a=0
2.load 1     1
3.+     1
4.賦值給a    1

desc
1.load a     a=0 
2.load 1
3.-     -1     
4.賦值給a     -1
在這個字節碼過程當中的任意一個過程,
線程都有可能被切換出去給另一個線程.



使用了鎖之後,多線程之間誰先獲取鎖,那麼這個線程就
會先執行自己的代碼,並且如果這個線程的鎖如果沒有在
執行後得到釋放,那麼所有其他所有線程都會被卡住。

1.使用了鎖之後會影響我們的性能,獲取鎖和釋放鎖都需
要時間。  這是鎖會影響併發性能的必然問題。
2.用鎖會引起死鎖。非常容易引起死鎖。(小心)
  死鎖的情況:A(a,b)
  A(a,b)
  首先 acquire (a)
       acquire (b)
  B(a,b)
  acquire (b)
  acquire(a)
  這種情況下造成鎖資源的競爭。
  當A線程在和B先程
  
'''

thread1.join()
thread2.join()
print(total)



#RLock在同一個線程裏面,可以連續調用多次acquire,
#一定要注意acquire的次數要和release的次數相等

Condition :
在使用從第同之前先使用lock實驗下面的場景,達不到說一句回答一句的
聊天效果。實例代碼如下:

from threading import Condition
#Condition條件變量,用於複雜的線程間同步的鎖。
from threading import  Lock
import threading
class XiaoAi(threading.Thread):
    def __init__(self,lock):
        self.lock=lock

        super().__init__(name="小愛")
    def run(self):
        self.lock.acquire()
        print("{}: 在".format(self.name))
        self.lock.release()
        self.lock.acquire()
        print("{}: 好啊。".format(self.name))
        self.lock.release()
        pass
class TianMaoJingLing(threading.Thread):
    def __init__(self,lock):
        self.lock=lock
        super().__init__(name="天貓精靈")
    def run(self):
       self.lock.acquire()
       print("{}: 小愛同學".format(self.name))
       self.lock.release()
       self.lock.acquire()
       print("{}: 我們來對古詩吧".format(self.name))
       self.lock.release()
if  __name__=="__main__":
     lock=Lock()
     xiaoai=XiaoAi(lock)
     tianmao=TianMaoJingLing(lock)
     tianmao.start()
     xiaoai.start()


使用Condition來實現聊天場景

from threading import Condition
#Condition條件變量,用於複雜的線程間同步的鎖。
from threading import  Lock
import threading
# class XiaoAi(threading.Thread):
#     def __init__(self,lock):
#         self.lock=lock
#
#         super().__init__(name="小愛")
#     def run(self):
#         self.lock.acquire()
#         print("{}: 在".format(self.name))
#         self.lock.release()
#         self.lock.acquire()
#         print("{}: 好啊。".format(self.name))
#         self.lock.release()
#         pass
# class TianMaoJingLing(threading.Thread):
#     def __init__(self,lock):
#         self.lock=lock
#         super().__init__(name="天貓精靈")
#     def run(self):
#        self.lock.acquire()
#        print("{}: 小愛同學".format(self.name))
#        self.lock.release()
#        self.lock.acquire()
#        print("{}: 我們來對古詩吧".format(self.name))
#        self.lock.release()
# if  __name__=="__main__":
#      lock=Lock()
#      xiaoai=XiaoAi(lock)
#      tianmao=TianMaoJingLing(lock)
#      tianmao.start()
#      xiaoai.start()

from threading import Condition
#通過condition完成協同對話
class XiaoAi(threading.Thread):
    def __init__(self,cond):
        self.cond=cond
        super().__init__(name="小愛")
    def run(self):
        # with self.cond:
            self.cond.acquire()
            #等待通知
            self.cond.wait()
            print("{}: 在".format(self.name))
            #通知天貓精靈
            self.cond.notify()

            #等待通知
            self.cond.wait()
            print("{}: 好啊.".format(self.name))
            #通知天貓精靈
            self.cond.notify()

            #等待通知
            self.cond.wait()
            print("{}: 君住長江尾.".format(self.name))
            #通知天貓精靈
            self.cond.notify()

            #等待通知
            self.cond.wait()
            print("{}: 共飲長江水.".format(self.name))
            #通知天貓精靈
            self.cond.notify()

            #等待通知
            self.cond.wait()
            print("{}: 此恨何時已.".format(self.name))
            #通知天貓精靈
            self.cond.notify()

            #等待通知
            self.cond.wait()
            print("{}: 定不負相思憶.".format(self.name))
            #通知天貓精靈
            self.cond.notify()
            self.cond.release()



class TianMaoJingLing(threading.Thread):
    def __init__(self,cond):
        self.cond=cond
        super().__init__(name="天貓精靈")
    def run(self):
       with self.cond:
           print("{}: 小愛同學。".format(self.name))
           #通知小愛同學
           self.cond.notify()
           #進入等待的狀態  等待小愛的通知
           self.cond.wait()

           print("{}: 我們來對古詩吧。".format(self.name))
           #通知小愛同學
           self.cond.notify()
           #進入等待的狀態  等待小愛的通知
           self.cond.wait()

           print("{}: 我在長江頭。".format(self.name))
           #通知小愛同學
           self.cond.notify()
           #進入等待的狀態  等待小愛的通知
           self.cond.wait()

           print("{}: 日日思君不見君。".format(self.name))
           #通知小愛同學
           self.cond.notify()
           #進入等待的狀態  等待小愛的通知
           self.cond.wait()

           print("{}: 此水幾時休。".format(self.name))
           #通知小愛同學
           self.cond.notify()
           #進入等待的狀態  等待小愛的通知
           self.cond.wait()

           print("{}: 只願君心似我心。".format(self.name))
           #通知小愛同學
           self.cond.notify()
           #進入等待的狀態  等待小愛的通知
           self.cond.wait()



if  __name__=="__main__":
     cond=Condition()
     xiaoai=XiaoAi(cond)
     tianmao=TianMaoJingLing(cond)
     #啓動順序很重要
     #在調用with cond 之後才能調用 wait()或者notify()
     #condition有兩層鎖一把底層鎖會在線程調用了wait方法
     # 的時候釋放掉,上面的鎖會在每次調用wait的時候分配
     #一把鎖並放到cond的等待隊列中,等待notify方法的喚醒
     
     xiaoai.start()
     tianmao.start()


運行結果如下圖:
在這裏插入圖片描述

wait()函數允許我們等待某個條件變量的通知

semaphore 是用於控制進入數量的鎖

#semphore  是用於控制進入數量的鎖

#文件, 讀,寫,寫一般只是用於一個線程寫,讀可以允許有多個線程讀
#爬蟲
import threading
import time
class HtmlSpider(threading.Thread):
    def __init__(self,url,sem):
        super().__init__()
        self.url=url
        self.sem=sem
    def run(self):
        time.sleep(2)
        print("got html text success")
        self.sem.release()
class UrlProducer(threading.Thread):
    def __init__(self,sem):
        super().__init__(name="")
        self.sem=sem
    def run(self):
        for i in range(20):
            self.sem.acquire()
            html_thread=HtmlSpider("http://www.baidu.com/{}".format(i),self.sem)
            html_thread.start()
if __name__=="__main__":
    #控制進入某段代碼的線程數量 即每次建立3個線程
    sem=threading.Semaphore(3)
    url_producer=UrlProducer(sem)
    url_producer.start()
'''
輸出結果每次輸出三個結果。
'''
    from queue import Queue

concurrent線程池編程

python3.2+

from concurrent import futures
#線程池   爲什麼要線程池
'''
能不能有個線程管理包去管理我們的線程,
第一控制線程進入的數量

在主線程中可以獲取某某一個線程的狀態或者說某一個任務
的狀態以及返回值
當一個線程完成的時候我們主線程能夠立即知道
futures可以讓多線程和多進程編碼接口一致


'''
from concurrent.futures import ThreadPoolExecutor,as_completed,wait,FIRST_COMPLETED
                                                   #wait可以讓我們的主線程阻塞
import time
def  get_html(times):
    time.sleep(times)
    print("get page {} success".format(times))
    return times
#實例化線程池
executor=ThreadPoolExecutor(max_workers=2)
#通過submit函數提交執行的函數到線程池中  submit是立即返回是非阻塞的
# task1=executor.submit(get_html,(3))
# task2=executor.submit(get_html,2)

'''
在實際的爬蟲當中,我們並不是獲取所有的線程的狀態,
而是要獲取已經成功的task的返回
'''
urls=[3,2,4]
#1批量提交
all_task=[executor.submit(get_html,(url)) for url in urls]
# wait(all_task)
#這樣的話,我們就可以控制主線程等待某件事情的發生之後再繼續執行
wait(all_task,return_when=FIRST_COMPLETED)
print("main")

#通過as_completed獲取成已完成的task的結果1
# for future in as_completed(all_task):
#     data=future.result()
#     print("get {} page success".format(data))

#2通過executor的map獲取已經完成的task的結果
# for result in executor.map(get_html,urls):
#     print("get {} page".format(result))







# #done()用於判定某個任務是否完成
# print(task1.done())
# #取消某一個線程
# print(task2.cancel())
# time.sleep(5)
# #是一個阻塞的方法,它能夠返回一個結果
# print(task1.done())
# #result可以獲取task的執行結果
# print(task1.result())






from concurrent.futures import Future
‘’’
Future == task的返回容器或者未來對象 這一設計理念比較重要
是因爲在submit()返回的對象線程任務所處的
狀態有可能是未完成的,但是有可能在將來某個時候完成
所以說他是我們異步的核心。

多進程

因爲有一把gil鎖的存在,python中的多線程實際上無法利用我們多核的優勢,無法達到並行的優勢,多進程編程就可以利用我們多個cpu併發的這麼個需求去提高我們的運行效率。

多進程 與 多線程:
1.對於耗cpu的操作,使用多進程.
2.對於io的操作,使用多線程.切換進程的代價要高於線程

對於耗費cpu的操作,計算,圖形處理,機器學習的算法,以及流行的比特幣挖礦等等耗費到cpu已經不能夠滿足計算的需求,所以說現在的顯卡里面的GPU它的運算速度是高於CPU的 。 對於耗費cpu的操作,多進程優於多線程。

#多cpu的操作用多進程編程
#1.對於耗cpu的操作,使用多進程。
# 2.對於io的操作,使用多線程.切換進程的代價要高於線程。

'''

多進程和多線程使用concurrent.futures的效率是差不多的。
'''

def  fibo(n):
    if n<2:
        return 1
    return fibo(n-1)+fibo(n-2)
# print(fibo(10))#89
# 1.使用多線程
from concurrent.futures import ThreadPoolExecutor,as_completed
import time
# with ThreadPoolExecutor(3) as excutor:
#     alltask=[excutor.submit(fibo,(num)) for num in range(25,35)]
#     starttime=time.time()
#     for future in as_completed(alltask):
#         data=future.result()
#         print("get numbers results is:  {}".format(data))
#     print("last time is:{}".format(time.time()-starttime))
'''
89
get numbers results is:  121393
get numbers results is:  196418
get numbers results is:  317811
get numbers results is:  514229
get numbers results is:  832040
get numbers results is:  1346269
get numbers results is:  2178309
get numbers results is:  3524578
get numbers results is:  5702887
get numbers results is:  9227465
last time is:15.730899810791016

'''
#2.多進程
import time
from  concurrent.futures import ProcessPoolExecutor
'''
注意:windows多進程編程時一定要將執行的ProcessPoolExecutor代碼放到
if  __name__=="__main__":之下,不然會報錯。
'''
if __name__=="__main__":
    with ProcessPoolExecutor(3) as excutor:
        alltask = [excutor.submit(fibo, (num)) for num in range(25, 45)]
        starttime = time.time()
        for future in as_completed(alltask):
            data = future.result()
            print("get numbers results is:  {}".format(data))
        print("last time is:{}".format(time.time() - starttime))
'''
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter11/progress_test.py
get numbers results is:  121393
get numbers results is:  196418
get numbers results is:  317811
get numbers results is:  514229
get numbers results is:  832040
get numbers results is:  1346269
get numbers results is:  2178309
get numbers results is:  3524578
get numbers results is:  5702887
get numbers results is:  9227465
last time is:9.456540822982788
'''

對於io操作來說,多線程優於我們的多進程:

#對於io操作來說,多線程優於我們的多進程
def random_sleep(n):
    time.sleep(n)
    return n
# if __name__=="__main__":
#     with ThreadPoolExecutor(3) as excutor:
#         alltask = [excutor.submit(random_sleep, (num)) for num in [2]*30]
#         starttime = time.time()
#         for future in as_completed(alltask):
#             data = future.result()
#             print("get numbers results is:  {}".format(data))
#         print("last time is:{}".format(time.time() - starttime))
'''
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter11/progress_test.py
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
last time is:20.00114417076111
'''
if __name__=="__main__":
    with ProcessPoolExecutor(3) as excutor:
        alltask = [excutor.submit(random_sleep, (num)) for num in [2]*30]
        starttime = time.time()
        for future in as_completed(alltask):
            data = future.result()
            print("get numbers results is:  {}".format(data))
        print("last time is:{}".format(time.time() - starttime))
'''
H:\ANACONDA\python.exe I:/ainlp/pythonHight/chapter11/progress_test.py
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
get numbers results is:  2
last time is:20.690183401107788
'''

其實在計算量不是很大的時候,多線程和多進程性能差別不大。操作系統調用的線程數比進程數要多很多,調用和切換進程會消耗很多的cpu內存,因此在某些情況下使用多線程的性能是不會比多進程差的,而且多線程的穩定性是比多進程好的。

多進程編程:

先看一下這個東西:

import  os
import time
#fork只能用於linux/unix上

pid  =os.fork()
print("xiaopang")
if pid==0:
    print('子進程 {} ,父進程是: {}.'.format(os.getpid(),os.getppid()))
else:
    print('我是父進程 : {}'.format(pid))
time.sleep(2)

運行結果:
在這裏插入圖片描述
爲什麼兩個進程的fpid不同呢,這與fork函數的特性有關。fork調用的一個奇妙之處就是它僅僅被調用一次,卻能夠返回兩次,它可能有三種不同的返回值:
1)在父進程中,fork返回新創建子進程的進程ID;
2)在子進程中,fork返回0;
3)如果出現錯誤,fork返回一個負值;

將print("xiaopang ")放在fok()的前面。

import  os
import time
#fork只能用於linux/unix上
print("xiaopang")
pid  =os.fork()
if pid==0:
    print('子進程 {} ,父進程是: {}.'.format(os.getpid(),os.getppid()))
else:
    print('我是父進程 : {}'.format(pid))
time.sleep(2)

在這裏插入圖片描述

多進程和進程池

from concurrent.futures import ProcessPoolExecutor
import multiprocessing


#多進程編程
import  time
def  get_html(n):
   time.sleep(n)
   print("sub_progress success")
   return  n


if   __name__=="__main__":
    # process=multiprocessing.Process(target=get_html,args=(2,))
    # print(process.pid)
    # process.start()
    # print(process.pid)
    # process.join()
    # print("main progress end")


    # #使用進程池
    pool=multiprocessing.Pool(multiprocessing.cpu_count())
    # result=pool.apply_async(get_html,args=(3,))
    # #等待所有任務完成
    # pool.close()
    # pool.join()
    # print(result.get())
    #imap
    # for result in pool.imap(get_html,[1,5,3]):
    #     print("{} sleep success".format(result))
    #imap_unordered
    for result in pool.imap_unordered(get_html,[1,5,3]):
        print("{} sleep success".format(result))

多進程之間的通信

1使用multiprogressing 中的queue在進程之間進行通信

from multiprocessing import  Process,Queue

import  time
def producer(queue):
    queue.put("a")
    time.sleep(2)
def cusumer(queue):
    time.sleep(2)
    data=queue.get()
    print(data)
if __name__=="__main__":
    queue=Queue(10)
    myproducer=Process(target=producer,args=(queue,))
    mycusumer=Process(target=cusumer,args=(queue,))
    myproducer.start()
    mycusumer.start()
    myproducer.join()
    mycusumer.join()

multiprocessing中的queue不能用於進程池的通訊,因爲
不起作用。

#multiprocessing中的queue不能用於進程池的通訊
from multiprocessing import  Process,Queue,Pool

import  time
def producer(queue):
    queue.put("a")
    time.sleep(2)
def cusumer(queue):
    time.sleep(2)
    data=queue.get()
    print(data)
if __name__=="__main__":
    queue=Queue(10)
    pool=Pool(2)
    pool.apply_async(producer,args=(queue,))
    pool.apply_async(cusumer,args=(queue,))
    pool.close()
    pool.join()

可以使用Manage之下的queue進行進程池間的通信

from multiprocessing import  Process,Queue,Pool,Manager

import  time
def producer(queue):
    queue.put("a")
    time.sleep(2)
def cusumer(queue):
    time.sleep(2)
    data=queue.get()
    print(data)
if __name__=="__main__":
    queue=Manager().Queue(10)
    pool=Pool(2)
    pool.apply_async(producer,args=(queue,))
    pool.apply_async(cusumer,args=(queue,))
    pool.close()
    pool.join()

進程間通訊的方式 管道pipe
pipe的性能高於queue
pipe只能用於兩個進程間的通訊



#進程間通訊的方式  管道pipe
# pipe的性能高於queue
# pipe只能用於兩個進程間的通訊
from multiprocessing import  Process,Pipe
                                #Pipe是簡化版的queue
import  time
def producer(pipe):
    pipe.send("xiaopang")

def cusumer(pipe):
    print(pipe.recv())


if __name__=="__main__":
    recevie_pipe,send_pipe=Pipe()
    # pipe只能用於兩個進程間的通訊
    myproducer=Process(target=producer,args=(send_pipe,))
    mycusumer=Process(target=cusumer,args=(recevie_pipe,))

    myproducer.start()
    mycusumer.start()
    myproducer.join()
    mycusumer.join()


使用Manage裏的dict進行進程間的共享內存操作,注意數據間的進程同步.

# 使用Manage裏的dict進行進程間的共享內存操作
from multiprocessing import Manager,Process
def add_data(p_dict,key,value):
    p_dict[key]=value
if __name__=="__main__":
    process_dict=Manager().dict()
    first_Process=Process(target=add_data,args=(process_dict,"xiaopang ",23))
    second_process=Process(target=add_data,args=(process_dict,"xiaohe",26))
    first_Process.start()
    second_process.start()
    first_Process.join()
    second_process.join()
    print(process_dict)


完結

下一篇: 協程和異步io

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章