Python進程VS線程

#1.進程和線程 隊列: 一、進程之間的通訊: q = multiprocessing.Queue() 二、進程池之間的通訊: q = multiprocessing.Manager().Queue() 三、線程之間的通訊: q = queue.Queue() ##1.功能數據庫

  • 進程,可以完成多任務,好比 在一臺電腦上可以同時運行多個QQ
  • 線程,可以完成多任務,好比 一個QQ中的多個聊天窗口

##2.定義的不一樣安全

  • 進程是系統進行資源分配和調度的一個獨立單位.
  • 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程本身基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),可是它可與同屬一個進程的其餘的線程共享進程所擁有的所有資源.

##3.區別bash

  • 一個程序至少有一個進程,一個進程至少有一個線程.
  • 線程的劃分尺度小於進程(資源比進程少),使得多線程程序的併發性高。
  • 進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存,從而極大地提升了程序的運行效率
  • 線程不可以獨立執行,必須依存在進程中

##4.優缺點 線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。 #2.同步的概念 ##1.多線程開發可能遇到的問題 假設兩個線程t1和t2都要對num=0進行增1運算,t1和t2都各對num修改10次,num的最終的結果應該爲20。 可是因爲是多線程訪問,有可能出現下面狀況: 在num=0時,t1取得num=0。此時系統把t1調度爲」sleeping」狀態,把t2轉換爲」running」狀態,t2也得到num=0。而後t2對獲得的值進行加1並賦給num,使得num=1。而後系統又把t2調度爲」sleeping」,把t1轉爲」running」。線程t1又把它以前獲得的0加1後賦值給num。這樣,明明t1和t2都完成了1次加1工做,但結果仍然是num=1。多線程

from threading import Thread
import time

g_num = 0

def test1():
    global g_num
    for i in range(1000000):
        g_num += 1

    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    for i in range(1000000):
        g_num += 1

    print("---test2---g_num=%d"%g_num)


p1 = Thread(target=test1)
p1.start()

# time.sleep(3) #取消屏蔽以後 再次運行程序,結果的不一樣

p2 = Thread(target=test2)
p2.start()

print("---g_num=%d---"%g_num)
複製代碼

運行結果卻不是2000000:併發

---g_num=129699---
---test2---g_num=1126024
---test1---g_num=1135562
複製代碼

取消屏蔽以後,再次運行結果以下:函數

---test1---g_num=1000000
---g_num=1025553---
---test2---g_num=2000000
複製代碼

問題產生的緣由就是沒有控制多個線程對同一資源的訪問,對數據形成破壞,使得線程運行的結果不可預期。這種現象稱爲「線程不安全」。 ##2.同步ui

  • 同步就是協同步調,按預約的前後次序進行運行。
  • 如進程、線程同步,可理解爲進程或線程A和B一塊配合,A執行到必定程度時要依靠B的某個結果,因而停下來,示意B運行;B依言執行,再將結果給A;A再繼續操做。

##3.解決線程不安全的方法 能夠經過線程同步來解決spa

  1. 系統調用t1,而後獲取到num的值爲0,此時上一把鎖,即不容許其餘如今操做num
  2. 對num的值進行+1
  3. 解鎖,此時num的值爲1,其餘的線程就可使用num了,並且是num的值不是0而是1
  4. 同理其餘線程在對num進行修改時,都要先上鎖,處理完後再解鎖,在上鎖的整個過程當中不容許其餘線程訪問,就保證了數據的正確性

#3.互斥鎖線程

  • 當多個線程幾乎同時修改某一個共享數據的時候,須要進行同步控制 線程同步可以保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。
  • 互斥鎖爲資源引入一個狀態:鎖定/非鎖定。
  • 某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲「鎖定」,其餘線程不能更改;直到該線程釋放資源,將資源的狀態變成「非鎖定」,其餘的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操做,從而保證了多線程狀況下數據的正確性。 threading模塊中定義了Lock類,能夠方便的處理鎖定:
#建立鎖
mutex = threading.Lock()
#鎖定
mutex.acquire([blocking])
#釋放
mutex.release()
複製代碼

其中,鎖定方法acquire能夠有一個blocking參數。code

  • 若是設定blocking爲True,則當前線程會堵塞,直到獲取到這個鎖爲止(若是沒有指定,那麼默認爲True)
  • 若是設定blocking爲False,則當前線程不會堵塞
from threading import Thread, Lock
import time
g_num = 0
def test1():
    global g_num
    for i in range(1000000):
        #True表示堵塞 即若是這個鎖在上鎖以前已經被上鎖了,那麼這個線程會在這裏一直等待到解鎖爲止 
        #False表示非堵塞,即無論本次調用可以成功上鎖,都不會卡在這,而是繼續執行下面的代碼
        mutexFlag = mutex.acquire(True) 
        if mutexFlag:
            g_num += 1
            mutex.release()

    print("---test1---g_num=%d"%g_num)
def test2():
    global g_num
    for i in range(1000000):
        mutexFlag = mutex.acquire(True) #True表示堵塞
        if mutexFlag:
            g_num += 1
            mutex.release()

    print("---test2---g_num=%d"%g_num)
#建立一個互斥鎖
#這個鎖默認是未上鎖的狀態
mutex = Lock()
p1 = Thread(target=test1)
p1.start()
p2 = Thread(target=test2)
p2.start()
print("---g_num=%d---"%g_num)
複製代碼

運行結果:

---g_num=19446---
---test1---g_num=1699950
---test2---g_num=2000000
複製代碼

加入互斥鎖後,運行結果與預期相符。 咱們能夠模擬一下賣票的程序:

# Python主要經過標準庫中的threading包來實現多線程
import threading  
import time
import os
def doChore():  # 做爲間隔 每次調用間隔0.5s
    time.sleep(0.5)
def booth(tid):
    global i
    global lock
    while True:
        lock.acquire()                      # 獲得一個鎖,鎖定
        if i != 0:
            i = i - 1                       # 售票 售出一張減小一張
            print(tid, ':now left:', i)    # 剩下的票數
            doChore()
        else:
            print("Thread_id", tid, " No more tickets")
            os._exit(0)                     # 票售完 退出程序
        lock.release()                      # 釋放鎖
        doChore()
#全局變量
i = 15                      # 初始化票數
lock = threading.Lock()     # 建立鎖
def main():
    # 總共設置了3個線程
    for k in range(3):
        # 建立線程; Python使用threading.Thread對象來表明線程
        new_thread = threading.Thread(target=booth, args=(k,))
        # 調用start()方法啓動線程
        new_thread.start()
if __name__ == '__main__':
    main()
複製代碼

運行結果:

0 :now left: 14
1 :now left: 13
0 :now left: 12
2 :now left: 11
1 :now left: 10
0 :now left: 9
1 :now left: 8
2 :now left: 7
0 :now left: 6
1 :now left: 5
2 :now left: 4
0 :now left: 3
2 :now left: 2
0 :now left: 1
1 :now left: 0
Thread_id 2  No more tickets
複製代碼
  • 上鎖解鎖過程 當一個線程調用鎖的acquire()方法得到鎖時,鎖就進入「locked」狀態。 每次只有一個線程能夠得到鎖。若是此時另外一個線程試圖得到這個鎖,該線程就會變爲「blocked」狀態,稱爲「阻塞」,直到擁有鎖的線程調用鎖的release()方法釋放鎖以後,鎖進入「unlocked」狀態。 線程調度程序從處於同步阻塞狀態的線程中選擇一個來得到鎖,並使得該線程進入運行(running)狀態。 鎖的好處:
  • 確保了某段關鍵代碼只能由一個線程從頭至尾完整地執行 鎖的壞處:
  • 阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地降低了
  • 因爲能夠存在多個鎖,不一樣的線程持有不一樣的鎖,並試圖獲取對方持有的鎖時,可能會形成死鎖

