操做系統/應用程序、操做中的「併發」、線程和進程,python中線程和進程(GIL鎖),python線程編寫+鎖

併發編程前言:python

      一、網絡應用程序員

           1)爬蟲 直接應用併發編程;面試

           2)網絡框架 django flask tornado 源碼-併發編程django

           3)socketserver 源碼-併發編程編程

      二、運維領域flask

           1)自動化開發-運維開發(機器的批量管理,任務的批量執行等)網絡

1、操做系統/應用程序多線程

a、硬件併發

       - 硬盤框架

       - CPU

       - 主板

       - 顯卡

       - 內存

       - 電源

       . . . . . .

b、裝系統(軟件)

       - 系統就是一個由程序員寫出來的軟件,該軟件用於控制計算機的硬件,讓他們之間進行相互配合。

c、安軟件(安裝應用程序)

       - QQ

       - 百度雲

       - pycharm

       . . . . . .

2、並行與併發

       併發,是僞的,因爲執行速度特別快,人感受不到停頓;

       並行,是真的,建立10我的同時操做;

       並行是指二者同時執行,好比賽跑,兩我的都在不停的往前跑(資源夠用,好比三個線程,四核的cpu);

       併發是指資源有限的狀況下,二者交替輪流使用資源,好比一段路(單核cpu資源)同時只能過一我的,A走一段後,讓給B,B用完繼續給A,交替使用,目的是提升效率;

區別:

       並行是從微觀上,也就是在一個精確的時間片刻,有不一樣的程序在執行,這就要求必須有多個處理器。

       併發是從宏觀上,在一個時間段上能夠當作是同時執行的。

3、線程和進程

a、單進程、單線程的應用程序,好比:

  print('666')

b、到底什麼是線程?什麼是進程?

       python本身沒有這玩意,python中調用的操做系統的線程和進程。

c、單進程、多線程的應用程序,好比:

    import threading
    print('666')

    def func(arg):
        print(arg)
    t = threading.Thread(target=func,args=(11,)) # 建立一個線程
    t.start()

  一個應用程序(軟件),能夠有多個進程(默認只有一個),一個進程中能夠建立多個線程(默認一個)。

d、故事:Alex甄嬛西遊傳

總結:

       1)操做系統幫助開發者操做硬件;

       2)程序員寫好代碼在操做系統上運行(依賴解釋器);

       3)任務比較多時,可將之前代碼寫法做以下改進:

    import requests
    import uuid

    url_list = [
'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg',
'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg',
'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg',
    ]

    def task(url):
        ret = requests.get(url)
        file_name = str(uuid.uuid4()) + '.jpg'
        with open(file_name, mode='wb') as f:
            f.write(ret.content)

    for url in url_list:
        task()
    """
    - 咱們寫好代碼
    - 交給解釋器運行: python s1.py 
    - 解釋器讀取代碼,再交給操做系統去執行,根據咱們的代碼去選擇建立多少個線程/進程去執行(此程序是單進程/單線程)。
    - 操做系統調用硬件:硬盤、cpu、網卡....
    """
之前的咱們寫代碼
    import threading
    import requests
    import uuid

    url_list = [
'https://www3.autoimg.cn/newsdfs/g28/M05/F9/98/120x90_0_autohomecar__ChsEnluQmUmARAhAAAFES6mpmTM281.jpg',
'https://www2.autoimg.cn/newsdfs/g28/M09/FC/06/120x90_0_autohomecar__ChcCR1uQlD6AT4P3AAGRMJX7834274.jpg',
'https://www2.autoimg.cn/newsdfs/g3/M00/C6/A9/120x90_0_autohomecar__ChsEkVuPsdqAQz3zAAEYvWuAspI061.jpg',
    ]

    def task(url):
        ret = requests.get(url)
        file_name = str(uuid.uuid4()) + '.jpg'
        with open(file_name, mode='wb') as f:
            f.write(ret.content)

    for url in url_list:
        t = threading.Thread(target=task, args=(url,)) # 建立線程
        t.start()
    """
    - 你寫好代碼
    - 交給解釋器運行: python s2.py 
    - 解釋器讀取代碼,再交給操做系統去執行,根據你的代碼去選擇建立多少個線程/進程去執行(此程序是單進程/4線程)。
    - 操做系統調用硬件:硬盤、cpu、網卡....
    """
如今的咱們寫代碼

4、python中線程和進程(GIL鎖)

       GIL鎖:python內置的一個全局解釋器鎖。用於限制一個進程中同一時刻只有一個線程被cpu調度。

       擴展:默認GIL鎖再執行100個cpu指令(過時時間)。

    import sys
    v = sys.getcheckinterval()
    print(v)  # 100
查看方法

  問題1:爲何有這把GIL鎖?

              python語言的創始人在開發這門語言時,目的是快速把語言開發出來,若是加上GIL鎖(c語言加鎖),切換時按照100條字節指令來進行線程間的切換。

       問題2:進程和線程的區別?

              線程,線程是cpu工做的最小單元;

              進程,進程是cpu資源分配的最小單元,爲線程提供一個資源共享的空間;

              一個進程中能夠有多個線程,一個進程中默認有一個主線程;

              對於python來講,它的進程和線程和其餘語言有差別,有GIL鎖。它保證一個進程中同一時刻只有一個線程被cpu調度;

              IO密集型操做可使用多線程,計算密集型可使用多進程;

  問題3:線程建立的越多越好嗎?

              不是,線程之間進行切換,要作上下文管理。

5、python線程編寫

一、線程的基本使用,以下示例:

  import threading
  def func(arg):
      print(arg)

  t = threading.Thread(target=func,args=(11,)) # 建立一個子線程
  t.start()

  print(123)
  # 11
  # 123
  # 程序結束

