python學(xué)習(xí)日記-2016.7.25

1.多進(jìn)程

multiprocessing模塊就是跨平臺版本的多進(jìn)程模塊。multiprocessing模塊提供了一個Process類來代表一個進(jìn)程對象,下面的例子演示了啟動一個子進(jìn)程并等待其結(jié)束:

from multiprocessing import Process
import os
# 子進(jìn)程要執(zhí)行的代碼
def run_proc(name): 
  print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__': 
  print('Parent process %s.' % os.getpid()) 
  p = Process(target=run_proc, args=('test',)) 
  print('Child process will start.') 
  p.start() 
  p.join() 
  print('Child process end.')

運行結(jié)果如下

Parent process 10960
child process will start
run process test (7448)...
child process end

創(chuàng)建子進(jìn)程時,只需要傳入一個執(zhí)行函數(shù)和函數(shù)的參數(shù),創(chuàng)建一個Process實例,用start()方法啟動,這樣創(chuàng)建進(jìn)程比fork()還要簡單。

join()方法可以等待子進(jìn)程結(jié)束后再繼續(xù)往下運行,通常用于進(jìn)程間的同步。
如果要啟動大量的子進(jìn)程,可以用進(jìn)程池的方式批量創(chuàng)建子進(jìn)程:

2.進(jìn)程池

from multiprocessing 
import Poolimport os, time, random
def a(name):    
  print('run task %s (%s)' %(name,os.getpid()))    
  start=time.time()    
  time.sleep(random.random()*3)    
  end=time.time()    
  print('task %s runs %0.2f seconds' %(name,(end-start)))

if __name__=='__main__':    
  print('parent process %s' %os.getpid())    
  p=Pool(4)    
  for i in range(5):        
    p.apply_async(a, args=(i,))    
    print('wait for all subprocessed done')    
    p.close()    
    p.join()    
    print('all subprocess done')

執(zhí)行結(jié)果如下:

parent process 9404
wait for all subprocessed done
run task 0 (4896)
run task 1 (1964)
run task 2 (2296)
run task 3 (11092)
task 1 runs 0.59 seconds
run task 4 (1964)
task 4 runs 1.08 seconds
task 2 runs 2.25 seconds
task 3 runs 2.78 seconds
task 0 runs 2.97 seconds
all subprocess done

代碼解讀:
對Pool對象調(diào)用join()方法會等待所有子進(jìn)程執(zhí)行完畢,調(diào)用join()之前必須先調(diào)用close(),調(diào)用close()之后就不能繼續(xù)添加新的Process了。請注意輸出的結(jié)果,task0,1,2,3是立刻執(zhí)行的,而task4要等待前面某個task完成后才執(zhí)行,這是因為Pool的默認(rèn)大小在我的電腦上是4,因此,最多同時執(zhí)行4個進(jìn)程。這是Pool有意設(shè)計的限制,并不是操作系統(tǒng)的限制。如果改成:p = Pool(5)就可以同時跑5個進(jìn)程。由于Pool的默認(rèn)大小是CPU的核數(shù),如果你不幸擁有8核CPU,你要提交至少9個子進(jìn)程才能看到上面的等待效果。

3.子進(jìn)程

很多時候,子進(jìn)程并不是自身,而是一個外部進(jìn)程。我們創(chuàng)建了子進(jìn)程后,還需要控制子進(jìn)程的輸入和輸出。subprocess模塊可以讓我們非常方便地啟動一個子進(jìn)程,然后控制其輸入和輸出。下面的例子演示了如何在Python代碼中運行命令nslookup www.python.org,這和命令行直接運行的效果是一樣的:

import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])print('Exit code:', r)

運行結(jié)果:

$ nslookup www.python.org
Server: 192.168.19.4
Address: 192.168.19.4#53

Non-authoritative answer:
www.python.org canonical name = python.map.fastly.net.
Name: python.map.fastly.net
Address: 199.27.79.223
Exit code: 0

如果子進(jìn)程還需要輸入,則可以通過communicate()方法輸入:

import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)

上面的代碼相當(dāng)于在命令行執(zhí)行命令nslookup,然后手動輸入:
set q=mx
python.org
exit

運行結(jié)果如下:

$ nslookup
Traceback (most recent call last):
  File "E:/intelliJProject/firstpython/b.py", line 7, in <module>
    print(output.decode('utf-8'))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8 in position 2: invalid continuation byte

結(jié)果發(fā)現(xiàn)出錯了,不能使用utf-8,那么改成gbk之后再運行就可以了

$ nslookup
默認(rèn)服務(wù)器:  UnKnown
Address:  172.20.1.4

