操做系統,進程和線程

 

                         操做系統介紹                             

      操做系統管理計算機的硬件設備,使應用軟件能方便、高效地使用這些設備。在微機上常見的有:DOS、WINDOWS、UNIX、OS/2等。
      在計算機軟件中最重要且最基本的就是操做系統(OS)。它是最底層的軟件,它控制全部計算機運行的程序並管理整個計算機的資源,是計算機裸機與應用程序及用戶之間的橋樑。沒有它,用戶也就沒法使用某種軟件或程序。
 
     操做系統是計算機系統的控制和管理中心,從資源角度來看,它具備處理機、存儲器管理、設備管理、文件管理等4項功能。
     經常使用的系統有DOS操做系統、WINDOWS操做系統、UNIX操做系統和Linux、Netware等操做系統 .

    細分的話,操做系統應該分紅兩部分功能:html

#一:隱藏了醜陋的硬件調用接口,爲應用程序員提供調用硬件資源的更好,更簡單,更清晰的模型(系統調用接口)。應用程序員有了這些接口後,就不用再考慮操做硬件的細節,專心開發本身的應用程序便可。
例如:操做系統提供了文件這個抽象概念,對文件的操做就是對磁盤的操做,有了文件咱們無需再去考慮關於磁盤的讀寫控制(好比控制磁盤轉動,移動磁頭讀寫數據等細節),

#二:將應用程序對硬件資源的競態請求變得有序化
例如:不少應用軟件實際上是共享一套計算機硬件,比方說有可能有三個應用程序同時須要申請打印機來輸出內容,那麼a程序競爭到了打印機資源就打印,而後多是b競爭到打印機資源,也多是c,這就致使了無序,打印機可能打印一段a的內容而後又去打印c...,操做系統的一個功能就是將這種無序變得有序。

 

                               進程                                    

          什麼是進程          

   進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎.在早期面向進程設計的計算機結構中,進程是程序的基本執行實體:在當代面向線程設計的計算機結構中,進程是線程的容器.程序是指令,數據及其組織形式的描述,進程是程序的實體.python

    注意 : 同一個程序執行兩次,就會在操做系統中出現兩個進程,因此咱們能夠同時運行一個軟件,分別作不一樣的事情也不會混亂.linux

         進程的並行與併發        

  並行 : 並行指二者同時進行 , 好比跑賽,兩我的都在不停的向前跑 : (資源夠用)
程序員

  併發 : 併發指在資源有限的狀況下,二者交替輪流使用資源.好比一張大餅(單核CPU),兩我的都很是餓,可是吃的時候只能有一我的吃,那麼就是A吃一口,讓給B,B再吃一口然給A,交替使用,目的是提升效率.面試

   區別 : 編程

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

   併發 : 併發是從宏觀上,在一個時間段裏能夠看出是同時執行的,好比一個服務器同時處理多個程序.(好比,一遍看視頻,一遍聊天.)服務器

 

 

                       Python中的進程操做                

在Windows操做系統中因爲沒有fork(linux操做系統中建立進程的機制),在建立子進程的時候會自動 import 啓動它的這個文件,而在 import 的時候又執行了整個文件。所以若是將process()直接寫在文件中就會無限遞歸建立子進程報錯。因此必須把建立子進程的部分使用if __name__ ==‘__main__’ 判斷保護起來,import 的時候  ,就不會遞歸運行了。
在Windows上使用進程的注意事項

          進程的建立          網絡

1 p.daemon:默認值爲False,若是設爲True,表明p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,而且設定爲True後,p不能建立本身的新進程,必須在p.start()以前設置
2 p.name:進程的名稱
3 p.pid:進程的pid / ident---進程的id
4 p.exitcode:進程在運行時爲None、若是爲–N,表示被信號N結束(瞭解便可)
5 p.authkey:進程的身份驗證鍵,默認是由os.urandom()隨機生成的32字符的字符串。這個鍵的用途是爲涉及網絡鏈接的底層進程間通訊提供安全性,這類鏈接只有在具備相同的身份驗證鍵時才能成功(瞭解便可)
6 p.join([timeout]):主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,須要強調的是,p.join只能join住start開啓的進程,而不能join住run開啓的進程  
屬性介紹

            基本操做       多線程

