進(jìn)程&線程

進(jìn)程與線程的區(qū)別

現(xiàn)在,多核CPU已經(jīng)非常普及了陈症,但是蔼水,即使過(guò)去的單核CPU,也可以執(zhí)行多任務(wù)爬凑。由于CPU執(zhí)行代碼都是順序執(zhí)行的徙缴,那么试伙,單核CPU是怎么執(zhí)行多任務(wù)的呢嘁信?
答案就是操作系統(tǒng)輪流讓各個(gè)任務(wù)交替執(zhí)行,任務(wù)1執(zhí)行0.01秒疏叨,切換到任務(wù)2潘靖,任務(wù)2執(zhí)行0.01秒,再切換到任務(wù)3蚤蔓,執(zhí)行0.01秒……這樣反復(fù)執(zhí)行下去卦溢。表面上看,每個(gè)任務(wù)都是交替執(zhí)行的秀又,但是单寂,由于CPU的執(zhí)行速度實(shí)在是太快了,我們感覺(jué)就像所有任務(wù)都在同時(shí)執(zhí)行一樣吐辙。
真正的并行執(zhí)行多任務(wù)只能在多核CPU上實(shí)現(xiàn)宣决,但是,由于任務(wù)數(shù)量遠(yuǎn)遠(yuǎn)多于CPU的核心數(shù)量昏苏,所以尊沸,操作系統(tǒng)也會(huì)自動(dòng)把很多任務(wù)輪流調(diào)度到每個(gè)核心上執(zhí)行。
對(duì)于操作系統(tǒng)來(lái)說(shuō)贤惯,一個(gè)任務(wù)就是一個(gè)進(jìn)程(Process)洼专,比如打開(kāi)一個(gè)瀏覽器就是啟動(dòng)一個(gè)瀏覽器進(jìn)程,打開(kāi)一個(gè)記事本就啟動(dòng)了一個(gè)記事本進(jìn)程孵构,打開(kāi)兩個(gè)記事本就啟動(dòng)了兩個(gè)記事本進(jìn)程屁商,打開(kāi)一個(gè)Word就啟動(dòng)了一個(gè)Word進(jìn)程。
有些進(jìn)程還不止同時(shí)干一件事颈墅,比如Word蜡镶,它可以同時(shí)進(jìn)行打字、拼寫檢查精盅、打印等事情帽哑。在一個(gè)進(jìn)程內(nèi)部,要同時(shí)干多件事叹俏,就需要同時(shí)運(yùn)行多個(gè)“子任務(wù)”妻枕,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。
由于每個(gè)進(jìn)程至少要干一件事,所以屡谐,一個(gè)進(jìn)程至少有一個(gè)線程述么。當(dāng)然,像Word這種復(fù)雜的進(jìn)程可以有多個(gè)線程愕掏,多個(gè)線程可以同時(shí)執(zhí)行度秘,多線程的執(zhí)行方式和多進(jìn)程是一樣的,也是由操作系統(tǒng)在多個(gè)線程之間快速切換饵撑,讓每個(gè)線程都短暫地交替運(yùn)行剑梳,看起來(lái)就像同時(shí)執(zhí)行一樣。當(dāng)然滑潘,真正地同時(shí)執(zhí)行多線程需要多核CPU才可能實(shí)現(xiàn)垢乙。

  • 定義方面:進(jìn)程是程序在某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng);線程是進(jìn)程中的一個(gè)執(zhí)行路徑语卤。
  • 角色方面:在支持線程機(jī)制的系統(tǒng)中追逮,進(jìn)程是系統(tǒng)資源分配的單位,線程是系統(tǒng)調(diào)度的單位粹舵。
  • 資源共享方面:進(jìn)程之間不能共享資源钮孵,而線程共享所在進(jìn)程的地址空間和其它資源。同時(shí)線程還有自己的棧和棧指針眼滤,程序計(jì)數(shù)器等寄存器巴席。
  • 獨(dú)立性方面:進(jìn)程有自己獨(dú)立的地址空間,而線程沒(méi)有柠偶,線程必須依賴于進(jìn)程而存在情妖,線程之間共享地址空間。

進(jìn)程和線程的最主要區(qū)別在于它們的系統(tǒng)資源管理方式不同诱担。進(jìn)程有獨(dú)立的地址空間毡证,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響蔫仙。而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑料睛,線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間摇邦,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉恤煞,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí)施籍,耗費(fèi)資源較大居扒,效率要差一些。


python多進(jìn)程的實(shí)現(xiàn)

如果你打算編寫多進(jìn)程的服務(wù)程序丑慎,Unix/Linux無(wú)疑是正確的選擇喜喂,在Unix/Linux上實(shí)現(xiàn)多進(jìn)程可以使用fork模塊瓤摧。但Windows沒(méi)有fork調(diào)用,難道在Windows上無(wú)法用Python編寫多進(jìn)程的程序玉吁?
由于Python是跨平臺(tái)的照弥,自然也應(yīng)該提供一個(gè)跨平臺(tái)的多進(jìn)程支持。multiprocessing模塊就是跨平臺(tái)版本的多進(jìn)程模塊进副。
window系統(tǒng)下这揣,需要注意的是要想啟動(dòng)一個(gè)子進(jìn)程,必須加上那句if __name__ == "main"影斑,進(jìn)程相關(guān)的要寫在這句下面给赞。

1、Process類

構(gòu)造方法:

Process([group [, target [, name [, args [, kwargs]]]]])

group: 線程組鸥昏,目前還沒(méi)有實(shí)現(xiàn)塞俱,庫(kù)引用中提示必須是None姐帚;
  target: 要執(zhí)行的方法吏垮;
  name: 進(jìn)程名,可為空罐旗;
  args/kwargs: 要傳入方法的參數(shù)膳汪。

實(shí)例方法:
  is_alive():返回進(jìn)程是否在運(yùn)行。
  join([timeout]):join()代表啟動(dòng)多進(jìn)程九秀,但是阻塞并發(fā)運(yùn)行遗嗽,一個(gè)進(jìn)程執(zhí)行結(jié)束后再執(zhí)行第二個(gè)進(jìn)程」难眩可以給其設(shè)置一個(gè)timeout值比如join(5)代表5秒后無(wú)論當(dāng)前進(jìn)程是否結(jié)果都繼續(xù)并發(fā)執(zhí)行第二個(gè)進(jìn)程痹换。
  start():進(jìn)程準(zhǔn)備就緒,等待CPU調(diào)度
  run():strat()調(diào)用run方法都弹,如果實(shí)例進(jìn)程時(shí)未制定傳入target娇豫,這start執(zhí)行默認(rèn)run()方法。
  terminate():不管任務(wù)是否完成畅厢,立即停止工作進(jìn)程冯痢。

屬性:
  authkey
  daemon:和線程的setDeamon功能一樣
  exitcode(進(jìn)程在運(yùn)行時(shí)為None、如果為–N框杜,表示被信號(hào)N結(jié)束)
  name:進(jìn)程名字浦楣。
  pid:進(jìn)程號(hào)。

使用Process類創(chuàng)建多個(gè)進(jìn)程試一下:

from multiprocessing import Process
import threading
import time

def foo(i):
    print 'say hi', i

if __name__ == '__main__':
    for i in range(10):
        p = Process(target=foo, args=(i,))
        p.start()
        # p.join()

運(yùn)行結(jié)果咪辱,可以看出多個(gè)進(jìn)程隨機(jī)順序執(zhí)行振劳。

say hi 4
say hi 3
say hi 5
say hi 2
say hi 1
say hi 6
say hi 0
say hi 7
say hi 8
say hi 9

Process finished with exit code 0

對(duì)比一下未注釋p.join()時(shí)的執(zhí)行結(jié)果,很明顯看出區(qū)別

say hi 0
say hi 1
say hi 2
say hi 3
say hi 4
say hi 5
say hi 6
say hi 7
say hi 8
say hi 9

Process finished with exit code 0
2油狂、Pool類

進(jìn)程池內(nèi)部維護(hù)一個(gè)進(jìn)程序列历恐,當(dāng)使用時(shí)庐杨,則去進(jìn)程池中獲取一個(gè)進(jìn)程,如果進(jìn)程池序列中沒(méi)有可供使用的進(jìn)進(jìn)程夹供,那么程序就會(huì)等待灵份,直到進(jìn)程池中有可用進(jìn)程為止。進(jìn)程池設(shè)置最好等于CPU核心數(shù)量

構(gòu)造方法:

Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])

