一、线程间共享全局变量
多线程开发的时候共享全局变量会带来资源竞争效果,数据不安全。
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print(f"test1--->{g_num}")
def test2(num):
global g_num
for i in range(num):
g_num += 1
print(f"test2--->{g_num}")
def main():
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
if __name__ == '__main__':
main()
二、同步异步概念
同步的意思就是协同步调,按预定的先后次序执行。例如你先说完然后我再说。
大家不要将同步理解成一起动作,同步是指协同、协助、互相配合。
例如线程同步,可以理解为线程A和B一块配合工作,A执行到一定程度时要依靠B的某个结果,于是停下来示意B执行,B执行完将结果给A,然后A继续执行。
A强依赖B(对方),A必须等到B的回复,才能做出下一步响应。即A的操作(行程)是顺序执行的,中间少了哪一步都不可以,或者说中间哪一步出错都不可以。
举个例子:
你去外地上学(人生地不熟),突然生活费不够了;此时你决定打电话回家,通知家里转生活费过来,可是当你拨出电话时,对方一直处于待接听状态(即:打不通,联系不上),为了拿到生活费,你就不停的oncall、等待,最终可能不能及时要到生活费,导致你今天要做的事都没有完成,而白白花掉了时间。
异步:
异步则相反,A并不强依赖B,A对B响应的时间也不敏感,无论B返回还是不返回,A都能继续运行;B响应并返回了,A就继续做之前的事情,B没有响应,A就做其他的事情。也就是说A不存在等待对方的概念。
举个例子:
在你打完电话发现没人接听时,猜想:对方可能在忙,暂时无法接听电话,所以你发了一条短信(或者语音留言,亦或是其他的方式)通知对方后便忙其他要紧的事了;这时你就不需要持续不断的拨打电话,还可以做其他事情;待一定时间后,对方看到你的留言便回复响应你,当然对方可能转钱也可能不转钱。但是整个一天下来,你还做了很多事情。 或者说你找室友临时借了一笔钱,又开始happy的上学时光了。
对于多线程共享全局变量计算错误的问题,我们可以使用线程同步来进行解决。
三、互斥锁
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进入写入操作。从而保证了多线程下数据的安全性。
1.练习一使用互斥锁解决200万次的计算问题。
import threading
import time
g_num = 0
lock = threading.Lock() # 创建一个锁,默认不上锁
def test1(num):
global g_num
lock.acquire()
for i in range(num):
g_num += 1
lock.release()
print(f"test1--->{g_num}")
def test2(num):
global g_num
lock.acquire()
for i in range(num):
g_num += 1
lock.release()
print(f"test2--->{g_num}")
def main():
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
if __name__ == '__main__':
main()
四、线程间使用非全局变量
from threading import Thread
import threading
import time
def test1():
name = threading.current_thread().getName() # 获取当前线程的名字
print(f"----thread name is {name}")
g_num = 100
if name == "Thread-1":
g_num += 1
# else:
# time.sleep(2)
print(f"----thread is {name}, g_num is {g_num}")
def main():
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test1)
t2.start()
if __name__ == '__main__':
main()
非全局对于同一个函数来说.可以通过线程的名字来区分.
五、死锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源时,就会造成死锁。
尽管死锁很少发生,但一旦发生就会造成应用的停止响应。
产生死锁的代码:
import threading
import time
lockA = threading.Lock()
lockB = threading.Lock()
class Thread_1(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.foo()
self.bar()
def foo(self):
lockA.acquire()
print(f"{self.name}得到lockA {time.ctime()}")
lockB.acquire()
print(f"{self.name}得到lockB {time.ctime()}")
lockB.release()
print(f"{self.name}释放lockB {time.ctime()}")
lockA.release()
print(f"{self.name}释放lockA {time.ctime()}")
def bar(self):
lockB.acquire()
print(f"{self.name}得到lockB {time.ctime()}")
lockA.acquire()
print(f"{self.name}得到lockA {time.ctime()}")
lockA.release()
print(f"{self.name}释放lockA {time.ctime()}")
lockB.release()
print(f"{self.name}释放lockB {time.ctime()}")
if __name__ == '__main__':
for i in range(10):
t = Thread_1()
t.start()
六、解决死锁办法
1. 递归锁
递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
上面的例子如果使用RLock代替Lock,则不会发生死锁,
二者的区别是:
递归锁可以连续acquire多次,每acquire一次计数器加1,
只要计数不为0,就不能被其他线程抢到。只有计数为0时,才能被其他线程抢到acquire。释放一次计数器-1
而互斥锁只能加锁acquire一次,想要再加锁acquire,就需要release解之前的锁
import threading
import time
lockB = lockA = threading.RLock()
class Thread_1(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.foo()
self.bar()
def foo(self):
lockA.acquire()
print(f"{self.name}得到lockA {time.ctime()}")
lockB.acquire()
print(f"{self.name}得到lockB {time.ctime()}")
lockB.release()
print(f"{self.name}释放lockB {time.ctime()}")
lockA.release()
print(f"{self.name}释放lockA {time.ctime()}")
def bar(self):
lockB.acquire()
print(f"{self.name}得到lockB {time.ctime()}")
lockA.acquire()
print(f"{self.name}得到lockA {time.ctime()}")
lockA.release()
print(f"{self.name}释放lockA {time.ctime()}")
lockB.release()
print(f"{self.name}释放lockB {time.ctime()}")
if __name__ == '__main__':
for i in range(10):
t = Thread_1()
t.start()
2. Semaphore(信号量)
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
import threading
import time
seamphore = threading.Semaphore(5)
def foo():
seamphore.acquire()
time.sleep(2)
print("ok")
seamphore.release()
for i in range(10):
t = threading.Thread(target=foo)
t.start()
3. condition实现线程通信
threading.Condition() 可以理解为更加高级的锁,比 Lock 和 Rlock 的用法更高级,能处理一些复杂的线程同步问题。threading.Condition() 创建一把资源锁(默认是Rlock),提供 acquire() 和 release() 方法,用法和 Rlock 一致。此外 Condition 还提供 wait()、Notify() 和 NotifyAll() 方法。
wait():线程挂起,直到收到一个 Notify() 通知或者超时(可选参数),wait() 必须在线程得到 Rlock 后才能使用。
Notify() :在线程挂起的时候,发送一个通知,让 wait() 等待线程继续运行,Notify() 也必须在线程得到 Rlock 后才能使用。 Notify(n=1),最多唤醒 n 个线程。
NotifyAll() :在线程挂起的时候,发送通知,让所有 wait() 阻塞的线程都继续运行。
import threading,time
def TestA():
cond.acquire()
print('李白:看见一个敌人,请求支援')
cond.wait()
print('李白:好的')
cond.notify()
cond.release()
def TestB():
time.sleep(2)
cond.acquire()
print('亚瑟:等我...')
cond.notify()
cond.wait()
print('亚瑟:我到了,发起冲锋...')
if __name__=='__main__':
cond = threading.Condition()
testA = threading.Thread(target=TestA)
testB = threading.Thread(target=TestB)
testA.start()
testB.start()
testA.join()
testB.join()
4. Event实现线程通信
hreading.Event() 原理是在线程中立了一个 Flag ,默认值是 False ,当一个或多个线程遇到 event.wait() 方法时阻塞,直到 Flag 值 变为 True 。threading.Event() 通常用来实现线程之间的通信,使一个线程等待其他线程的通知 ,把 Event 传递到线程对象中。
event.wait() :阻塞线程,直到 Flag 值变为 True
event.set() :设置 Flag 值为 True
event.clear() :修改 Flag 值为 False
event.isSet() : 仅当 Flag 值为 True 时返回
下面这个例子,主线程启动子线程后 sleap 2秒,子线程因为 event.wait() 被阻塞。当主线程醒来后执行 event.set() ,子线程才继续运行,两者输出时间差 2s。
mport threading
import datetime,time
class thread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name='线程' + threadname)
self.threadname = int(threadname)
def run(self):
event.wait()
print('子线程运行时间:%s'%datetime.datetime.now())
if __name__ == '__main__':
event = threading.Event()
t1 = thread('0')
#启动子线程
t1.start()
print('主线程运行时间:%s'%datetime.datetime.now())
time.sleep(2)
# Flag设置成True
event.set()
t1.join()
-
threading.active_count():返回当前存活的线程对象的数量
-
threading.current_thread():返回当前线程对象
-
threading.enumerate():返回当前所有线程对象的列表
-
threading.get_ident():返回线程pid
-
threading.main_thread():返回主线程对象