import multiprocessing

def func(arg):
    print(arg)

if __name__ == '__main__':     
    for i in range(10):
        t = multiprocessing.Process(target=func,args=(i,))
        t.start()

       ●       進程間的數據不共享     

import multiprocessing

li = []
def func(arg):
    li.append(arg)
    print(li)


def run():
    for i in range(10):
        t = multiprocessing.Process(target=func,args=(i,))
        t.start()

if __name__ == '__main__':
    run()

結果:    每個數都是一個進程
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]

   ●    進程通用功能  -- name         

import multiprocessing
import time

def func(arg):
    time.sleep(2)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,))
    # p1.name = "pig"     #進程名稱
    # print(p1.name)
    p1.start()
    print("888")

    p2 = multiprocessing.Process(target=func,args=(2,))
    # p2.name = "dog"
    
    p2.start()
    print("999")

if __name__ == '__main__':
    run()

   ●開發者能夠控制主進程等待子進程(最多等待時間)  join()                     

import multiprocessing
import time

def func(arg):
    time.sleep(5)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,))
    p1.start()
    # p1.join(1)
    print("888")
    p1.join(1)

    p2 = multiprocessing.Process(target=func,args=(2,))
    p2.start()
    # p1.join(1)
    print("999")
    p2.join(1)

if __name__ == '__main__':
    run()

結果:
666
888
#2秒後
999
#5秒後
1
2
join方法            

           注意:join() :   

    無參數,讓主線程在這裏等着,等到子線程t1執行完畢,才能夠繼續往下走。
    有參數,讓主線程在這裏最多等待n秒,不管是否執行完畢,會繼續往下走。

    ● 守護進程   Daemon

      會隨着主進程的結束而結束

      主進程建立守護進程

    其一:守護進程會在主進程代碼執行結束後就終止

    其二:守護進程內沒法再開啓子進程,不然拋出異常:AssertionError: daemonic processes are not allowed to have children

    注意:進程之間是互相獨立的,主進程代碼運行結束,守護進程隨即終止

import os
import time
from multiprocessing import Process

class Myprocess(Process):
    def __init__(self,person):
        super().__init__()
        self.person = person
    def run(self):
        print(os.getpid(),self.name)
        print('%s正在和女主播聊天' %self.person)


p=Myprocess('託塔')
p.daemon=True #必定要在p.start()前設置,設置p爲守護進程,禁止p建立子進程,而且父進程代碼執行結束,p即終止運行
p.start()
time.sleep(10) # 在sleep時查看進程id對應的進程ps -ef|grep id
print('')
守護進程的啓動
import multiprocessing
import time

def func(arg):
    time.sleep(5)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,)) #daemon 也能夠放這裏
    p1.daemon = True 
    p1.start()
    print("888")

    p2 = multiprocessing.Process(target=func,args=(2,))
    p2.daemon = True
    p2.start()
    print("999")


if __name__ == '__main__':
    run()

        ● 獲取id     pid / ident           

import multiprocessing
import time

def func(arg):
    time.sleep(5)
    print(arg)

def run():
    print("666")
    p1 = multiprocessing.Process(target=func,args=(1,))
    p1.start()
    print(p1.pid)

    p2 = multiprocessing.Process(target=func,args=(2,))
    p2.start()
    print(p2.pid)

if __name__ == '__main__':
    run()

結果:
666
4808
15240
1
2

           類繼承的方式               

class MyProcess(multiprocessing.Process):

    def run(self):
        print('當前進程',multiprocessing.current_process())


def run():
    p1 = MyProcess()
    p1.start()

    p2 = MyProcess()
    p2.start()

if __name__ == '__main__':
    run()

結果:
當前進程 <MyProcess(MyProcess-1, started)>
當前進程 <MyProcess(MyProcess-2, started)>

 

       進程間的數據共享          