processes:使用的工作進(jìn)程的數(shù)量哮洽,如果processes是None那么使用 os.cpu_count()返回的數(shù)量填渠,即默認(rèn)為cpu的核心數(shù)。
  initializer:如果initializer是None鸟辅,那么每一個(gè)工作進(jìn)程在開(kāi)始的時(shí)候會(huì)調(diào)用initializer(*initargs)氛什。
  maxtasksperchild:工作進(jìn)程退出之前可以完成的任務(wù)數(shù),完成后用一個(gè)新的工作進(jìn)程來(lái)替代原進(jìn)程匪凉,來(lái)讓閑置的資源被釋放枪眉。maxtasksperchild默認(rèn)是None,意味著只要Pool存在工作進(jìn)程就會(huì)一直存活再层。
  context: 用在制定工作進(jìn)程啟動(dòng)時(shí)的上下文贸铜,一般使用 multiprocessing.Pool() 或者一個(gè)context對(duì)象的Pool()方法來(lái)創(chuàng)建一個(gè)池,兩種方法都適當(dāng)?shù)脑O(shè)置了context

實(shí)例方法:
  apply(func[, args[, kwds]]):同步進(jìn)程池
  apply_async(func[, args[, kwds[, callback[, error_callback]]]]) :異步進(jìn)程池
  close() : 關(guān)閉進(jìn)程池聂受,阻止更多的任務(wù)提交到pool蒿秦,待任務(wù)完成后,工作進(jìn)程會(huì)退出蛋济。
  terminate() : 結(jié)束工作進(jìn)程棍鳖,不在處理未完成的任務(wù)
  join() : wait工作進(jìn)程的退出,在調(diào)用join()前碗旅,必須調(diào)用close() or terminate()渡处。這樣是因?yàn)楸唤K止的進(jìn)程需要被父進(jìn)程調(diào)用wait(join等價(jià)與wait),否則進(jìn)程會(huì)成為僵尸進(jìn)程祟辟。pool.join()必須使用在close() or terminate()之后医瘫。
例子一(異步進(jìn)程池):

# coding:utf-8
from  multiprocessing import Pool
import time


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

def Bar(arg):
    print arg

if __name__ == '__main__':
    t_start=time.time()
    pool = Pool(5)  #池內(nèi)5個(gè)進(jìn)程

    for i in range(10):
        pool.apply_async(func=Foo, args=(i,), callback=Bar)#異步,維持執(zhí)行的進(jìn)程總數(shù)為processes川尖,當(dāng)一個(gè)進(jìn)程執(zhí)行完畢后會(huì)添加新的進(jìn)程進(jìn)去

    pool.close()
    pool.join()  # 進(jìn)程池中進(jìn)程執(zhí)行完畢后再關(guān)閉登下,如果注釋,那么程序直接關(guān)閉叮喳。
    pool.terminate()
    t_end=time.time()
    t=t_end-t_start
    print 'the program time is :%s' %t

運(yùn)行結(jié)果如下被芳,結(jié)果打印時(shí)分兩次打印,每次打印5個(gè)數(shù)馍悟,即池內(nèi)5個(gè)進(jìn)程是異步運(yùn)行的畔濒。

101
100
102
103
104
106
105
107
108
109
the program time is :4.22099995613

Process finished with exit code 0

例子二(同步進(jìn)程池):

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from  multiprocessing import Process, Pool
import time


def Foo(i):
    time.sleep(1)
    print i + 100


if __name__ == '__main__':
    t_start=time.time()
    pool = Pool(5)

    for i in range(10):
        pool.apply(Foo, (i,)) #同步進(jìn)程

    pool.close()
    pool.join()  # 進(jìn)程池中進(jìn)程執(zhí)行完畢后再關(guān)閉,如果注釋锣咒,那么程序直接關(guān)閉侵状。
    t_end=time.time()
    t=t_end-t_start
    print 'the program time is :%s' %t

運(yùn)行結(jié)果赞弥,結(jié)果打印時(shí)一個(gè)一個(gè)打印,即池內(nèi)進(jìn)程是串行運(yùn)行的趣兄。

100
101
102
103
104
105
106
107
108
109
the program time is :4.334000110626221

Process finished with exit code 0

如果將pool.join()注釋掉绽左,那么進(jìn)程未執(zhí)行完就會(huì)被關(guān)閉,運(yùn)行結(jié)果就會(huì)成為下面這個(gè)樣子艇潭。

the program time is :0.7160000801086426
3拼窥、子進(jìn)程

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

import subprocess

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

運(yùn)行結(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)程還需要輸入捍壤,則可以通過(guò)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,然后手動(dòng)輸入:

set q=mx
python.org
exit

運(yùn)行結(jié)果如下:

$ nslookup
Server:        192.168.19.4
Address:    192.168.19.4#53

Non-authoritative answer:
python.org    mail exchanger = 50 mail.python.org.

Authoritative answers can be found from:
mail.python.org    internet address = 82.94.164.166
mail.python.org    has AAAA address 2001:888:2000:d::a6

Exit code: 0

可以參考http://www.xuebuyuan.com/2118731.html

4隔心、進(jìn)程間通信

Process之間肯定是需要通信的白群,操作系統(tǒng)提供了很多機(jī)制來(lái)實(shí)現(xiàn)進(jìn)程間的通信。Python的multiprocessing模塊包裝了底層的機(jī)制硬霍,提供了Queue、Pipes等多種方式來(lái)交換數(shù)據(jù)笼裳。
我們以Queue為例唯卖,在父進(jìn)程中創(chuàng)建兩個(gè)子進(jìn)程,一個(gè)往Queue里寫數(shù)據(jù)躬柬,一個(gè)從Queue里讀數(shù)據(jù):

from multiprocessing import Process, Queue
import os, time, random

# 寫數(shù)據(jù)進(jìn)程執(zhí)行的代碼:
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())

# 讀數(shù)據(jù)進(jìn)程執(zhí)行的代碼:
def read(q):
    print('Process to read: %s' % os.getpid())
    while True:
        value = q.get(True)
        print('Get %s from queue.' % value)

if __name__=='__main__':
    # 父進(jìn)程創(chuàng)建Queue拜轨,并傳給各個(gè)子進(jìn)程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 啟動(dòng)子進(jìn)程pw,寫入:
    pw.start()
    # 啟動(dòng)子進(jìn)程pr允青,讀取:
    pr.start()
    # 等待pw結(jié)束:
    pw.join()
    # pr進(jìn)程里是死循環(huán)橄碾,無(wú)法等待其結(jié)束,只能強(qiáng)行終止:
    pr.terminate()

運(yùn)行結(jié)果

Process to write: 50563
Put A to queue...
Process to read: 50564
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

python多線程的實(shí)現(xiàn)

Python的標(biāo)準(zhǔn)庫(kù)提供了兩個(gè)模塊:_thread和threading颠锉,_thread是低級(jí)模塊法牲,threading是高級(jí)模塊,對(duì)_thread進(jìn)行了封裝琼掠。絕大多數(shù)情況下拒垃,我們只需要使用threading這個(gè)高級(jí)模塊。

threading模塊提供的類
  Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local瓷蛙。

threading 模塊提供的常用方法
  threading.currentThread(): 返回當(dāng)前的線程變量悼瓮。
  threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list戈毒。正在運(yùn)行指線程啟動(dòng)后、結(jié)束前横堡,不包括啟動(dòng)前和終止后的線程埋市。
  threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果命贴。

threading 模塊提供的常量
  threading.TIMEOUT_MAX 設(shè)置threading全局超時(shí)時(shí)間恐疲。

1、Thread類

構(gòu)造方法:
Thread(group=None, target=None, name=None, args=(), kwargs={})

group: 線程組套么,目前還沒(méi)有實(shí)現(xiàn)培己,庫(kù)引用中提示必須是None;
  target: 要執(zhí)行的方法胚泌;
  name: 線程名省咨;
  args/kwargs: 要傳入方法的參數(shù)。

實(shí)例方法:
  isAlive(): 返回線程是否在運(yùn)行玷室。正在運(yùn)行指啟動(dòng)后零蓉、終止前。
  get/setName(name): 獲取/設(shè)置線程名穷缤。
  start(): 線程準(zhǔn)備就緒敌蜂,等待CPU調(diào)度
  is/setDaemon(bool): 獲取/設(shè)置是后臺(tái)線程(默認(rèn)前臺(tái)線程(False))。(在start之前設(shè)置)
    如果是后臺(tái)線程津肛,主線程執(zhí)行過(guò)程中章喉,后臺(tái)線程也在進(jìn)行,主線程執(zhí)行完畢后身坐,后臺(tái)線程不論成功與否秸脱,主線程和后臺(tái)線程均停止
  如果是前臺(tái)線程,主線程執(zhí)行過(guò)程中部蛇,前臺(tái)線程也在進(jìn)行摊唇,主線程執(zhí)行完畢后,等待前臺(tái)線程也執(zhí)行完成后涯鲁,程序停止
  start(): 啟動(dòng)線程巷查。
  join([timeout]): 阻塞當(dāng)前上下文環(huán)境的線程,直到調(diào)用此方法的線程終止或到達(dá)指定的timeout(可選參數(shù))抹腿。
