Python全棧之路系列之線程與進程

What is a Thread?

線程是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位,一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

在同一個進程內的線程的數據是可以進行互相訪問的。

線程的切換使用過上下文來實現的,比如有一本書,有a和b這兩個人(兩個線程)看,a看完之後記錄當前看到那一頁哪一行,然後交給b看,b看完之後記錄當前看到了那一頁哪一行,此時a又要看了,那麼a就通過上次記錄的值(上下文)直接找到上次看到了哪裏,然後繼續往下看。

What is a Process?

一個進程至少要包含一個線程,每個進程在啓動的時候就會自動的啓動一個線程,進程裏面的第一個線程就是主線程,每次在進程內創建的子線程都是由主線程進程創建和銷燬,子線程也可以由主線程創建出來的線程創建和銷燬線程。

進程是對各種資源管理的集合,比如要調用內存、CPU、網卡、聲卡等,進程要操作上述的硬件之前都必須要創建一個線程,進程裏面可以包含多個線程,QQ就是一個進程。

繼續拿QQ來說,比如我現在打卡了QQ的聊天窗口、個人信息窗口、設置窗口等,那麼每一個打開的窗口都是一個線程,他們都在執行不同的任務,比如聊天窗口這個線程可以和好友進行互動,聊天,視頻等,個人信息窗口我可以查看、修改自己的資料。

爲了進程安全起見,所以兩個進程之間的數據是不能夠互相訪問的(默認情況下),比如自己寫了一個應用程序,然後讓別人運行起來,那麼我的這個程序就可以訪問用戶啓動的其他應用,我可以通過我自己的程序去訪問QQ,然後拿到一些聊天記錄等比較隱祕的信息,那麼這個時候就不安全了,所以說進程與進程之間的數據是不可以互相訪問的,而且每一個進程的內存是獨立的。

進程與線程的區別?

  1. 線程是執行的指令集,進程是資源的集合
  2. 線程的啓動速度要比進程的啓動速度要快
  3. 兩個線程的執行速度是一樣的
  4. 進程與線程的運行速度是沒有可比性的
  5. 線程共享創建它的進程的內存空間,進程的內存是獨立的。
  6. 兩個線程共享的數據都是同一份數據,兩個子進程的數據不是共享的,而且數據是獨立的;
  7. 同一個進程的線程之間可以直接交流,同一個主進程的多個子進程之間是不可以進行交流,如果兩個進程之間需要通信,就必須要通過一箇中間代理來實現;
  8. 一個新的線程很容易被創建,一個新的進程創建需要對父進程進行一次克隆
  9. 一個線程可以控制和操作同一個進程裏的其他線程,線程與線程之間沒有隸屬關係,但是進程只能操作子進程
  10. 改變主線程,有可能會影響到其他線程的行爲,但是對於父進程的修改是不會影響子進程;

一個多併發的小腳本

4在學習過程中有什麼不懂得可以加我的
python學習交流扣扣qun,10667510
羣裏有不錯的學習視頻教程、開發工具與電子書籍。
與你分享python企業當下人才需求及怎麼從零基礎學習好python,和學習什麼內容

import threading
import time
def Princ(String):
    print('task', String)
    time.sleep(5)
# target=目標函數, args=傳入的參數
t1 = threading.Thread(target=Princ, args=('t1',))
t1.start()
t2 = threading.Thread(target=Princ, args=('t1',))
t2.start()
t3 = threading.Thread(target=Princ, args=('t1',))
t3.start()

參考文檔

進程與線程的一個簡單解釋

http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

Linux進程與線程的區別

https://my.oschina.net/cnyinlinux/blog/422207

線程

Thread module emulating a subset of Java’s threading model.

調用threading模塊調用線程的兩種方式

直接調用

import threading
import time
def Princ(String):
    print('task', String)
    time.sleep(5)
# target=目標函數, args=傳入的參數
t1 = threading.Thread(target=Princ, args=('t1',))
t1.start()
t2 = threading.Thread(target=Princ, args=('t1',))
t2.start()
t3 = threading.Thread(target=Princ, args=('t1',))
t3.start()

通過類調用

import threading
import time
class MyThreading(threading.Thread):
    def __init__(self, conn):
        super(MyThreading, self).__init__()
        self.conn = conn
    def run(self):
        print('run task', self.conn)
        time.sleep(5)
t1 = MyThreading('t1')
t2 = MyThreading('t2')
t1.start()
t2.start()

