Python实现多线程的方式是
时间片轮转+优先级调度
多线程的实现方案
1. 调用threading.Thread()
示例代码:
import threading
import time
def dance():
for i in range(5):
print("dance")
time.sleep(1)
def sing():
for i in range(5):
print("sing")
time.sleep(1)
def main():
t1 = threading.Thread(target=dance)
t2 = threading.Thread(target=sing)
print("The number of threads equals %d"%len(threading.enumerate()))
t1.start()
t2.start()
print("The number of threads equals %d"%len(threading.enumerate()))
if __name__ == "__main__":
main()
输出:
The number of threads equals 1
dance
sing
The number of threads equals 3
dance
sing
dance
sing
dance
sing
dance
sing
可见,在start之后才正式创建了多线程,而且线程之间的执行顺序不确定。
2. 调用实例对象
创建对象,继承threading.Thread 类,重写run(self) 方法,通过在主函数中实例化对象,然后调用start 方法。
这适用于比较复杂的情况,能够使main函数尽量简洁。
import threading
import time
class Mythread1(threading.Thread):
def login(self):
for i in range(5):
print("login")
time.sleep(1)
def run(self):
self.login()
class Mythread2(threading.Thread):
def register(self):
for i in range(5):
print("register")
time.sleep(1)
def run(self):
self.register()
if __name__ == "__main__":
t1 = Mythread1()
t2 = Mythread2()
t1.start()
t2.start()
输出
login
register
login
register
login
register
login
register
login
register
一定要注意,启动实例线程对象的方法是start 而不是重写的run 方法!
多线程之间共享全局变量
python子函数中是否需要声明全局变量?
问题:python中的全局变量是否要在函数中使用global声明?
在一个函数中,对全局变量进行修改,是否要在子函数中对变量进行说明,要看 是否对全局变量的指向进行了修改,例如对全局变量指向了一个新的地方(test()), 必须要声明global;否则如果只是增加/减少变量,即没有改变全局变量的指向test2(),不需要声明。
示例:
num = 100
nums = [11,22]
def test():
global num
num+=100
def test2():
nums.append(33)
def test3():
global nums
nums+=[33,44]
print(num)
print(nums)
test()
test2()
print(num)
print(nums)
test3()
print(num)
print(nums)
上面的例子中,只有test2属于没有改变全局变量的指向,所以不用在test2中声明。
实验:python中多线程共享全局变量
有了上面的教训,我们的全局变量设置为列表,各线程的修改仅限于增添元素,示例代码如下
import threading
import time
g_num = 100
def test1(temp):
temp.append(33)
print("test1:%s"%str(temp))
def test2(temp):
print("test1:%s"%str(temp))
g_nums = [11,22]
def main():
t1 = threading.Thread(target=test1,args=(g_nums,))
t2 = threading.Thread(target=test2,args=(g_nums,))
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print("-----in Main thread, g_num = %s"%str(g_nums,))
if __name__ == "__main__":
main()
输出
test1:[11, 22, 33]
test2:[11, 22, 33]
-----in Main thread, g_num = [11, 22, 33]
可见,python多线程之间共享了nums这个全局变量,如果有线程修改了全局变量的指向,则需要在线程中使用global声明。
值得注意的是,threading.Thread(target=function,args=(参数1,参数2)) 中传入的参数必须打包成元组。
互斥锁和循环锁
在多个线程同时修改全局变量时,往往会导致意想不到的后果,解决方法就是给资源加锁,threading 提供了两类锁,互斥锁Lock 和循环锁RLock 。
互斥锁主要涉及到两个操作,acquire 和release ,分别是加锁和解锁。
互斥锁的语法
import threading
lock = threading.Lock()
lock.acquire()
pass
lock.release()
死锁和银行家算法
死锁(Deadlock):是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。称此时系统处于死锁状态或系统产生了死锁。总之,对不可剥夺的资源的不合理分配会导致死锁。
银行家算法基本思想: 允许进程动态地申请资源,系统在每次实施资源分配之前,先计算资源分配的安全性,若此次资源分配安全(即资源分配后,系统能按某种顺序来为每个进程分配其所需的资源,使每个进程都可以顺利地完成),便将资源分配给进程,否则不分配资源,让进程等待。
例如,银行家的总额 10万,客户1借9万,客户2借4万,客户三借8万
- 初始分配 2 2 4,剩余2万
- 再借给客户二 2万,凑够四万,找回四万
- 再借给客户三 4万,凑够八万,找回八万
- 再借给客户一 7万,凑够九万,找回九万
通过约定多个锁的顺序,防止死锁的发生。
【补充】python GIL锁
Python GIL锁的全称是Global interpreter lock,指有多个线程执行时,每个线程在执行的时候都需要先获取GIL,保证同一时刻只有一个线程在执行,这就是所谓的时间片轮转
互斥锁和GIL的区别 首先假设只有一个进程,这个进程中有两个线程 Thread1,Thread2, 要修改共享的数据date, 并且有互斥锁,执行以下步骤:
(1) 多线程运行,假设Thread1获得GIL可以使用cpu,这时Thread1获得 互斥锁lock,Thread1可以改date数据(但并没有开始修改数据);
(2) Thread1线程在修改date数据前发生了 i/o操作 或者 ticks计数满100 (注意就是没有运行到修改data数据),这个时候 Thread1 让出了Gil,Gil锁可以被竞争;
(3) Thread1 和 Thread2 开始竞争 Gil (注意:如果Thread1是因为 i/o 阻塞 让出的Gil Thread2必定拿到Gil,如果Thread1是因为ticks计数满100让出Gil 这个时候 Thread1 和 Thread2 公平竞争);
(4) 假设 Thread2正好获得了GIL, 运行代码去修改共享数据date,由于Thread1有互斥锁lock,所以Thread2无法更改共享数据date,这时Thread2让出Gil锁 , GIL锁再次发生竞争;
(5) 假设Thread1又抢到GIL,由于其有互斥锁Lock所以其可以继续修改共享数据data,当Thread1修改完数据释放互斥锁lock,Thread2在获得GIL与lock后才可对data进行修改;
可见,GIL锁仅给了线程调用CPU资源的权限,而互斥锁仍然控制着修改数据的权限,这两者相互独立。
|