使用例子一(未設(shè)置setDeamon)

# coding:utf-8
import threading
import time

def action(arg):
    time.sleep(1)
    print  'sub thread start!the thread name is:%s\r' % threading.currentThread().getName()
    print 'the arg is:%s\r' %arg
    time.sleep(1)

for i in xrange(4):
    t =threading.Thread(target=action,args=(i,))
    # t.setDaemon(True)#設(shè)置線程為后臺(tái)線程
    t.start()

print 'main_thread end!'

運(yùn)行結(jié)果岛请,從結(jié)果可以看出主線程執(zhí)行過(guò)程中,前臺(tái)線程也在進(jìn)行幢踏,主線程執(zhí)行完畢后髓需,等待前臺(tái)線程也執(zhí)行完成后,程序停止房蝉。

main_thread end!
sub thread start!the thread name is:Thread-1
the arg is:0
sub thread start!the thread name is:Thread-3
the arg is:2

the arg is:1
sub thread start!the thread name is:Thread-4
the arg is:3

Process finished with exit code 0

使用例子二(設(shè)置setDeamon)僚匆,當(dāng) t.setDaemon(True)未被注釋時(shí)的運(yùn)行結(jié)果如下微渠;

main_thread end!

Process finished with exit code 0

運(yùn)行結(jié)果,從結(jié)果可以看出當(dāng)主線程執(zhí)行完畢后咧擂,后臺(tái)線程不論成功與否逞盆,主線程和后臺(tái)線程均停止,所以沒(méi)打印后臺(tái)線程的信息松申。
join用法

#coding=utf-8
import threading
import time

def action(arg):
    time.sleep(1)
    print  ('sub thread start!the thread name is:%s    ' % threading.currentThread().getName())
    print ('the arg is:%s   ' %arg)
    time.sleep(1)

thread_list = []    #線程存放列表
for i in range(4):
    t =threading.Thread(target=action,args=(i,))
    t.setDaemon(True)
    thread_list.append(t)

for t in thread_list:
    t.start()

for t in thread_list:
    t.join()

print (type(thread_list[0]))

運(yùn)行結(jié)果云芦,驗(yàn)證了 join()阻塞當(dāng)前上下文環(huán)境的線程,直到調(diào)用此方法的線程終止或到達(dá)指定的timeout贸桶,即使設(shè)置了setDeamon(True)主線程依然要等待子線程結(jié)束舅逸。

sub thread start!the thread name is:Thread-1    
the arg is:0   
sub thread start!the thread name is:Thread-3    
the arg is:2   
sub thread start!the thread name is:Thread-4    
the arg is:3   
sub thread start!the thread name is:Thread-2    
the arg is:1   
<class 'threading.Thread'>

Process finished with exit code 0

注意t.join()不要和t.start()一同寫在循環(huán)里,否則會(huì)出現(xiàn)每個(gè)線程都被上一個(gè)線程的join阻塞皇筛,線程只能挨個(gè)執(zhí)行琉历,使得“多線程”失去了多線程意義。

2水醋、Lock旗笔、Rlock類

多線程和多進(jìn)程最大的不同在于,多進(jìn)程中拄踪,同一個(gè)變量蝇恶,各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響撮弧,而多線程中耀盗,所有變量都由所有線程共享,所以叛拷,任何一個(gè)變量都可以被任何一個(gè)線程修改,因此岂却,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)改一個(gè)變量,把內(nèi)容給改亂了躏哩。
為了多個(gè)線程同時(shí)操作一個(gè)內(nèi)存中的資源時(shí)不產(chǎn)生混亂,我們使用鎖扫尺。

Lock(指令鎖)是可用的最低級(jí)的同步指令筋栋。Lock處于鎖定狀態(tài)時(shí),不被特定的線程擁有弊攘。Lock包含兩種狀態(tài)——鎖定和非鎖定,以及兩個(gè)基本的方法襟交。

可以認(rèn)為L(zhǎng)ock有一個(gè)鎖定池捣域,當(dāng)線程請(qǐng)求鎖定時(shí),將線程至于池中焕梅,直到獲得鎖定后出池。池中的線程處于狀態(tài)圖中的同步阻塞狀態(tài)斜棚。