Queue

  建立共享的進程隊列,Queue是多進程安全的隊列,可使用Queue實現多進程之間的數據傳遞.

Queue([maxsize]) 
建立共享的進程隊列。
參數 :maxsize是隊列中容許的最大項數。若是省略此參數,則無大小限制。
底層隊列使用管道和鎖定實現。
Queue([maxsize]) 
建立共享的進程隊列。maxsize是隊列中容許的最大項數。若是省略此參數,則無大小限制。底層隊列使用管道和鎖定實現。另外,還須要運行支持線程以便隊列中的數據傳輸到底層管道中。 
Queue的實例q具備如下方法:

q.get( [ block [ ,timeout ] ] ) 
返回q中的一個項目。若是q爲空,此方法將阻塞,直到隊列中有項目可用爲止。block用於控制阻塞行爲,默認爲True. 若是設置爲False,將引起Queue.Empty異常(定義在Queue模塊中)。timeout是可選超時時間,用在阻塞模式中。若是在制定的時間間隔內沒有項目變爲可用,將引起Queue.Empty異常。

q.get_nowait( ) 
同q.get(False)方法。

q.put(item [, block [,timeout ] ] ) 
將item放入隊列。若是隊列已滿,此方法將阻塞至有空間可用爲止。block控制阻塞行爲,默認爲True。若是設置爲False,將引起Queue.Empty異常(定義在Queue庫模塊中)。timeout指定在阻塞模式中等待可用空間的時間長短。超時後將引起Queue.Full異常。

q.qsize() 
返回隊列中目前項目的正確數量。此函數的結果並不可靠,由於在返回結果和在稍後程序中使用結果之間,隊列中可能添加或刪除了項目。在某些系統上,此方法可能引起NotImplementedError異常。


q.empty() 
若是調用此方法時 q爲空,返回True。若是其餘進程或線程正在往隊列中添加項目,結果是不可靠的。也就是說,在返回和使用結果之間,隊列中可能已經加入新的項目。

q.full() 
若是q已滿,返回爲True. 因爲線程的存在,結果也多是不可靠的(參考q.empty()方法)。。

方法介紹
方法介紹

 示例 :  

import multiprocessing
q = multiprocessing.Queue()

def task(arg, q):
    q.put(arg)

def run():
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i, q,))
        p.start()

    while True:
        v = q.get()
        print(v)
run()
在linux系統
import multiprocessing


def task(arg, q):
    q.put(arg)


if __name__ == '__main__':
    q = multiprocessing.Queue()
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i, q,))
        p.start()
    while True:
        v = q.get()
        print(v)

結果:
0
1
2
3
4
5
6
7
8
9
在Windows系統

 

 Manager   ***

進程間數據是獨立的,能夠藉助於隊列或管道實現通訊,兩者都是基於消息傳遞的
雖然進程間數據獨立,但能夠經過Manager實現數據共享,事實上Manager的功能遠不止於此

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.
Manager模塊介紹

 示例 : 

import multiprocessing
import time

def task(arg, dic):
    time.sleep(2)
    dic[arg] = 100


if __name__ == '__main__':
    m = multiprocessing.Manager()
    dic = m.dict()

    process_list = []
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i, dic,))
        p.start()

        process_list.append(p)

    while True:
        count = 0
        for p in process_list:
            if not p.is_alive():
                count += 1
        if count == len(process_list):
            break
    print(dic)

結果:
{0: 100, 1: 100, 2: 100, 3: 100, 4: 100, 5: 100, 6: 100, 7: 100, 8: 100, 9: 100}
在Windows系統中
import multiprocessing

m = multiprocessing.Manager()
dic = m.dict()
def task(arg):
    dic[arg] = 100

def run():
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(i,))
        p.start()

    input('>>>')
    print(dic.values())


if __name__ == '__main__':
    run()
在Linux系統

 

       進程鎖        

       進程鎖與線程鎖的方法是同樣的,也是有Lock , RLock , Semaphore , Event , Condition.

     RLock       