二、主線程默認等子線程執行完畢,才結束程序,以下示例:

    import threading
    import time
    def func(arg):
        time.sleep(arg)
        print(arg)

    t1 = threading.Thread(target=func,args=(3,))
    t1.start()

    t2 = threading.Thread(target=func,args=(6,))
    t2.start()
    # 3  # 程序開始3秒後
    # 6  # 程序開始6秒後
    # 程序結束

三、主線程再也不等,當主線程終止則全部子線程也終止,使用setDaemon(True),以下示例:

    import threading
    import time
    def func(arg):
        time.sleep(arg)
        print(arg)

    t1 = threading.Thread(target=func,args=(3,))
    t1.setDaemon(False)  # 主線程等待此子進程
    t1.start()

    t2 = threading.Thread(target=func,args=(6,))
    t2.setDaemon(True)  # 主線程再也不等待此子進程
    t2.start()

    print(123)
    # 123
    # 3
    # # 程序結束

四、開發者能夠控制主線程等待子線程(最多等待時間),使用join(n),以下示例:

    import threading
    import time
    def func(arg):
        time.sleep(arg)
        print(arg)

    print('建立子線程t1')
    t1 = threading.Thread(target=func,args=(3,))
    t1.start()
    t1.join(2)
    # 無參數,讓主線程在這裏等着,等到子線程t1執行完畢,才能夠繼續往下走。
    # 有參數,讓主線程在這裏最多等待n秒,不管是否執行完畢,會繼續往下走。

    print('建立子線程t2')
    t2 = threading.Thread(target=func,args=(1,))
    t2.start()
    t2.join(3) # 讓主線程在這裏等着,最多等待3秒,會繼續往下走,
    # 若子線程t2不到3秒就執行完畢,則子線程執行完畢主線程就往下走。

    print(123)
    # 建立子線程t1
    # 建立子線程t2
    # 3
    # 1
    # 123
    # 程序結束

五、線程名稱,以下示例:

    import threading
    def func(arg):
        t = threading.current_thread()  # 獲取當前執行該函數的線程的對象
        name = t.getName()  # 根據當前線程對象獲取當前線程名稱
        print(name,arg)

    t1 = threading.Thread(target=func,args=(11,))
    t1.setName('郭德綱')  # 設置線程名稱
    t1.start()

    t2 = threading.Thread(target=func,args=(22,))
    t2.setName('孫紅雷')  # 設置線程名稱
    t2.start()

    print(123)
    # 郭德綱 11
    # 孫紅雷 22
    # 123

六、線程本質,以下示例:

    # 先打印:11?123?
    import threading
    def func(arg):
        print(arg)

    t1 = threading.Thread(target=func,args=(11,))
    t1.start()
    # start 是開始運行線程嗎?不是
    # start 告訴cpu,我已經準備就緒,你能夠調度我了。
    print(123)

七、面向對象版本的多線程,以下示例:

    import threading
    def func(arg):
        print(arg)

    t1 = threading.Thread(target=func,args=(11,))
    t1.start()
常見建立多線程的方式:
    import threading
    class MyThread(threading.Thread):
        def run(self):
            print(11111,self._args,self._kwargs)

    t1 = MyThread(args=(11,))
    t1.start()  # 在cpu內部,若是要調度這個線程的話會執行這個對象的run方法

    t2 = MyThread(args=(22,))
    t2.start()

    print('end')
    # 11111 (11,) {}
    # 11111 (22,) {}
    # end
面向對象方式(通常不用,瞭解便可):

八、多線程

1)計算密集型多線程無用,以下示例:

    import threading
    v1 = [11,22,33] # +1
    v2 = [44,55,66] # +100

    def func(data,plus):
        for i in range(len(data)):
            data[i] = data[i] + plus

    t1 = threading.Thread(target=func,args=(v1,1))
    t1.start()

    t2 = threading.Thread(target=func,args=(v2,100))
    t2.start()

2)IO操做,多線程有用(IO操做不佔用cpu),如「3、線程和進程」中建立多個線程的代碼示例;

九、多線程的問題(加鎖+釋放鎖) 

    import time
    import threading

    lock = threading.RLock()

    n = 10

    def task(i):
        print('這段代碼不加鎖',i)

        lock.acquire() # 加鎖,此區域的代碼同一時刻只能有一個線程執行
        global n
        print('當前線程',i,'讀取到的n值爲:',n)
        n = i
        time.sleep(1)
        print('當前線程',i,'修改n值爲:',n)
        lock.release() # 釋放鎖

    for i in range(10):
        t = threading.Thread(target=task,args=(i,))
        t.start()

總結:

       1. 應用程序/進程/線程的關係? *****(面試題:進程/線程/協程的區別?)

             

       2. 爲何要建立線程?

              因爲線程是cpu工做的最小單元,建立線程能夠利用多核優點實現並行操做(Java/C#)。

              注意:線程是爲了工做。

                    

       3. 爲何要建立進程?

              進程和進程之間作數據隔離(Java/C#)。

                    

              注意:進程是爲了提供環境讓線程工做。

                    

       4. Python

              a. Python中存在一個GIL鎖    *****

                     - 形成:多線程沒法利用多核優點。

                     - 解決:開多進程處理(浪費資源)

                     進程和線程的使用準則:

                            IO密集型:多線程

                            計算密集型:多進程

              b. 線程的建立

                     - Thread             *****

                     - MyThread

              c. 其餘

                     - join                  *****

                     - setDaemon      *****

                     - setName           *****

                     - threading.current_thread()  *****

              d. 鎖

                     - 得到

                     - 釋放

相關文章
相關標籤/搜索