RLock(可重入鎖)是一個(gè)可以被同一個(gè)線程請(qǐng)求多次的同步指令蜗字。RLock使用了“擁有的線程”和“遞歸等級(jí)”的概念,處于鎖定狀態(tài)時(shí)粗梭,RLock被某個(gè)線程擁有级零。擁有RLock的線程可以再次調(diào)用acquire(),釋放鎖時(shí)需要調(diào)用release()相同次數(shù)奏纪。

可以認(rèn)為RLock包含一個(gè)鎖定池和一個(gè)初始值為0的計(jì)數(shù)器序调,每次成功調(diào)用 acquire()/release(),計(jì)數(shù)器將+1/-1发绢,為0時(shí)鎖處于未鎖定狀態(tài)。

簡(jiǎn)言之:Lock屬于全局经柴,Rlock屬于線程墩朦。

構(gòu)造方法:
Lock(),Rlock(),推薦使用Rlock()

**實(shí)例方法: **
  acquire([timeout]): 嘗試獲得鎖定牛哺。使線程進(jìn)入同步阻塞狀態(tài)陋气。
  release(): 釋放鎖。使用前線程必須已獲得鎖定恩伺,否則將拋出異常椰拒。

使用例子一(未加鎖)

import time, threading

# 假定這是你的銀行存款:
balance = 0

def change_it(n):
    # 先存后取,結(jié)果應(yīng)該為0:
    global balance
    balance = balance + n
    balance = balance - n

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

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