#4.多線程-非共享數據 對於多線程中全局變量和局部變量是否共享

  • 多線程局部變量
#coding=utf-8
    import threading
    import time

    class MyThread(threading.Thread):
        # 重寫 構造方法
        def __init__(self,num,sleepTime):
            threading.Thread.__init__(self)
            self.num = num
            self.sleepTime = sleepTime

        def run(self):
            self.num += 1
            time.sleep(self.sleepTime)
            print('線程(%s),num=%d'%(self.name, self.num))

    if __name__ == '__main__':
        mutex = threading.Lock()
        t1 = MyThread(100,5)
        t1.start()
        t2 = MyThread(200,1)
        t2.start()
複製代碼

運行結果:

線程(Thread-2),num=201
線程(Thread-1),num=101
複製代碼
  • 多線程全局變量
import threading
from time import sleep
def test(sleepTime):
    num = 1
    sleep(sleepTime)
    num+=1
    print('---(%s)--num=%d'%(threading.current_thread(), num))
if __name__ == '__main__':
    t1 = threading.Thread(target = test,args=(5,))
    t2 = threading.Thread(target = test,args=(1,))

    t1.start()
    t2.start()
複製代碼

運行結果:

---(<Thread(Thread-2, started 10876)>)--num=2
---(<Thread(Thread-1, started 7484)>)--num=2
複製代碼
  • 在多線程開發中,全局變量是多個線程都共享的數據,而局部變量等是各自線程的,是非共享的

#5.同步應用

  • 多個線程有序執行