> > 服務(wù)器:  UnKnown
Address:  172.20.1.4

python.org  MX preference = 50, mail exchanger = mail.python.org

python.org  nameserver = ns3.p11.dynect.net
python.org  nameserver = ns4.p11.dynect.net
python.org  nameserver = ns1.p11.dynect.net
python.org  nameserver = ns2.p11.dynect.net
mail.python.org internet address = 188.166.95.178
mail.python.org AAAA IPv6 address = 2a03:b0c0:2:d0::71:1
ns1.p11.dynect.net  internet address = 208.78.70.11
ns2.p11.dynect.net  internet address = 204.13.250.11
ns3.p11.dynect.net  internet address = 208.78.71.11
ns4.p11.dynect.net  internet address = 204.13.251.11
> 
Exit code: 0

4.進(jìn)程間的通信

Process之間肯定是需要通信的,操作系統(tǒng)提供了很多機(jī)制來實現(xiàn)進(jìn)程間的通信。Python的multiprocessing模塊包裝了底層的機(jī)制,提供了Queue、Pipes等多種方式來交換數(shù)據(jù)。

我們以Queue為例,在父進(jìn)程中創(chuàng)建兩個子進(jìn)程,一個往Queue里寫數(shù)據(jù),一個從Queue里讀數(shù)據(jù):

#-*- coding=utf-8 -*-
from multiprocessing import Process,Queue
import os,time,random
def write(q):    
  print('process to write:%s' %os.getpid())    
  for value in ['A','B','C']:        
    print('put %s to queue...'%value)        
    q.put(value)        
    time.sleep(random.random())

def read(q):    
  print('process to read:%s' %os.getpid())    
  while(True):        
    value=q.get(True)        
    print('get %s frome queue...' %value)

if __name__=='__main__':    
  q=Queue()    
  pw=Process(target=write, args=(q,))    
  pr=Process(target=read, args=(q,))    
  pw.start()    
  pr.start()    
  pw.join()    
  pr.terminate()

運行結(jié)果:

process to write:9980
put A to queue...
process to read:6424
get A frome queue...
put B to queue...
get B frome queue...
put C to queue...
get C frome queue...

5.多線程

多任務(wù)可以由多進(jìn)程完成,也可以由一個進(jìn)程內(nèi)的多線程完成。我們前面提到了進(jìn)程是由若干線程組成的,一個進(jìn)程至少有一個線程。由于線程是操作系統(tǒng)直接支持的執(zhí)行單元,因此,高級語言通常都內(nèi)置多線程的支持,Python也不例外,并且,Python的線程是真正的Posix Thread,而不是模擬出來的線程。Python的標(biāo)準(zhǔn)庫提供了兩個模塊:_thread
和threading,_thread是低級模塊,threading是高級模塊,對_thread
進(jìn)行了封裝。絕大多數(shù)情況下,我們只需要使用threading這個高級模塊。啟動一個線程就是把一個函數(shù)傳入并創(chuàng)建Thread實例,然后調(diào)用start()
開始執(zhí)行:

import time, threading
# 新線程執(zhí)行的代碼:
def loop(): 
  print('thread %s is running...' % threading.current_thread().name) 
  n = 0 
  while n < 5: 
    n = n + 1 
    print('thread %s >>> %s' % (threading.current_thread().name, n))
    time.sleep(1) 
print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

執(zhí)行結(jié)果如下:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5t
hread LoopThread ended.
thread MainThread ended.

由于任何進(jìn)程默認(rèn)就會啟動一個線程,我們把該線程稱為主線程,主線程又可以啟動新的線程,Python的threading模塊有個current_thread()
函數(shù),它永遠(yuǎn)返回當(dāng)前線程的實例。主線程實例的名字叫MainThread
,子線程的名字在創(chuàng)建時指定,我們用LoopThread命名子線程。名字僅僅在打印時用來顯示,完全沒有其他意義,如果不起名字Python就自動給線程命名為Thread-1,Thread-2……

6.線程鎖

多線程和多進(jìn)程最大的不同在于,多進(jìn)程中,同一個變量,各自有一份拷貝存在于每個進(jìn)程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數(shù)據(jù)最大的危險在于多個線程同時改一個變量,把內(nèi)容給改亂了。
來看看多個線程同時操作一個變量怎么把內(nèi)容給改亂了:

#-*- coding=utf-8 -*-
import time,threading
balance=0
def change_it(n):    
  global balance    
  balance+=n    
  balance-=n

def runthread(n):    
  for i in range(100000):
    change_it(n)