多線程

多線程在Python內實則就是一個假象,爲什麼這麼說呢,因爲CPU的處理速度是很快的,所以我們看起來以一個線程在執行多個任務,每個任務的執行速度是非常之快的,利用上下文切換來快速的切換任務,以至於我們根本感覺不到。

但是頻繁的使用上下文切換也是要耗費一定的資源,因爲單線程在每次切換任務的時候需要保存當前任務的上下文。

什麼時候用到多線程?

首先IO操作是不佔用CPU的,只有計算的時候纔會佔用CPU(譬如1+1=2),Python中的多線程不適合CPU密集型的任務,適合IO密集型的任務(sockt server)。

啓動多個線程

主進程在啓動之後會啓動一個主線程,下面的腳本中讓主線程啓動了多個子線程,然而啓動的子線程是獨立的,所以主線程不會等待子線程執行完畢,而是主線程繼續往下執行,並行執行。

for i in range(50):
    t = threading.Thread(target=Princ, args=('t-%s' % (i),))
    t.start()

join()

join() 方法可以讓程序等待每一個線程之後完成之後再往下執行,又成爲串行執行。

import threading
import time
def Princ(String):
    print('task', String)
    time.sleep(1)
for i in range(50):
    t = threading.Thread(target=Princ, args=('t-%s' % (i),))
    t.start()
    # 當前線程執行完畢之後在執行後面的線程
    t.join()

讓主線程阻塞,子現在並行執行

import threading
import time
def Princ(String):
    print('task', String)
    time.sleep(2)
# 執行子線程的時間
start_time = time.time()
# 存放線程的實例
t_objs = []
for i in range(50):
    t = threading.Thread(target=Princ, args=('t-%s' % (i),))
    t.start()
    # 爲了不讓後面的子線程阻塞,把當前的子線程放入到一個列表中
    t_objs.append(t)
# 循環所有子線程實例,等待所有子線程執行完畢
for t in t_objs:
    t.join()
# 當前時間減去開始時間就等於執行的過程中需要的時間
print(time.time() - start_time)

查看主線程與子線程

import threading
class MyThreading(threading.Thread):
    def __init__(self):
        super(MyThreading, self).__init__()
    def run(self):
        print('我是子線程: ', threading.current_thread())
t = MyThreading()
t.start()
print('我是主線程: ', threading.current_thread())

輸出如下:

C:\Python\Python35\python.exe E:/MyCodeProjects/進程與線程/s3.py
我是子線程:  <MyThreading(Thread-1, started 7724)>
我是主線程:  <_MainThread(MainThread, started 3680)>

Process finished with exit code 0

查看當前進程的活動線程個數

import threading
class MyThreading(threading.Thread):
    def __init__(self):
        super(MyThreading, self).__init__()
    def run(self):
        print('www.anshengme.com')
t = MyThreading()
t.start()
print('線程個數: ', threading.active_count())

輸出如下:

C:\Python\Python35\python.exe E:/MyCodeProjects/進程與線程/s3.py
www.anshengme.com
# 一個主線程和一個子線程
線程個數:  2

Process finished with exit code 0

Event

Event是線程間通信最間的機制之一:一個線程發送一個event信號,其他的線程則等待這個信號。用於主線程控制其他線程的執行。 Events 管理一個flag,這個flag可以使用set

()設置成True或者使用clear()重置爲False,wait()則用於阻塞,在flag爲True之前。flag默認爲False。

選項 描述
Event.wait([timeout]) 堵塞線程,直到Event對象內部標識位被設爲True或超時(如果提供了參數timeout)
Event.set() 將標識位設爲Ture
Event.clear() 將標識伴設爲False
Event.isSet() 判斷標識位是否爲Ture
#!/use/bin/env python
# _*_ coding: utf-8- _*_

import threading

def runthreading(event):
    print("Start...")
    event.wait()
    print("End...")
event_obj = threading.Event()
for n in range(10):
    t = threading.Thread(target=runthreading, args=(event_obj,))
    t.start()

event_obj.clear()
inp = input("True/False?>> ")
if inp == "True":
    event_obj.set()

守護進程(守護線程)

一個主進程可以啓動多個守護進程,但是主進程必須要一直運行,如果主進程掛掉了,那麼守護進程也會隨之掛掉

程序會等待主線程(進程)執行完畢,但是不會等待守護進程(線程)

import threading
import time

def Princ(String):
    print('task', String)
    time.sleep(2)
for i in range(50):
    t = threading.Thread(target=Princ, args=('t-%s' % (i),))
    t.setDaemon(True)  # 把當前線程設置爲守護線程,要在start之前設置
    t.start()

場景預設:比如現在有一個FTP服務,每一個用戶連接上去的時候都會創建一個守護線程,現在已經有300個用戶連接上去了,就是說已經創建了300個守護線程,但是突然之間FTP服務宕掉了,這個時候就不會等待守護線程執行完畢再退出,而是直接退出,如果是普通的線程,那麼就會登臺線程執行完畢再退出。

#!/use/bin/env python
# _*_ coding:utf-8 _*_

from multiprocessing import Process
import time
def runprocess(arg):
    print(arg)
    time.sleep(2)

p = Process(target=runprocess, args=(11,))
p.daemon=True
p.start()

print("end")

線程之間的數據交互與鎖(互斥鎖)

python2.x 需要加鎖,但是在 python3.x 上面就不需要了

# _*_ coding:utf-8 _*_
import threading
def Princ():
    # 獲取鎖
    lock.acquire()
    # 在函數內可以直接修改全局變量
    global number
    number += 1
    # 爲了避免讓程序出現串行,不能加sleep
    # time.sleep(1)
    # 釋放鎖
    lock.release()
# 鎖
lock = threading.Lock()
# 主線程的number
number = 0
t_objs = []
for i in range(100):
    t = threading.Thread(target=Princ)
    t.start()
    t_objs.append(t)
for t in t_objs:
    t.join()
print('Number:', number)

遞歸鎖(Lock/RLock)

import threading
def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num += 1
    lock.release()
    return num
def run2():
    print("grab the second part data")
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2
def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res, res2)
t_objs = []
if __name__ == '__main__':
    num, num2 = 0, 0
    lock = threading.RLock()  # RLock()類似創建了一個字典,每次退出的時候找到字典的值進行退出
    # lock = threading.Lock()  # Lock()會阻塞在這兒
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()
        t_objs.append(t)
for t in t_objs:
    t.join()
print(num, num2)

信號量(Semaphore)

互斥鎖 同時只允許一個線程更改數據,而 Semaphore 是同時允許一定數量的線程更改數據

import threading
import time
def run(n):
    semaphore.acquire()  # 獲取信號,信號可以有多把鎖
    time.sleep(1)  # 等待一秒鐘
    print("run the thread: %s\n" % n)
    semaphore.release()  # 釋放信號
t_objs = []
if __name__ == '__main__':
    semaphore = threading.BoundedSemaphore(5)  # 聲明一個信號量,最多允許5個線程同時運行
    for i in range(20):  # 運行20個線程
        t = threading.Thread(target=run, args=(i,))  # 創建線程
        t.start()  # 啓動線程
        t_objs.append(t)
for t in t_objs:
    t.join()
print('>>>>>>>>>>>>>')

以上代碼中,類似與創建了一個隊列,最多放5個任務,每執行完成一個任務就會往後面增加一個任務。

多進程

多進程的資源是獨立的,不可以互相訪問。

啓動一個進程

from multiprocessing import Process
import time
def f(name):
    time.sleep(2)
    print('hello', name)
if __name__ == '__main__':
    # 創建一個進程
    p = Process(target=f, args=('bob',))
    # 啓動
    p.start()
    # 等待進程執行完畢
    p.join()

在進程內啓動一個線程

from multiprocessing import Process
import threading
def Thread(String):
    print(String)
def Proces(String):
    print('hello', String)
    t = threading.Thread(target=Thread, args=('Thread %s' % (String),))  # 創建一個線程
    t.start()  # 啓動它
if __name__ == '__main__':
    p = Process(target=Proces, args=('World',))  # 創建一個進程
    p.start()  # 啓動
    p.join()  # 等待進程執行完畢

啓動一個多進程

from multiprocessing import Process
import time
def f(name):
    time.sleep(2)
    print('hello', name)
if __name__ == '__main__':
    for n in range(10):  # 創建一個進程
        p = Process(target=f, args=('bob %s' % (n),))
        # 啓動
        p.start()
        # 等待進程執行完畢

獲取啓動進程的PID

# _*_ coding:utf-8 _*_
from multiprocessing import Process
import os
def info(String):
    print(String)
    print('module name:', __name__)
    print('父進程的PID:', os.getppid())
    print('子進程的PID:', os.getpid())
    print("\n")
def ChildProcess():
    info('\033[31;1mChildProcess\033[0m')
if __name__ == '__main__':
    info('\033[32;1mTheParentProcess\033[0m')
    p = Process(target=ChildProcess)
    p.start()

輸出結果

C:\Python\Python35\python.exe E:/MyCodeProjects/多進程/s1.py
TheParentProcess
module name: __main__
# Pycharm的PID
父進程的PID: 6888
# 啓動的腳本PID
子進程的PID: 4660

ChildProcess
module name: __mp_main__
# 腳本的PID
父進程的PID: 4660
# 父進程啓動的子進程PID
子進程的PID: 8452

Process finished with exit code 0

進程間通信

默認情況下進程與進程之間是不可以互相通信的,若要實現互相通信則需要一箇中間件,另個進程之間通過中間件來實現通信,下面是進程間通信的幾種方式。

進程Queue

# _*_ coding:utf-8 _*_
from multiprocessing import Process, Queue
def ChildProcess(Q):
    Q.put(['Hello', None, 'World'])  # 在Queue裏面上傳一個列表
if __name__ == '__main__':
    q = Queue()  # 創建一個Queue
    p = Process(target=ChildProcess, args=(q,))  # 創建一個子進程,並把Queue傳給子進程,相當於克隆了一份Queue
    p.start()  # 啓動子進程
    print(q.get())  # 獲取q中的數據
    p.join()

管道(Pipes)

# _*_ coding:utf-8 _*_
from multiprocessing import Process, Pipe
def ChildProcess(conn):
    conn.send(['Hello', None, 'World'])  # 寫一段數據
    conn.close()  # 關閉
if __name__ == '__main__':
    parent_conn, child_conn = Pipe()  # 生成一個管道實例,parent_conn, child_conn管道的兩頭
    p = Process(target=ChildProcess, args=(child_conn,))
    p.start()
    print(parent_conn.recv())  # 收取消息
    p.join()

數據共享(Managers)

# _*_ coding:utf-8 _*_
# _*_ coding:utf-8 _*_
from multiprocessing import Process, Manager
import os

def ChildProcess(Dict, List):
    Dict['k1'] = 'v1'
    Dict['k2'] = 'v2'
    List.append(os.getpid())  # 獲取子進程的PID
    print(List)  # 輸出列表中的內容

if __name__ == '__main__':
    manager = Manager()  # 生成Manager對象
    Dict = manager.dict()  # 生成一個可以在多個進程之間傳遞共享的字典
    List = manager.list()  # 生成一個字典

    ProcessList = []  # 創建一個空列表,存放進程的對象,等待子進程執行用於

    for i in range(10):  # 生成是個子進程
        p = Process(target=ChildProcess, args=(Dict, List))  # 創建一個子進程
        p.start()  # 啓動
        ProcessList.append(p)  # 把子進程添加到p_list列表中

    for res in ProcessList:  # 循環所有的子進程
        res.join()  # 等待執行完畢
    print('\n')
    print(Dict)
    print(List)

輸出結果

C:\Python\Python35\python.exe E:/MyCodeProjects/多進程/s4.py
[5112]
[5112, 3448]
[5112, 3448, 4584]
[5112, 3448, 4584, 2128]
[5112, 3448, 4584, 2128, 11124]
[5112, 3448, 4584, 2128, 11124, 10628]
[5112, 3448, 4584, 2128, 11124, 10628, 5512]
[5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460]
[5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460, 10484]
[5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460, 10484, 6804]

{'k1': 'v1', 'k2': 'v2'}
[5112, 3448, 4584, 2128, 11124, 10628, 5512, 10460, 10484, 6804]

Process finished with exit code 0

鎖(Lock)

from multiprocessing import Process, Lock

def ChildProcess(l, i):
    l.acquire()  # 獲取鎖
    print('hello world', i)
    l.release()  # 釋放鎖

if __name__ == '__main__':
    lock = Lock()  # 生成Lock對象
    for num in range(10):
        Process(target=ChildProcess, args=(lock, num)).start()  # 創建並啓動一個子進程

進程池

同一時間啓動多少個進程

#!/use/bin/env python
# _*_ coding: utf-8 _*_

from multiprocessing import Pool
import time

def myFun(i):
    time.sleep(2)
    return i+100

def end_call(arg):
    print("end_call>>", arg)

p = Pool(5)  # 允許進程池內同時放入5個進程
for i in range(10):
    p.apply_async(func=myFun, args=(i,),callback=end_call) # # 平行執行,callback是主進程來調用
    # p.apply(func=Foo)  # 串行執行

