python線程和進程操作(含例子學習)

線程

線程是CPU使用的基本單元,由主線程來創建,並使用這個進程的資源。python中thread模塊提供支持,通過以下兩種方法創建線程:

  • 通過threading.Thread直接在線程中運行函數
  • 通過繼承threading.Thread類來創建線程

用threading.Thread直接在線程中運行函數

這裏我們就用這個函數創建一個線程,簡單模擬打印平方,這個函數原型我們首先熟悉一下,尤其是各個參數

Thread(group=None, target=None, Name=None, args=(), kwargs={}, *, daemon = None)
# 其中target參數就是運行的函數,args是傳入函數的參數元組。

例子:並行打印平方

運行效果:(並行效果喲!)
在這裏插入圖片描述

import threading


def thrfun(x, y):
    for i in range(x, y):
        print(str(i * i) + ';')


ta = threading.Thread(target=thrfun, args=(1, 6))
tb = threading.Thread(target=thrfun, args=(16, 21))
tc = threading.Thread(target=thrfun, args=(11,15))
ta.start()
tb.start()
tc.start()

博主用pycharm是同步執行,但是代碼本身含義是並行。因此大家如果使用pycharm同步的話,可以用ipython試試。

通過繼承threading.Thread類來創建線程

這個創建類來繼承就行了,瞭解面向對象的同學應該明白,肯定有init的方法,我們把要執行的函數寫到run函數裏,因爲它繼承可以重載。我們還是仍以求平方爲例子

例子:繼承類並行打印平方

實驗效果:
在這裏插入圖片描述
實驗代碼:

import threading

class myThread(threading.Thread):
    def __init__(self,mynum):
        super().__init__()
        self.mynum = mynum

    def run(self):
        for i in range(self.mynum,self.mynum+5):
            print(str(i*i)+';')

ma = myThread(1)
mb = myThread(16)
ma.start()
mb.start()



線程類Thread使用

上面的例子我們只是用thread裏的init和run方法,還有幾個重要的方法和屬性一起熟悉一下:
方法:

  • join([timeout])
  • isAlive()

屬性:

  • name
  • daemon
# join()方法的作用是當某個線程或函數執行時需等另一個線程
# --完成後才能繼續,則應調用另一個線程的join()方法
# 其中可選參數timeout用於指定線程運行的最長時間
# isAlive()方法用於查看線程是否運行

# 屬性
# name屬性是線程設置的線程名
# daemon屬性用來設置線程是否隨主線程退出而退出,一般來說,其屬性值爲True不會隨主線程退出而退出

下面就以兩個例子演示join和daemon的使用

例子:同步執行平方

實驗效果:
在這裏插入圖片描述
實驗代碼:

import threading
import time


def thrfun(x, y, thr=None):
    if thr:
        thr.join()
    else:
        time.sleep(2)
    for i in range(x, y):
        print(str(i * i) + ';')


ta = threading.Thread(target=thrfun, args=(1, 6))
tb = threading.Thread(target=thrfun, args=(16, 21,ta))


ta.start()
tb.start()

這裏的join函數本傳入了ta,也就是tb線程應等待ta結束後運行。

例子:將函數轉到後臺執行不做輸出

實驗效果:
在這裏插入圖片描述
實驗代碼:

import threading
import time
class  myThread(threading.Thread):
    def __init__(self,mynum):
        super().__init__()
        self.mynum = mynum

    def run(self):
        time.sleep(1)
        for i in range(self.mynum, self.mynum + 5):
            print(str(i * i) + ';')

def main():
    print('start...')
    ma = myThread(1)
    mb = myThread(16)
    ma.daemon =True
    mb.daemon = True
    ma.start()
    mb.start()
    print('end...')

if __name__ == '__main__':
    main()

將打印平方的工作,都交到後臺去了,這裏不做執行

RLock與Event講解

線程的隔離性就不需要彼此修改,因此產生鎖的概念。python中threading模塊中的對象Lock和RLock進行簡單的同步,對於同一個時刻只允許一個線程操作的數據對象,可以進行加鎖和解鎖進行隔離。過程原型如下

lock = threading.RLock() # 創建lock對象
lock.acquire() # 開始鎖定
pass # pass就是指我們要執行的語句和操作
lock.release() # 釋放鎖

線程間的通信我們用Event對象,Event實例管理着一個內部標誌,通過set()方法會將它設置爲True,使用clear()方法會將它重置爲False,wait([timeout])方法會使當前線程阻塞標誌爲True.
下面我們用兩個例子進行簡單模擬,一個是打印數據每次加30,第二個是兩個進程通信

例子:RLock對線程數據加30後輸出

實驗效果:
在這裏插入圖片描述
實驗代碼:

import threading
import time
class myThread(threading.Thread):
    def run(self):
        global x
        lock.acquire()
        for i in range(3):
            x += 10
        time.sleep(1)
        print(x)
        lock.release()

x = 0
lock = threading.RLock()
def main():
    thrs = []
    for item in range(5):
        thrs.append(myThread())

    for item in thrs:
        item.start()

if __name__ == '__main__':
    main()


例子:模擬兩個線程通過Event進行通信

實驗效果
在這裏插入圖片描述
實驗代碼:

import threading
import time

class myThreada(threading.Thread):
    def run(self):
        evt.wait()
        print(self.name,':Good morning!')
        evt.clear()
        time.sleep(1)
        evt.set()
        time.sleep(1)
        evt.wait()
        print(self.name,":I'm fine,thank you.")

class myThreadb(threading.Thread):
    def run(self):
        print(self.name,':Good morning!')
        evt.set()
        time.sleep(1)
        evt.wait()
        print(self.name,":How are you?")
        evt.clear()
        time.sleep(1)
        evt.set()


evt = threading.Event()

def main():
    John = myThreada()
    John.name = 'John'
    Smith = myThreadb()
    Smith.name = 'Smith'
    John.start()
    Smith.start()

if __name__ == '__main__':
    main()

進程

使用python的多進程模塊可以將工作分配給不受鎖定限制的單獨子進程。python3對多進程支持的是multiprocessing模塊和subprocess模塊。使用multiprocessing模塊創建和使用多線程,基本上和threading模塊的使用方法一致。創建進程使用multiprocessing.Process對象來完成。

進程基礎

下面我們用subprocess創建模塊,它用來創建新進程,獲取進程的輸入、輸出以及錯誤信息。它提供了更高級的接口,可以替換os.system、os.spawn*、popen等,subprocess模塊的基本函數如下:

call(args,*,stdin = None,stdout=None,stderr = None,shell=False,timeout=None)
# 創建新進程運行程序,輸入輸出綁定到父進程,返回新進程的退出碼
check_call(args,*,stdin = None,stdout=None,stderr = None,shell=False,timeout=None)
# 創建新進程運行程序,輸入和輸出綁定到父進程,退出碼爲0正常返回
# 否則,返回CalledProcessError
getstatusoutput(cmd)
# 創建新進程運行程序,元組形式返回新進程退出碼和輸出
getoutput(cmd)
# 創建新進程運行程序,返回新進程輸出(字符串)
call(args,*,input=None,stdin = None,stdout=None,stderr = None,shell=False,
		universal_newlines=False,timeout=None)
# 創建新進程運行程序,返回新進程的輸出(bytesarray)

  • stdin,stdout,stderr 用來處理輸入、輸出、錯誤信息
  • shell 是否使用一箇中間shell來執行(可以使用shell相關變量等)
  • input 爲命令行提供一個輸入信息(字符串),不能與stdin同時用
  • universal_newline 返回值和輸入值爲字符串而不是bytes

下面我們用一個例子演示如何操作,

例子:用進程打開py文件,並輸出

實驗準備,新建protest.py,內容爲:

print('hello world!')

實驗效果:
在這裏插入圖片描述
實驗代碼:

import subprocess
print('call() test:',subprocess.call(['python','protest.py']))
print('')
print('check_call() test:',subprocess.check_call(['python','protest.py']))
print('getstatusoutput() test:',subprocess.getstatusoutput(['python','protest.py']))
print('')
print('getoutput() test:',subprocess.getoutput(['python','protest.py']))
print('')
print('check_output() test:', subprocess.check_output(['python','protest.py']))

用Popen類創建進程

Popen也可以創建新進程,它是一個類,因此初始化參數如下:

class Popen(args, bufsize = -1,executable = None, stdin = None, stdout = None,stderr = None,
		preexec_fn = None,close_fds = True,shell = False,cwd = None,env = None,
		universal_newlines = False,startupinfo = None, creationflags = 0,
		restore_signals = True,start_new_session = False,pass_fds =())

因爲大部分參數都跟上面類似,我們用到啥就直接等於啥。以下常用方法

  • poll() 檢查子進程是否結束
  • wait(timeout=None) 等待子進程結束
  • communicate(input=None,timeout=None) 用於和紫禁城交互,發送標準輸入數據,返回由標準輸出和錯誤輸出構成的元組

其中常用屬性有:

  • pid 子進程的pid
  • returncode 子進程的退出碼

下面用兩個例子演示popen創建子進程和communicate函數的效果

例子:popen產生子進程,執行py源代碼

實驗準備:創建protest.py文件,內容爲:

print('hello world!')
print(a)

實驗效果:因a沒有賦值,所以會報錯
在這裏插入圖片描述
實驗代碼:

import subprocess
prcs = subprocess.Popen(['python','protest.py'],stdout=subprocess.PIPE,
                        stdin=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        universal_newlines=True,
                        shell=True)

prcs.communicate('These strings are from stdin.')
print('subprocess pid:',prcs.pid)
print('\nSTDOUT:')
print(str(prcs.communicate()[0]))
print('STDERR:')
print(prcs.communicate()[1])

例子:communicate實現進程數據傳遞

實驗準備,創建protest1.py內容爲:

a = input()
a = a.split(' ')
a[0] = str(int(a[0])+1)
print(' '.join(a))

實驗效果
在這裏插入圖片描述
實驗代碼:

import subprocess
processes = []
psum = 5
for i in range(psum):
    processes.append(subprocess.Popen(['python','protest1.py'],stdout=subprocess.PIPE,
                                      stdin=subprocess.PIPE,
                                      universal_newlines=True,
                                      shell = True))

processes[0].communicate('0 bouquet of flowers!')
for before,after in zip(processes[:psum],processes[1:]):
    after.communicate(before.communicate()[0])
print('\nSum of Process :%d'%psum)
print()
for item in processes:
    print(item.communicate()[0])

總結

通過線程與進程的學習,能自己創建進程與線程,在後面的學習中,可以對這些不斷強化,進而完成掌握的功底!程序員是碼出來的,加油!

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