import time
import threading
import multiprocessing


lock = multiprocessing.RLock()

def task(arg):
    print("我真帥")
    lock.acquire()
    time.sleep(2)
    print(arg)
    lock.release()


if __name__ == '__main__':
    p1 = multiprocessing.Process(target=task,args=(1,))
    p1.start()

    p2 = multiprocessing.Process(target=task, args=(2,))
    p2.start()

結果:
我真帥
我真帥
1
2

 

 

         進程池         

   爲何要有進程池?進程池的概念。

   在程序實際處理問題過程當中,忙時會有成千上萬的任務須要被執行,閒時可能只有零星任務。那麼在成千上萬個任務須要被執行的時候,咱們就須要去建立成千上萬個進程麼?首先,建立進程須要消耗時間,銷燬進程也須要消耗時間。第二即使開啓了成千上萬的進程,操做系統也不能讓他們同時執行,這樣反而會影響程序的效率。所以咱們不能無限制的根據任務開啓或者結束進程。那麼咱們要怎麼作呢?

   定義一個池子,在裏面放上固定數量的進程,有需求來了,就拿一個池中的進程來處理任務,等處處理完畢,進程並不關閉,而是將進程再放回進程池中繼續等待任務。若是有不少任務須要執行,池中的進程數量不夠,任務就要等待以前的進程執行任務完畢歸來,拿到空閒進程才能繼續執行。也就是說,池中進程的數量是固定的,那麼同一時間最多有固定數量的進程在運行。這樣不會增長操做系統的調度難度,還節省了開閉進程的時間,也必定程度上可以實現併發效果。

import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def task(arg):
    time.sleep(2)
    print(arg)

if __name__ == '__main__':

    pool = ProcessPoolExecutor(5)
    for i in range(10):
        pool.submit(task,i)

結果:
#2秒後
0
2
1
3
4
#2秒後
5
7
6
8
9

  因此,咱們能夠利用進程池,寫一個小爬蟲的實例:

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

def get_page(url,pattern):
    response=urlopen(url).read().decode('utf-8')
    return pattern,response

def parse_page(info):
    pattern,page_content=info
    res=re.findall(pattern,page_content)
    for item in res:
        dic={
            'index':item[0].strip(),
            'title':item[1].strip(),
            'actor':item[2].strip(),
            'time':item[3].strip(),
        }
        print(dic)
if __name__ == '__main__':
    regex = r'<dd>.*?<.*?class="board-index.*?>(\d+)</i>.*?title="(.*?)".*?class="movie-item-info".*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>'
    pattern1=re.compile(regex,re.S)

    url_dic={
        'http://maoyan.com/board/7':pattern1,
    }

    p=Pool()
    res_l=[]
    for url,pattern in url_dic.items():
        res=p.apply_async(get_page,args=(url,pattern),callback=parse_page)
        res_l.append(res)

    for i in res_l:
        i.get()
