全局变量和局部变量
线程由进程创建,而且和进程共享一些资源。
由下面的例子可以看出线程之间的全局变量是共享的,局部变量是非共享的。
#coding=utf-8
import threading
import time
a = 100
def do_thread1():
print('in func do_thread1')
global a
a += 1
b = 'local str in thread1'
b += 'add'
print(a,b)
def do_thread2():
global a
b = 'local str in thread2'
time.sleep(2) #确保thread1先执行完
print('in func do_thread2')
print(a,b)
if __name__ == '__main__':
print('-------------main begin-------a:%d' % (a))
t1 = threading.Thread(target=do_thread1)
t2 = threading.Thread(target=do_thread2)
t1.start()
t2.start()
t1.join()
t2.join()
print('-------------main over-------a:%d' % (a))
结果:
-------------main begin-------a:100
in func do_thread1
101 local str in thread1add
in func do_thread2
101 local str in thread2
-------------main over-------a:101
锁mutex
由于线程之间的全局变量是共享的,因此在多线程访问的时候可以会有一些问题。看下面的例子:
#coding=utf-8
import threading
a = 0
def do_thread1():
global a
for i in range(1000000):
a += 1
def do_thread2():
global a
for i in range(1000000):
a += 1
if __name__ == '__main__':
print('---------main begin ----------')
t1 = threading.Thread(target=do_thread1)
t2 = threading.Thread(target=do_thread2)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
print('---------main end ----------')
结果:
---------main begin ----------
1232187
---------main end ----------
以上的代码中创建了两个线程,每个都是对全局变量做1000000次的加1操作,理论上最后全局变量的值应该是2000000,但是事实很难达到。(多次执行的结果可能不一样)这就是因为线程之间之间全局变量共享的而产生的。比如在thread1的某次循环中刚刚取到a的值,还没执行加1的操作,但是CPU却切换到thread2进行了一次加1操作,CPU再次切换执行thread1从原来的位置执行加1,这样就浪费了一次加1的机会,就会导致最后的结果不是2000000。如何解决这个问题呢:在访问共同资源的时候加锁。
#coding=utf-8
import threading
a = 0
def do_thread1():
global a
for i in range(1000000):
mutex.acquire()
a += 1
mutex.release()
def do_thread2():
global a
for i in range(1000000):
mutex.acquire() #加锁
a += 1
mutex.release() #解锁
if __name__ == '__main__':
print('---------main begin ----------')
mutex = threading.Lock() #创建一把锁
t1 = threading.Thread(target=do_thread1)
t2 = threading.Thread(target=do_thread2)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
print('---------main end ----------')
结果:
---------main begin ----------
2000000
---------main end ----------
上面的代码中先使用threading.Lock()方法获得一个锁,默认是没上锁的状态,当两个线程开始执行后,开始同时竞争这一把锁。那个线程先获得到锁哪个线程就可以继续执行,获得不到锁的线程只能在原地阻塞等待释放锁。周而复始,这样就保证了同一时刻只能有一个线程访问全局变量。
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进“unlocked”状态。
锁的好处:
- 确保了某段关键代码只能由一个线程从头到尾完整地执行
死锁
锁能保证多线程访问统一资源的安全性,也很容易出现死锁的情况。下面的代码中,假如thread1执行到time.sleep(1),紧接着就该执行mutex2上锁,此时发生CPU切换开始执行thread2,并且开始执行mutex1上锁,此时便发生了死锁。
死锁通常发生在一个线程占用了资源同时有等待其他线程资源的情况,因此当使用多线程的时候一定要注意避免这种情况的发生。
#coding=utf-8
import threading
import time
def do_thread1():
print(' in thread1 begin')
mutex1.acquire()
print('do something1 ...')
time.sleep(1)
if mutex2.acquire():
print('do something1_1 ...')
mutex2.release()
mutex1.release()
print(' in thread1 over')
def do_thread2():
print(' in thread2 begin')
mutex2.acquire()
print('do something2 ...')
if mutex1.acquire():
print('do something2_2...')
mutex1.release()
mutex2.release()
print(' in thread2 over')
if __name__ == '__main__':
print('---------main begin ----------')
mutex1 = threading.Lock()
mutex2 = threading.Lock()
t1 = threading.Thread(target=do_thread1)
t2 = threading.Thread(target=do_thread2)
t1.start()
t2.start()
t1.join()
t2.join()
print('---------main end ----------')
结果:
---------main begin ----------
in thread1 begin
do something1 ...
in thread2 begin
do something2 ...
(已经死锁)
只要是多线程的程序就极易可能发生死锁,因此设计者应该尽可能给每个线程只分配一把锁保证资源的唯一性。要想完全避免死锁几乎是不可能的,即便是这样我们还是可能在一些层面上尽可能避免死锁。如下,给加锁的语句添加超时时间,到了规定的时间如果还没不能得到这个锁
#coding=utf-8
import threading
import time
def do_thread1():
print(' in thread1 begin')
mutex1.acquire()
print('do something1 ...')
time.sleep(1)
if mutex2.acquire(timeout=0.5): #这个方法默认是阻塞的,给它添加一个默认的超时时间。
print('do something1_1 ...')
mutex2.release()
mutex1.release()
print(' in thread1 over')
def do_thread2():
print(' in thread2 begin')
mutex2.acquire()
print('do something2 ...')
if mutex1.acquire():
print('do something2_2...')
mutex1.release()
mutex2.release()
print(' in thread2 over')
if __name__ == '__main__':
print('---------main begin ----------')
mutex1 = threading.RLock()
mutex2 = threading.RLock()
t1 = threading.Thread(target=do_thread1)
t2 = threading.Thread(target=do_thread2)
t1.start()
t2.start()
t1.join()
t2.join()
print('---------main end ----------')
结果:
---------main begin ----------
in thread1 begin
do something1 ...
in thread2 begin
do something2 ...
in thread1 overdo something2_2...
in thread2 over
---------main end ----------
附一个很不错的链接: