3-Python多线程


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() # 注意,调用的是start方法,而不是run方法!!
    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
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) # 保证t1先于t2执行
    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

互斥锁主要涉及到两个操作,acquirerelease,分别是加锁和解锁。

互斥锁的语法

import threading
# 创建互斥锁
lock = threading.Lock()
# 对需要访问的资源加锁
lock.acquire()
# 访问资源
pass
# 解锁
lock.release()

死锁和银行家算法

死锁(Deadlock):是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。称此时系统处于死锁状态或系统产生了死锁。总之,对不可剥夺的资源的不合理分配会导致死锁。

在这里插入图片描述

银行家算法基本思想: 允许进程动态地申请资源,系统在每次实施资源分配之前,先计算资源分配的安全性,若此次资源分配安全(即资源分配后,系统能按某种顺序来为每个进程分配其所需的资源,使每个进程都可以顺利地完成),便将资源分配给进程,否则不分配资源,让进程等待。

例如,银行家的总额 10万,客户1借9万,客户2借4万,客户三借8万

  1. 初始分配 2 2 4,剩余2万
  2. 再借给客户二 2万,凑够四万,找回四万
  3. 再借给客户三 4万,凑够八万,找回八万
  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资源的权限,而互斥锁仍然控制着修改数据的权限,这两者相互独立。

猜你喜欢

转载自blog.csdn.net/weixin_43721070/article/details/121757417