from threading import Thread,Lock
from time import sleep
class Task1(Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print("------Task 1 -----")
                sleep(0.5)
                lock2.release()
class Task2(Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print("------Task 2 -----")
                sleep(0.5)
                lock3.release()
class Task3(Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print("------Task 3 -----")
                sleep(0.5)
                lock1.release()
#使用Lock建立出的鎖默認沒有「鎖上」
lock1 = Lock()
#建立另一把鎖,而且「鎖上」
lock2 = Lock()
lock2.acquire()
#建立另一把鎖,而且「鎖上」
lock3 = Lock()
lock3.acquire()
t1 = Task1()
t2 = Task2()
t3 = Task3()
t1.start()
t2.start()
t3.start()
複製代碼

運行結果:

------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
...........`
複製代碼
  • 可使用互斥鎖完成多個任務,有序的進程工做,這就是線程的同步

#6.生產者與消費者模式

  • Python的Queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(後入先出)隊列LifoQueue,和優先級隊列PriorityQueue。這些隊列都實現了鎖原語(能夠理解爲原子操做,即要麼不作,要麼就作完),可以在多線程中直接使用。可使用隊列來實現線程間的同步。
  • 用FIFO隊列實現上述生產者與消費者問題的代碼以下:
import threading,time
from queue import Queue
class Producer(threading.Thread):
    def run(self):
        global queue
        count = 0
        while True:
            if queue.qsize() < 1000:
                for i in range(100):
                    count = count +1
                    msg = '生成產品'+str(count)
                    queue.put(msg)
                    print(msg)
            time.sleep(0.5)
class Consumer(threading.Thread):
    def run(self):
        global queue
        while True:
            if queue.qsize() > 100:
                for i in range(3):
                    msg = self.name + '消費了 '+queue.get()
                    print(msg)
            time.sleep(1)
if __name__ == '__main__':
    queue = Queue()
    for i in range(500):
        queue.put('初始產品'+str(i))
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()
複製代碼

運行結果:

生成產品1
生成產品2
生成產品1
生成產品3
生成產品2
生成產品4
Thread-3消費了 初始產品0
生成產品3
生成產品5
Thread-3消費了 初始產品1
生成產品4
生成產品6
Thread-4消費了 初始產品2
Thread-3消費了 初始產品3
生成產品5
生成產品7
Thread-4消費了 初始產品4
生成產品6
生成產品8
Thread-5消費了 初始產品5
Thread-4消費了 初始產品6
............
複製代碼

此時就出現生產者與消費者的問題 ##1.Queue的說明 1.對於Queue,在多線程通訊之間扮演重要的角色 2.添加數據到隊列中,使用put()方法 3.從隊列中取數據,使用get()方法 4.判斷隊列中是否還有數據,使用qsize()方法 ##2.生產者消費者模式的說明

  • 使用生產者和消費者模式的緣由 在線程世界裏,生產者就是生產數據的線程,消費者就是消費數據的線程。在多線程開發當中,若是生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產數據。一樣的道理,若是消費者的處理能力大於生產者,那麼消費者就必須等待生產者。爲了解決這個問題因而引入了生產者和消費者模式。
  • 生產者消費者模式 生產者消費者模式是經過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通信,而經過阻塞隊列來進行通信,因此生產者生產完數據以後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就至關於一個緩衝區,平衡了生產者和消費者的處理能力。這個阻塞隊列就是用來給生產者和消費者解耦的。 ##3.ThreadLocal 在多線程環境下,每一個線程都有本身的數據。一個線程使用本身的局部變量比使用全局變量好,由於局部變量只有線程本身能看見,不會影響其餘線程,而全局變量的修改必須加鎖。 ###1.使用函數傳參的方法
def process_student(name):
    std = Student(name)
    # std是局部變量,可是每一個函數都要用它,所以必須傳進去:
    do_task_1(std)
    do_task_2(std)
def do_task_1(std):
    do_subtask_1(std)
    do_subtask_2(std)
def do_task_2(std):
    do_subtask_2(std)
    do_subtask_2(std)
複製代碼

說明:用局部變量也有問題,由於每一個線程處理不一樣的Student對象,不能共享。 ###2.使用全局字典的方法

import threading
# 建立字典對象:
myDict={}
def process_student():
    # 獲取當前線程關聯的student:
    std = myDict[threading.current_thread()]
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
    # 綁定ThreadLocal的student:
    myDict[threading.current_thread()] = name
    process_student()
t1 = threading.Thread(target=process_thread, args=('yongGe',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()
複製代碼

運行結果;

Hello, yongGe (in Thread-A)
Hello, 老王 (in Thread-B)
複製代碼

這種方式理論上是可行的,它最大的優勢是消除了std對象在每層函數中的傳遞問題,可是,每一個函數獲取std的代碼有點low。 ###3.使用ThreadLocal的方法

import threading
# 建立全局ThreadLocal對象:
local_school = threading.local()
def process_student():
    # 獲取當前線程關聯的student:
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
    # 綁定ThreadLocal的student:
    local_school.student = name
    process_student()
t1 = threading.Thread(target=process_thread, args=('erererbai',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('老王',), name='Thread-B')
t1.start()
t2.start()
複製代碼

運行結果:

Hello, erererbai (in Thread-A)
Hello, 老王 (in Thread-B)
複製代碼

說明: 全局變量local_school就是一個ThreadLocal對象,每一個Thread對它均可以讀寫student屬性,但互不影響。你能夠把local_school當作全局變量,但每一個屬性如local_school.student都是線程的局部變量,能夠任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內部會處理。 能夠理解爲全局變量local_school是一個dict,不但能夠用local_school.student,還能夠綁定其餘變量,如local_school.teacher等等。 ThreadLocal最經常使用的地方就是爲每一個線程綁定一個數據庫鏈接,HTTP請求,用戶身份信息等,這樣一個線程的全部調用到的處理函數均可以很是方便地訪問這些資源。

  • 一個ThreadLocal變量雖然是全局變量,但每一個線程都只能讀寫本身線程的獨立副本,互不干擾。ThreadLocal解決了參數在一個線程中各個函數之間互相傳遞的問題
相關文章
相關標籤/搜索