我們知道,同一進(jìn)程的多個(gè)線(xiàn)程之間是內(nèi)存共享的,這意味著,當(dāng)一個(gè)線(xiàn)程對(duì)全局變量做了修改,將會(huì)影響到其他所有線(xiàn)程,這是很危險(xiǎn)的。為了避免多個(gè)線(xiàn)程同時(shí)修改全局變量,我們就需要對(duì)全局變量的修改加鎖。
除了對(duì)全局變量的修改進(jìn)行加鎖,你可能也想到了可以使用線(xiàn)程自己的局部變量,因?yàn)榫植孔兞恐挥芯€(xiàn)程自己能看見(jiàn),對(duì)同一進(jìn)程的其他線(xiàn)程是不可訪(fǎng)問(wèn)的。確實(shí)如此,讓我們先看一個(gè)例子:
from threading import Thread, current_thread
def echo(num):
print current_thread().name, num
def calc():
print 'thread %s is running...' % current_thread().name
local_num = 0
for _ in xrange(10000):
local_num += 1
echo(local_num)
print 'thread %s ended.' % current_thread().name
if __name__ == '__main__':
print 'thread %s is running...' % current_thread().name
threads = []
for i in range(5):
threads.append(Thread(target=calc))
threads[i].start()
for i in range(5):
threads[i].join()
print 'thread %s ended.' % current_thread().name
在上面的代碼中,我們創(chuàng)建了 5 個(gè)線(xiàn)程,每個(gè)線(xiàn)程都對(duì)自己的局部變量 local_num 進(jìn)行 10000 次的加 1 操作。由于對(duì)線(xiàn)程局部變量的修改不會(huì)影響到其他線(xiàn)程,因此,我們可以看到,每個(gè)線(xiàn)程結(jié)束時(shí)打印的 local_num 的值都為 10000,執(zhí)行結(jié)果如下:
thread MainThread is running...
thread Thread-4 is running...
Thread-4 10000
thread Thread-4 ended.
thread Thread-5 is running...
Thread-5 10000
thread Thread-5 ended.
thread Thread-6 is running...
Thread-6 10000
thread Thread-6 ended.
thread Thread-7 is running...
Thread-7 10000
thread Thread-7 ended.
thread Thread-8 is running...
Thread-8 10000
thread Thread-8 ended.
thread MainThread ended.
上面這種線(xiàn)程使用自己的局部變量的方法雖然可以避免多線(xiàn)程對(duì)同一變量的訪(fǎng)問(wèn)沖突,但還是有一些問(wèn)題。在實(shí)際的開(kāi)發(fā)中,我們會(huì)調(diào)用很多函數(shù),每個(gè)函數(shù)又有很多個(gè)局部變量,這時(shí)每個(gè)函數(shù)都這么傳參數(shù)顯然是不可取的。
為了解決這個(gè)問(wèn)題,一個(gè)比較容易想到的做法就是創(chuàng)建一個(gè)全局字典,以線(xiàn)程的 ID 作為 key,線(xiàn)程的局部數(shù)據(jù)作為 value,這樣就可以消除函數(shù)傳參的問(wèn)題,代碼如下:
from threading import Thread, current_thread
global_dict = {}
def echo():
num = global_dict[current_thread()] # 線(xiàn)程根據(jù)自己的 ID 獲取數(shù)據(jù)
print current_thread().name, num
def calc():
print 'thread %s is running...' % current_thread().name
global_dict[current_thread()] = 0
for _ in xrange(10000):
global_dict[current_thread()] += 1
echo()
print 'thread %s ended.' % current_thread().name
if __name__ == '__main__':
print 'thread %s is running...' % current_thread().name
threads = []
for i in range(5):
threads.append(Thread(target=calc))
threads[i].start()
for i in range(5):
threads[i].join()
print 'thread %s ended.' % current_thread().name
看下執(zhí)行結(jié)果:
thread MainThread is running...
thread Thread-64 is running...
thread Thread-65 is running...
thread Thread-66 is running...
thread Thread-67 is running...
thread Thread-68 is running...
Thread-67 10000
thread Thread-67 ended.
Thread-65 10000
thread Thread-65 ended.
Thread-68 10000
thread Thread-68 ended.
Thread-66 10000
thread Thread-66 ended.
Thread-64 10000
thread Thread-64 ended.
thread MainThread ended.
上面的做法雖然消除了函數(shù)傳參的問(wèn)題,但是還是有些不完美,為了獲取線(xiàn)程的局部數(shù)據(jù),我們需要先獲取線(xiàn)程 ID,另外,global_dict 是個(gè)全局變量,所有線(xiàn)程都可以對(duì)它進(jìn)行修改,還是有些危險(xiǎn)。
那到底如何是好?
事實(shí)上,Python 提供了 ThreadLocal 對(duì)象,它真正做到了線(xiàn)程之間的數(shù)據(jù)隔離,而且不用查找 dict,代碼如下:
from threading import Thread, current_thread, local
global_data = local()
def echo():
num = global_data.num
print current_thread().name, num
def calc():
print 'thread %s is running...' % current_thread().name
global_data.num = 0
for _ in xrange(10000):
global_data.num += 1
echo()
print 'thread %s ended.' % current_thread().name
if __name__ == '__main__':
print 'thread %s is running...' % current_thread().name
threads = []
for i in range(5):
threads.append(Thread(target=calc))
threads[i].start()
for i in range(5):
threads[i].join()
print 'thread %s ended.' % current_thread().name
在上面的代碼中,global_data 就是 ThreadLocal 對(duì)象,你可以把它當(dāng)作一個(gè)全局變量,但它的每個(gè)屬性,比如 global_data.num
都是線(xiàn)程的局部變量,沒(méi)有訪(fǎng)問(wèn)沖突的問(wèn)題。
讓我們看下執(zhí)行結(jié)果:
thread MainThread is running...
thread Thread-94 is running...
thread Thread-95 is running...
thread Thread-96 is running...
thread Thread-97 is running...
thread Thread-98 is running...
Thread-96 10000
thread Thread-96 ended.
Thread-97 10000
thread Thread-97 ended.
Thread-95 10000
thread Thread-95 ended.
Thread-98 10000
thread Thread-98 ended.
Thread-94 10000
thread Thread-94 ended.
thread MainThread ended.