貓眼---爬蟲

 

 

                               線程                               

          進程           

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

     線程的出現緣由       

      進程有不少優勢,它提供了多道編程,讓咱們感受咱們每一個人都擁有本身的CPU和其餘資源,能夠提升計算機的利用率。可是進程仍是有不少缺陷的,主要體如今兩點上:

  • 進程只能在一個時間幹一件事,若是想同時幹兩件事或多件事,進程就無能爲力了。

  • 進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行

  好比:若是把咱們在寫代碼的時候想要寫代碼的時候能夠聽歌和聊天.可是若是隻是提供進程這個機制的話,那麼咱們就只能寫完代碼,在聽歌,而後再聊天,不能再同時進行.

  因此咱們要解決這個問題,咱們徹底可讓寫代碼,聽歌,聊天三個獨立的過程,並行起來,這樣很明顯能夠提升效率。而實際的操做系統中,也一樣引入了這種相似的機制——線程。

 

      線程的出現       

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

       線程和進程的關係          

      線程與進程的區別 : 

    ● 地址空間和其餘資源(好比打開文件) : 進程之間相互獨立,同一進程的各線程之間共享資源.某進程內的線程和其餘進程內的線程是不可見的.

    ● 通訊 : 進程間通訊IPC,線程間能夠直接讀寫進程數據段(如全局變量)來進行通訊(--須要進程同步和互斥手段的輔助,以保證數據的一致性).

    ● 調度和切換 : 線程上下文切換比進程上下文切換要快的多.

    ● 在多線程操做系統中,進程不是一個可執行的實體.

              ***經過一個漫畫了解線程和進程(網上找的)***

       進程和線程的區別(面試):

     1.進程是CPU分配的最小單元.

        線程是CPU計算的最早單元.

        2.一個進程能夠有多個線程.

                  3.對於Python來講,它的進程和線程和其餘語言有差別,是有GIL鎖的

      GIL鎖保證一個進程中同一時刻只有一個線程被CPU調度.

        線程的特色          

    在多線程的操做系統中,一般是在一個進程中包括多個線程,每個線程都是做爲利用CPU的基本單元,是花費最小開銷的實體.

     線程的屬性 : 

    ● 輕型實體

      線程上的實體基本上不擁有系統資源,只是有一點必不可少的,能保證獨立運行的資源.

      線程的實體包括程序,數據和TCB.線程是動態概念,他的動態特性由線程控制塊TCB描述.

TCB包括如下信息:
(1)線程狀態。
(2)當線程不運行時,被保存的現場資源。
(3)一組執行堆棧。
(4)存放每一個線程的局部變量主存區。
(5)訪問同一個進程中的主存和其它資源。
用於指示被執行指令序列的程序計數器、保留局部變量、少數狀態參數和返回地址等的一組寄存器和堆棧。
TCB包括如下信息

    ● 獨立調度和分派的基本單位.

      在多線程OS中,線程是能獨立運行的最小單位,於是也是獨立調度和分派的基本單位,因爲現場很"輕",故線程的切換很是迅速且開銷小(在同一進程中的線程.)

    ● 共享進程資源

      線程在同一進程中的各個線程,均可以共享該進程所擁有的資源,這首先表如今 : 全部線程都具備相同的進程id,意味着,線程能夠訪問該進程的每個內存資源:此外,還能夠訪問進程所擁有的已經打開文件,定時器,信號量機構等.因爲同一個進程內的線程共享內存和文件,全部線程直接互相通訊沒必要調用內核.

    ● 可併發執行

      在同一個進程的多個線程之間,能夠併發執行,甚至容許在一個進程中全部線程都能併發執行:一樣,不一樣進程中的線程也能併發執行,充分利用和發揮了處理機制與外圍設備並行工做的能力.

      使用線程的實際場景       

  

        開啓一個字處理軟件進程,該進程確定須要辦不止一件事情,好比監聽鍵盤輸入,處理文字,定時自動將文字保存到硬盤,這三個任務操做的都是同一塊數據,於是不能用多進程。只能在一個進程裏併發地開啓三個線程,若是是單線程,那就只能是,鍵盤輸入時,不能處理文字和自動保存,自動保存時又不能輸入和處理文字。

                           Python中的線程                        

      Python中本身是沒有線程和進程的,Python是調用操做系統中的線程和進程.

          全局解釋器---GIL鎖             

        Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到要在主循環中,同時只有一個線程在執行。雖然 Python 解釋器中能夠「運行」多個線程,但在任意時刻只有一個線程在解釋器中運行。
  對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,因此,GIL鎖的做用就是保證一個進程中在同一時刻只要一個線程能夠運行(被CPU調度).

       注意 : 默認GIL鎖在執行100個CPU指令後換另一個線程執行.

      因此 , 因爲在Python中存在一個GIL鎖:

        ---致使 : 多線程沒法利用多核的優點

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

       線程的建立          

#基本使用

import threading

def func(arg):
    print(arg)
t = threading.Thread(target=func,args=(112,))     #子線程
t.start()
print(123)                                        #主線程

結果:
112
123
● 主線程默認等子線程執行完畢
import threading
import time

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

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

t2 = threading.Thread(target=func,args=(3,))
t2.start()
print("我真帥")

結果:
我真帥
#1秒後
1
#3秒後
3
主線程默認等子線程執行完畢

  ● 主線程再也不等,主線程終止則因此線程終止   setDaemon()

import threading
import time

def func(arg):
    time.sleep(2)
    print(arg)

t1 = threading.Thread(target=func,args=(3,))
t1.setDaemon(True)      #使主線程不等子線程
t1.start()

t2 = threading.Thread(target=func,args=(5,))
t2.setDaemon(True)
t2.start()

print("我真帥")
View Code

  ●開發者能夠控制主線程等待子線程(最多等待時間)  join()

import threading
import time

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

print("我是有多帥")
t1 = threading.Thread(target=func,args=(3,))
t1.start()
t1.join()

print("很是帥")
t2 = threading.Thread(target=func,args=(5,))
t2.start()
t2.join()

print("大實話")

結果:
我是有多帥
#3秒後
3
很是帥
#5秒後
5
大實話
無參數
import threading
import time

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

print("我是有多帥")
t1 = threading.Thread(target=func,args=(5,))
t1.start()
t1.join(2)

print("很是帥")
t2 = threading.Thread(target=func,args=(9,))
t2.start()
t2.join(2)

print("大實話")

結果:
我是有多帥
#2秒後
很是帥
#2秒後
大實話
#5秒後(和大實話差1秒)
5
#9秒後
9
有參數

     注意:join() :   

    無參數,讓主線程在這裏等着,等到子線程t1執行完畢,才能夠繼續往下走。
   有參數,讓主線程在這裏最多等待n秒,不管是否執行完畢,會繼續往下走。
● 線程名稱 srtName()
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)
線程名稱
● 線程本質
import threading

def func(arg):
    print(arg)

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

# start 是開始運行線程嗎?不是
# start 告訴cpu,我已經準備就緒,你能夠調度我了。

print("我好看")

結果:
66
我好看
線程本質
● 面向對象的多線程
import threading

線程方式:1 (常見)
def func(arg):
    print(arg)

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

# 多線程方式:2
class MyThread(threading.Thread):

    def run(self):
        print(11111,self._args,self._kwargs)

t1 = MyThread(args=(11,))
t1.start()

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

print('end')
View Code
● IO操做
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()
View Code

 

        總結 :
      
1.Python中存在一個GIL鎖。
        ---形成:多線程沒法利用多核優點
        ---解決:開多進程處理(浪費資源)

        2.IO密集型(文件的輸入/輸出/socket網絡通訊) : 多線程 (不佔用CPU)
        計算密集型 : 多進程
      3.爲何Python有GIL鎖?
       答 : Python語言創始人在開發這門語言時,目的是快速把語言開發出來,若是加上GIL鎖(C語言加鎖),切換時按照100條字節指令來進行線程間的切換.

        4.線程建立的越多越好嗎?
       答:很差,線程之間進行切換時,要作上下文處理.

 

         鎖           

          同步鎖    Lock(一次放一個)     

    線程安全:多線程操做時,內部會讓全部線程排隊處理.如:list / dict / Queue

    線程不安全 + 人 => 排隊處理

建立100個線程
    v = []
    加鎖
    -把本身的添加到列表中.安全的
    -讀取列表的最後一個.最後一個就不必定是本身的了,因此不安全
    解鎖
需求說明

 

import threading
import time

v = []
lock = threading.Lock()
def func(arg):
    lock.acquire()     上鎖
    v.append(arg)
    time.sleep(0.01)
    m = v[-1]
    print(arg,m)
    lock.release()     解鎖

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

   之後鎖一個代碼塊 

 

 

           遞歸鎖 RLock(一次放一個) 與死鎖            

所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程,以下就是死鎖.

import threading
import time

v = []
lock = threading.Lock()

def func(arg):
    lock.acquire()
    lock.acquire()

    v.append(arg)
    time.sleep(0.01)
    m = v[-1]
    print(arg,m)

    lock.release()
    lock.release()

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

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

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