print("end")
p.close()
p.join() # 進程池中進程執行完畢後再關閉,如果註釋,那麼程序直接關閉。

線程池

簡單實現

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import queue
import time
class MyThread:
    def __init__(self,max_num=10):
        self.queue = queue.Queue()
        for n in range(max_num):
            self.queue.put(threading.Thread)
    def get_thread(self):
        return self.queue.get()
    def put_thread(self):
        self.queue.put(threading.Thread)
pool = MyThread(5)
def RunThread(arg,pool):
    print(arg)
    time.sleep(2)
    pool.put_thread()
for n in range(30):
    thread = pool.get_thread()
    t = thread(target=RunThread, args=(n,pool,))
    t.start()

複雜版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import queue
import threading
import contextlib
import time

StopEvent = object()

class ThreadPool(object):

    def __init__(self, max_num, max_task_num = None):
        if max_task_num:
            self.q = queue.Queue(max_task_num)
        else:
            self.q = queue.Queue()
        self.max_num = max_num
        self.cancel = False
        self.terminal = False
        self.generate_list = []
        self.free_list = []

    def run(self, func, args, callback=None):
        """
        線程池執行一個任務
        :param func: 任務函數
        :param args: 任務函數所需參數
        :param callback: 任務執行失敗或成功後執行的回調函數,回調函數有兩個參數1、任務函數執行狀態;2、任務函數返回值(默認爲None,即:不執行回調函數)
        :return: 如果線程池已經終止,則返回True否則None
        """
        if self.cancel:
            return
        if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
            self.generate_thread()
        w = (func, args, callback,)
        self.q.put(w)

    def generate_thread(self):
        """
        創建一個線程
        """
        t = threading.Thread(target=self.call)
        t.start()

    def call(self):
        """
        循環去獲取任務函數並執行任務函數
        """
        current_thread = threading.currentThread()
        self.generate_list.append(current_thread)

        event = self.q.get()
        while event != StopEvent:

            func, arguments, callback = event
            try:
                result = func(*arguments)
                success = True
            except Exception as e:
                success = False
                result = None

            if callback is not None:
                try:
                    callback(success, result)
                except Exception as e:
                    pass

            with self.worker_state(self.free_list, current_thread):
                if self.terminal:
                    event = StopEvent
                else:
                    event = self.q.get()
        else:

            self.generate_list.remove(current_thread)

    def close(self):
        """
        執行完所有的任務後,所有線程停止
        """
        self.cancel = True
        full_size = len(self.generate_list)
        while full_size:
            self.q.put(StopEvent)
            full_size -= 1

    def terminate(self):
        """
        無論是否還有任務,終止線程
        """
        self.terminal = True

        while self.generate_list:
            self.q.put(StopEvent)

        self.q.queue.clear()

    @contextlib.contextmanager
    def worker_state(self, state_list, worker_thread):
        """
        用於記錄線程中正在等待的線程數
        """
        state_list.append(worker_thread)
        try:
            yield
        finally:
            state_list.remove(worker_thread)

# How to use

pool = ThreadPool(5)

def callback(status, result):
    # status, execute action status
    # result, execute action return value
    pass

def action(i):
    print(i)

for i in range(30):
    ret = pool.run(action, (i,), callback)

time.sleep(5)
print(len(pool.generate_list), len(pool.free_list))
print(len(pool.generate_list), len(pool.free_list))
pool.close()
pool.terminate()

什麼是IO密集型和CPU密集型?

IO密集型(I/O bound)

頻繁網絡傳輸、讀取硬盤及其他IO設備稱之爲IO密集型,最簡單的就是硬盤存取數據,IO操作並不會涉及到CPU。

計算密集型(CPU bound)

程序大部分在做計算、邏輯判斷、循環導致cpu佔用率很高的情況,稱之爲計算密集型,比如說python程序中執行了一段代碼 1+1 ,這就是在計算1+1的值

如果你依然在編程的世界裏迷茫,可以加入我們的Python學習扣qun:784758214,看看前輩們是如何學習的!交流經驗!自己是一名高級python開發工程師,從基礎的python腳本到web開發、爬蟲、django、數據挖掘等,零基礎到項目實戰的資料都有整理。送給每一位python的小夥伴!分享一些學習的方法和需要注意的小細節,點擊加入我們的python學習者聚集地

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