進(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)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(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,172評(píng)論 0 23
  • Day09的課程要點(diǎn)記錄詳細(xì)教程地址:Day9 - 進(jìn)程、線程、協(xié)程篇Python之路【第八篇】:堡壘機(jī)實(shí)例以及數(shù)...
    乘風(fēng)逐月閱讀 959評(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,748評(píng)論 2 4
  • 進(jìn)程:正在運(yùn)行的程序?qū)嵗菍?duì)資源管理的集合線程:系統(tǒng)調(diào)度的最小單位,是對(duì)執(zhí)行指令的集合協(xié)程:用戶態(tài)的線程,主動(dòng)的...
    湯湯湯湯湯雪林閱讀 475評(píng)論 0 0
  • 母親漸漸隆起的腹部 像一座小山丘 我靜靜的躺在里面 溫暖而舒適 也沒(méi)有驚恐 可是我不知道 這種舒適的代價(jià) 便是母親...
    冷冬年閱讀 192評(píng)論 6 8