import threading
import time

v = []
lock = threading.RLock()
def func(arg):
    lock.acquire()
    lock.acquire()

    v.append(arg)
    time.sleep(0.01)
    m = v[-1]
    print(arg,m)

    lock.release()
    lock.release()


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

 

 

        信號量 BoundedSemaphore  (一次放n個)         

同進程的同樣

BoundedSemaphore管理一個內置的計數器,
每當調用acquire()時內置計數器-1;
調用release() 時內置計數器+1;
計數器不能小於0;當計數器爲0時,acquire()將阻塞線程直到其餘線程調用release()。

實例:(同時只有3個線程能夠得到BoundedSemaphore,便可以限制最大鏈接數爲3):

import time
import threading

lock = threading.BoundedSemaphore(3)
def func(arg):
    lock.acquire()
    print(arg)
    time.sleep(2)
    lock.release()


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

結果:
#2秒後
0
1
2
#2秒後
3
4
5
#2秒後
6
7
8
9
10
11
12
13
14
15
16
17
18
19

 

 

          條件 Condition (一次放x個,根據條件)           

        使得線程等待,只有知足某條件時,才釋放n個線程

Python提供的Condition對象提供了對複雜線程同步問題的支持。Condition被稱爲條件變量,除了提供與Lock相似的acquire和release方法外,還提供了wait和notify方法。線程首先acquire一個條件變量,而後判斷一些條件。若是條件不知足則wait;若是條件知足,進行一些處理改變條件後,經過notify方法通知其餘線程,其餘處於wait狀態的線程接到通知後會從新判斷條件。不斷的重複這一過程,從而解決複雜的同步問題。
說明

  代碼說明: 

import time
import threading

lock = threading.Condition()

def func(arg):
    print('線程進來了')
    lock.acquire()
    lock.wait() # 加鎖

    print(arg)
    time.sleep(1)

    lock.release()


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

while True:
    inp = int(input('>>>'))

    lock.acquire()
    lock.notify(inp)
    lock.release()

結果:
線程進來了
線程進來了
線程進來了
線程進來了
線程進來了
線程進來了
線程進來了
線程進來了
線程進來了
線程進來了
>>>8 #輸入8. 由於誰也不知道CPU是先調用那個線程,因此沒有順序
>>>0
2
3
5
4
6
7
1
3  #輸入2 可是隻剩下2個
>>>8
9
實例1
import time
import threading

lock = threading.Condition()

def xxxx():
    print('來執行函數了')
    input(">>>")
    # ct = threading.current_thread() # 獲取當前線程
    # ct.getName()
    return True

def func(arg):
    print('線程進來了')
    lock.wait_for(xxxx)
    print(arg)
    time.sleep(1)

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

#說明:先循環,調用func函數,打印"線程來了",等待調用函數xxxx,打印"來執行函數了",用戶輸入數字,返回arg = i ,因此不管輸入什麼都是一個線程執行.
實例2

 

 

         事件 Event (一次放行)           

同進程的同樣

線程的一個關鍵特性是每一個線程都是獨立運行且狀態不可預測。若是程序中的其 他線程須要經過判斷某個線程的狀態來肯定本身下一步的操做,這時線程同步問題就會變得很是棘手。爲了解決這些問題,咱們須要使用threading庫中的Event對象。 對象包含一個可由線程設置的信號標誌,它容許線程等待某些事件的發生。在 初始狀況下,Event對象中的信號標誌被設置爲假。若是有線程等待一個Event對象, 而這個Event對象的標誌爲假,那麼這個線程將會被一直阻塞直至該標誌爲真。一個線程若是將一個Event對象的信號標誌設置爲真,它將喚醒全部等待這個Event對象的線程。若是一個線程等待一個已經被設置爲真的Event對象,那麼它將忽略這個事件, 繼續執行

event.isSet():返回event的狀態值;
event.wait():若是 event.isSet()==False將阻塞線程;
event.set(): 設置event的狀態值爲True,全部阻塞池的線程激活進入就緒狀態, 等待操做系統調度;
event.clear():恢復event的狀態值爲False。

 

 

import time
import threading

lock = threading.Event()

def func(arg):
    print('線程來了')
    lock.wait()     # 加鎖:紅燈
    print(arg)

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

input(">>>>")
lock.set()      # 綠燈

lock.clear()    # 再次變紅燈

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

input(">>>>")
lock.set()    #放行

 

                   總結 : 爲何要加鎖?

                              ---非線程安全

         ---控制一段代碼塊

 

        線程池     

   做用: 調用線程,規定併發的最大個數

from concurrent.futures import ThreadPoolExecutor
import time

def task(a1,a2):
    time.sleep(2)
    print(a1,a2)

# 建立了一個線程池(最多5個線程)
pool = ThreadPoolExecutor(5)

for i in range(40):
    # 去線程池中申請一個線程,讓線程執行task函數。
    pool.submit(task,i,8)

 

      threading.local        

   當咱們在考試的時候,會有收手機這個行爲,考場的老師會將咱們的手機放到一個收納盒中,等考試結束以後,才能夠拿本身的手機,可是這裏有一個弊端,會不會有人看到 iPhone X 就拿走了.可是當咱們爲每一個人都建立一個收納盒,放本身的手機在寫上名字,這樣是否是就不會拿錯了呢?

   因此 , threading.local 的做用 : 內部自動爲每個線程維護一個空間(以字典形式),用於當前存取屬於本身的值,保證數據隔離.

{
    線程ID: {...},
    線程ID: {...},
    線程ID: {...},
    線程ID: {...}
}

   示例 :  

import time
import threading

v = threading.local()

def func(arg):
    # 內部會爲當前線程建立一個空間用於存儲:phone=本身的值
    v.phone = arg
    time.sleep(2)
    print(v.phone,arg) # 去當前線程本身空間取值

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

結果:
0 0
1 1
4 4
2 2
5 5
3 3
6 6
7 7
9 9
8 8

 

"""
之後:Flask框架內部看到源碼 上下文管理

"""

import time
import threading
INFO = {}
class Local(object):

    def __getattr__(self, item):
        ident = threading.get_ident()
        return INFO[ident][item]

    def __setattr__(self, key, value):
        ident = threading.get_ident()
        if ident in INFO:
            INFO[ident][key] = value
        else:
            INFO[ident] = {key:value}

obj = Local()

def func(arg):
    obj.phone = arg # 調用對象的 __setattr__方法(「phone」,1)
    time.sleep(2)
    print(obj.phone,arg)


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

 

 

      生產者消費者模型         

      當咱們在節假日或者春運的時候買車票,咱們買票的和賣票的之間存在着必定的關係,由於賣票的不知道是要多少人才能知足人買票的需求,這就須要排隊.這樣就會形成必定的事件.可是,當咱們在買票和賣票的之間放一根管道:

 

 這樣,咱們能夠把需求放入管道里,就能夠去作別的事情,當票打印以後再通知你,這樣就能夠兩不當誤.咱們稱管道叫作:隊列.

       線程隊列       

   queue隊列: 使用 import queue

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

(隊列在線程編程中特別有用,當信息必須在多個線程之間安全交換時。)

queue.Queue() #先進先出

import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())

結果(先進先出):
first
second
third

    示例 : 

import time
import queue
import threading
q = queue.Queue() # 線程安全

def producer(id):
    """
    生產者
    :return:
    """
    while True:
        time.sleep(2)
        q.put('包子')
        print('廚師%s 生產了一個包子' %id )

for i in range(1,4):
    t = threading.Thread(target=producer,args=(i,))
    t.start()


def consumer(id):
    """
    消費者
    :return:
    """
    while True:
        time.sleep(1)
        v1 = q.get()
        print('顧客 %s 吃了一個包子' % id)

for i in range(1,3):
    t = threading.Thread(target=consumer,args=(i,))
    t.start()
相關文章
相關標籤/搜索