t1=threading.Thread(target=runthread, args=(5,))
t2=threading.Thread(target=runthread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

每次運行的結(jié)果都不一樣

13
5
5
0
...

我們定義了一個共享變量balance,初始值為0,并且啟動兩個線程,先存后取,理論上結(jié)果應(yīng)該為0,但是,由于線程的調(diào)度是由操作系統(tǒng)決定的,當(dāng)t1、t2交替執(zhí)行時,只要循環(huán)次數(shù)足夠多,balance的結(jié)果就不一定是0了。
原因是因為高級語言的一條語句在CPU執(zhí)行時是若干條語句,即使一個簡單的計算:balance = balance + n也分兩步:
計算balance + n,存入臨時變量中;
將臨時變量的值賦給balance。
也就是可以看成:
x = balance + n
balance = x

由于x是局部變量,兩個線程各自都有自己的x,當(dāng)代碼正常執(zhí)行時:

初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2 # balance = 0
結(jié)果 balance = 0

但是t1和t2是交替運行的,如果操作系統(tǒng)以下面的順序執(zhí)行t1、t2:

初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
結(jié)果 balance = -8

究其原因,是因為修改balance需要多條語句,而執(zhí)行這幾條語句時,線程可能中斷,從而導(dǎo)致多個線程把同一個對象的內(nèi)容改亂了。兩個線程同時一存一取,就可能導(dǎo)致余額不對,你肯定不希望你的銀行存款莫名其妙地變成了負(fù)數(shù),所以,我們必須確保一個線程在修改balance的時候,別的線程一定不能改。如果我們要確保balance計算正確,就要給change_it()
上一把鎖,當(dāng)某個線程開始執(zhí)行change_it()時,我們說,該線程因為獲得了鎖,因此其他線程不能同時執(zhí)行change_it(),只能等待,直到鎖被釋放后,獲得該鎖以后才能改。由于鎖只有一個,無論多少線程,同一時刻最多只有一個線程持有該鎖,所以,不會造成修改的沖突。創(chuàng)建一個鎖就是通過threading.Lock()來實現(xiàn):

balance = 0
lock = threading.Lock()
def run_thread(n): 
  for i in range(100000): 
    # 先要獲取鎖: 
    lock.acquire() 
    try: 
    # 放心地改吧: 
      change_it(n) 
    finally: 
      # 改完了一定要釋放鎖: 
      lock.release()

當(dāng)多個線程同時執(zhí)行l(wèi)ock.acquire()時,只有一個線程能成功地獲取鎖,然后繼續(xù)執(zhí)行代碼,其他線程就繼續(xù)等待直到獲得鎖為止。獲得鎖的線程用完后一定要釋放鎖,否則那些苦苦等待鎖的線程將永遠(yuǎn)等待下去,成為死線程。所以我們用try...finally來確保鎖一定會被釋放。鎖的好處就是確保了某段關(guān)鍵代碼只能由一個線程從頭到尾完整地執(zhí)行,壞處當(dāng)然也很多,首先是阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了。其次,由于可以存在多個鎖,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時,可能會造成死鎖,導(dǎo)致多個線程全部掛起,既不能執(zhí)行,也無法結(jié)束,只能靠操作系統(tǒng)強(qiáng)制終止。

7.ThreadLocal

一個ThreadLocal變量雖然是全局變量,但每個線程都只能讀寫自己線程的獨立副本,互不干擾。ThreadLocal解決了參數(shù)在一個線程中各個函數(shù)之間互相傳遞的問題。

8.正則表達(dá)式

參考
正則表達(dá)式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958...
    喵在野閱讀 3,696評論 0 1
  • 1.線程的基本概念 1.1 線程 線程是應(yīng)用程序最小的執(zhí)行單元,線程與進(jìn)程類似,進(jìn)程可以看做程序的一次執(zhí)行,而線程...
    XYZeroing閱讀 1,006評論 1 16
  • 引言&動機(jī) 考慮一下這個場景,我們有10000條數(shù)據(jù)需要處理,處理每條數(shù)據(jù)需要花費1秒,但讀取數(shù)據(jù)只需要0.1秒,...
    chen_000閱讀 527評論 0 0
  • 總結(jié) 代碼 總結(jié) 1. 斷點續(xù)傳 思路也主要是邏輯上的,不是真正網(wǎng)絡(luò)的斷點續(xù)傳,而是在Table-1:link_l...
    OldSix1987閱讀 408評論 0 0
  • 人都是善良的嗎?我覺得不是的,而且善良并不是只擁有一顆柔軟的心,還要有溫暖的舉動。所以對于那些心善良,又能做出善良...
    我不夠好所以你別來閱讀 522評論 0 0