線程
線程是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])
總結
通過線程與進程的學習,能自己創建進程與線程,在後面的學習中,可以對這些不斷強化,進而完成掌握的功底!程序員是碼出來的,加油!