這段代碼先存后取褒脯,結(jié)果應(yīng)該恒為0缆毁,但是當(dāng)操作系統(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

使用例子二(加鎖)

#coding=utf-8
import time, threading

# 假定這是你的銀行存款:
balance = 0
lock = threading.Lock()

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

def change_it(n):
    # 先存后取沉御,結(jié)果應(yīng)該為0:
    global balance
    balance = balance + n
    balance = balance - n

# def run_thread(n):
#     for i in range(100000):
#         change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t3 = threading.Thread(target=run_thread, args=(7,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()

print(balance)

在有鎖的情形下昭灵,即使線程再多,也能保證資源不亂试疙。

使用例子三(Lock與Rlock)

#coding:utf-8
 
import threading
lock = threading.Lock() #Lock對(duì)象
lock.acquire()
lock.acquire()  #鎖未釋放抠蚣,無(wú)法再次獲取,一直等待從而產(chǎn)生死鎖缓屠。
lock.release()
lock.release()
print lock.acquire()
import threading
rLock = threading.RLock()  #RLock對(duì)象
rLock.acquire()
rLock.acquire() #在同一線程內(nèi)护侮,程序不會(huì)堵塞储耐。
rLock.release()
rLock.release()
print (rLock.acquire())

上面兩段代碼執(zhí)行結(jié)果,前者產(chǎn)生了死鎖长赞,后者打印結(jié)果為True。

3脯颜、Condition類

Condition(條件變量)通常與一個(gè)鎖關(guān)聯(lián)贩据。需要在多個(gè)Contidion中共享一個(gè)鎖時(shí),可以傳遞一個(gè)Lock/RLock實(shí)例給構(gòu)造方法矾芙,否則它將自己生成一個(gè)RLock實(shí)例近上。
  可以認(rèn)為,除了Lock帶有的鎖定池外葱绒,Condition還包含一個(gè)等待池斗锭,池中的線程處于等待阻塞狀態(tài),直到另一個(gè)線程調(diào)用notify()/notifyAll()通知骚秦;得到通知后線程進(jìn)入鎖定池等待鎖定璧微。

**構(gòu)造方法: **
Condition([lock/rlock])

**實(shí)例方法: **
  acquire([timeout])/release(): 調(diào)用關(guān)聯(lián)的鎖的相應(yīng)方法。
  wait([timeout]): 調(diào)用這個(gè)方法將使線程進(jìn)入Condition的等待池等待通知胞得,并釋放鎖屹电。使用前線程必須已獲得鎖定危号,否則將拋出異常。
  notify(): 調(diào)用這個(gè)方法將從等待池挑選一個(gè)線程并通知外莲,收到通知的線程將自動(dòng)調(diào)用acquire()嘗試獲得鎖定(進(jìn)入鎖定池);其他線程仍然在等待池中磨确。調(diào)用這個(gè)方法不會(huì)釋放鎖定乏奥。使用前線程必須已獲得鎖定,否則將拋出異常邓了。
  notifyAll(): 調(diào)用這個(gè)方法將通知等待池中所有的線程驶悟,這些線程都將進(jìn)入鎖定池嘗試獲得鎖定。調(diào)用這個(gè)方法不會(huì)釋放鎖定硫豆。使用前線程必須已獲得鎖定笼呆,否則將拋出異常。

4汗茄、Event類

Event(事件)是最簡(jiǎn)單的線程通信機(jī)制之一:一個(gè)線程通知事件铭若,其他線程等待事件叼屠。Event內(nèi)置了一個(gè)初始為False的標(biāo)志,當(dāng)調(diào)用set()時(shí)設(shè)為True镜雨,調(diào)用clear()時(shí)重置為 False荚坞。wait()將阻塞線程至等待阻塞狀態(tài)。

Event其實(shí)就是一個(gè)簡(jiǎn)化版的 Condition各淀。Event沒(méi)有鎖诡挂,無(wú)法使線程進(jìn)入同步阻塞狀態(tài)疗我。

**構(gòu)造方法: **
Event()

**實(shí)例方法: **
  isSet(): 當(dāng)內(nèi)置標(biāo)志為True時(shí)返回True。
  set(): 將標(biāo)志設(shè)為True旧找,并通知所有處于等待阻塞狀態(tài)的線程恢復(fù)運(yùn)行狀態(tài)。
  clear(): 將標(biāo)志設(shè)為False鞭缭。
  wait([timeout]): 如果標(biāo)志為True將立即返回魏颓,否則阻塞線程至等待阻塞狀態(tài)甸饱,等待其他線程調(diào)用set()。

5偷遗、timer類

Timer(定時(shí)器)是Thread的派生類驼壶,用于在指定時(shí)間后調(diào)用一個(gè)方法。

**構(gòu)造方法: **
Timer(interval, function, args=[], kwargs={})
  interval: 指定的時(shí)間
  function: 要執(zhí)行的方法
  args/kwargs: 方法的參數(shù)

**實(shí)例方法: **
Timer從Thread派生泵喘,沒(méi)有增加實(shí)例方法般妙。

# encoding: UTF-8
import threading


def func():
    print 'hello timer!'


timer = threading.Timer(5, func)  #線程延遲5秒后執(zhí)行
timer.start()
6股冗、local類

local是一個(gè)小寫字母開(kāi)頭的類,用于管理 thread-local(線程局部的)數(shù)據(jù)烹棉。對(duì)于同一個(gè)local怯疤,線程無(wú)法訪問(wèn)其他線程設(shè)置的屬性;線程設(shè)置的屬性不會(huì)被其他線程設(shè)置的同名屬性替換伏社。

可以把local看成是一個(gè)“線程-屬性字典”的字典,local封裝了從自身使用線程作為 key檢索對(duì)應(yīng)的屬性字典速妖、再使用屬性名作為key檢索屬性值的細(xì)節(jié)聪黎。

# encoding: UTF-8
import threading
 
local = threading.local()
local.tname = 'main'
 
def func():
    local.tname = 'notmain'
    print local.tname
 
t1 = threading.Thread(target=func)
t1.start()
t1.join()
 
print local.tname

運(yùn)行結(jié)果

notmain
main
7稿饰、其它

Python的線程是真正的Posix Thread,而不是模擬出來(lái)的線程旅择。按理說(shuō)侣姆,多于多核cpu,可以同時(shí)執(zhí)行多個(gè)線程汇歹。對(duì)于N核CPU偿凭,啟動(dòng)N個(gè)死循環(huán)線程應(yīng)該可以將cpu利用率拔高到100%。
實(shí)際上卻不是痰哨,因?yàn)镻ython的線程雖然是真正的線程斤斧,但解釋器執(zhí)行代碼時(shí)霎烙,有一個(gè)GIL鎖:Global Interpreter Lock,任何Python線程執(zhí)行前游昼,必須先獲得GIL鎖尝蠕,然后,每執(zhí)行100條字節(jié)碼廊佩,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行顽铸。這個(gè)GIL全局鎖實(shí)際上把所有線程的執(zhí)行代碼都給上了鎖鸯绿,所以瓶蝴,多線程在Python中只能交替執(zhí)行租幕,即使100個(gè)線程跑在100核CPU上,也只能用到1個(gè)核男窟。

GIL是Python解釋器設(shè)計(jì)的歷史遺留問(wèn)題贾富,通常我們用的解釋器是官方實(shí)現(xiàn)的CPython颤枪,要真正利用多核,除非重寫一個(gè)不帶GIL的解釋器扇住。

所以盗胀,在Python中,可以使用多線程女阀,但不要指望能有效利用多核屑迂。如果一定要通過(guò)多線程利用多核屈糊,那只能通過(guò)C擴(kuò)展來(lái)實(shí)現(xiàn),不過(guò)這樣就失去了Python簡(jiǎn)單易用的特點(diǎn)夫晌。

不過(guò),也不用過(guò)于擔(dān)心所袁,Python雖然不能利用多線程實(shí)現(xiàn)多核任務(wù)凶掰,但可以通過(guò)多進(jìn)程實(shí)現(xiàn)多核任務(wù)。多個(gè)Python進(jìn)程有各自獨(dú)立的GIL鎖前翎,互不影響港华。

參考:
http://www.cnblogs.com/tkqasn/p/5700281.html http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192823818768cd506abbc94eb5916192364506fa5d000

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末立宜,一起剝皮案震驚了整個(gè)濱河市臊岸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灯帮,老刑警劉巖蜘澜,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鄙信,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡银受,警方通過(guò)查閱死者的電腦和手機(jī)鸦采,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門渔伯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人选浑,你說(shuō)我怎么就攤上這事古徒。” “怎么了代态?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵疹吃,是天一觀的道長(zhǎng)萨驶。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么豆挽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任帮哈,我火速辦了婚禮,結(jié)果婚禮上咖刃,老公的妹妹穿的比我還像新娘憾筏。我一直安慰自己氧腰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布箩帚。 她就那樣靜靜地躺著紧帕,像睡著了一般桅打。 火紅的嫁衣襯著肌膚如雪愈案。 梳的紋絲不亂的頭發(fā)上刻帚,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天涩嚣,我揣著相機(jī)與錄音航厚,去河邊找鬼。 笑死眯漩,一個(gè)胖子當(dāng)著我的面吹牛麻顶,可吹牛的內(nèi)容都是我干的辅肾。 我是一名探鬼主播角溃,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼左冬!你這毒婦竟也來(lái)了慧耍?” 一聲冷哼從身側(cè)響起概龄,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤旁钧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后嚎幸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體寄猩,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年箍铭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椎镣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惊科,靈堂內(nèi)的尸體忽然破棺而出馆截,到底是詐尸還是另有隱情,我是刑警寧澤混卵,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布窖张,位于F島的核電站荤堪,受9級(jí)特大地震影響枢赔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碎赢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一肮塞、第九天 我趴在偏房一處隱蔽的房頂上張望姻锁。 院中可真熱鬧,春花似錦拷窜、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酵颁。三九已至材义,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間油挥,已是汗流浹背款熬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惋鹅,地道東北人殉簸。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓般卑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親沐鼠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饲梭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 又來(lái)到了一個(gè)老生常談的問(wèn)題憔涉,應(yīng)用層軟件開(kāi)發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢析苫? 今天就這個(gè)問(wèn)題開(kāi)始布蔗,來(lái)談?wù)劜?..
    tangsl閱讀 4,088評(píng)論 0 23
  • Day09的課程要點(diǎn)記錄詳細(xì)教程地址:Day9 - 進(jìn)程泽谨、線程、協(xié)程篇Python之路【第八篇】:堡壘機(jī)實(shí)例以及數(shù)...
    乘風(fēng)逐月閱讀 924評(píng)論 0 0
  • 一.線程與進(jìn)程相關(guān) 1.進(jìn)程 ??定義:進(jìn)程是具有獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng)特漩,進(jìn)程是操作系統(tǒng)分...
    Geeks_Liu閱讀 1,708評(píng)論 2 4
  • 進(jìn)程:正在運(yùn)行的程序?qū)嵗蚴郏菍?duì)資源管理的集合線程:系統(tǒng)調(diào)度的最小單位丁鹉,是對(duì)執(zhí)行指令的集合協(xié)程:用戶態(tài)的線程,主動(dòng)的...
    湯湯湯湯湯雪林閱讀 456評(píng)論 0 0
  • 母親漸漸隆起的腹部 像一座小山丘 我靜靜的躺在里面 溫暖而舒適 也沒(méi)有驚恐 可是我不知道 這種舒適的代價(jià) 便是母親...
    冷冬年閱讀 184評(píng)論 6 8