目录
1. 多任务-线程基本介绍
- 多任务的理解
- 线程完成多任务
2. 锁的引入与应用
- 互斥锁与死锁的引入
- 线程同步 案例
课堂笔记
1. 多任务-线程基本介绍
1.1 多任务的理解
- 多任务,即我们所说的一心多用,如边开车时边听音乐…
- 程序中模拟多任务
import threading
def drive():
for i in range(3):
print('正在开车....')
def music():
for i in range(3):
print('正在听音乐....')
if __name__ == '__main__':
drive()
music()
1.2 线程完成多任务
- 多线程:程序中独立运行的片段称为线程,而软件或硬件上实现多个线程并发执行就称为多线程。
- 线程执行流程:主线程会等待子线程执行完毕后再执行,而子线程间会进行资源争夺,谁先抢到资源谁先执行。
- 使用Thread类时并不会创建线程,只有再start()方法调用时才会创建并启动线程。
import threading
def drive():
for i in range(3):
print('正在开车....')
def music():
for i in range(3):
print('正在听音乐....')
# 定义线程测试函数
def threadTest():
# 指定线程执行目标以及线程名,此时并不创建线程
t1 = threading.Thread(target=drive, name='thread1')
# 创建并执行线程
t1.start()
t2 = threading.Thread(target=music, name='thread2')
t2.start()
if __name__ == '__main__':
threadTest()
- 继承Thread类创建线程
内部必须重写run方法
class Test(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self):
for i in range(3):
print(i)
if __name__ == '__main__':
t = Test('test_name')
t.start()
# 获取当前线程信息
print(threading.enumerate())
# [<_MainThread(MainThread, started 12404)>, <Test(test_name, started 5540)>]
-
join()与setDaemon()区别
- jion():守护子线程,主线程A中调用子线程B,并调用B.join()方法,主线程会一直等待直到子线程结束然后返回调用的地方继续执行,join()方法可设置一个时间参数,用于控制最大等待时间,若超过该时间,线程会自动返回按正常方式执行
- setDaemon():守护线程,主线程A中调用子线程B,并在主线程中调用setDaemon()方法时,子线程会随着主线程的结束而结束,而不必等待子线程结束
class Test(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): for i in range(3): print(i) if __name__ == '__main__': t = Test('test_name') t.setDaemon(True) t.start() print('主线程结束') ''' 输出: 0 主线程结束 '''
-
线程间共享全局变量
:在一个函数中,对全局变量进行修改的时候,是否要加global要看是否对全局变量的指向进行了修改,如果修改了指向,那么必须使用global,仅仅是修改了指向的空间中的数据,此时不用必须使用global。
def drive():
# 修改全局变量指向
global x
x += 1
print(x)
print('正在开车....')
def music():
# 仅修改值
x = 5
print(x)
print('正在听音乐....')
def threadTest():
t1 = threading.Thread(target=drive, name='thread1')
t1.start()
print(x)
t2 = threading.Thread(target=music, name='thread2')
t2.start()
if __name__ == '__main__':
x = 10
threadTest()
'''
输出:
11
正在开车....
11
5
正在听音乐....
'''
2. 锁的引入与应用
2.1 互斥锁与死锁的引入
-
为什么会有锁
- 多线程情况下,会面对几个线程同时对数据的读取与写入的操作,这就可能造成数据来不及修改就被其他线程读取,这显然不是真实的数据,而是修改前的数据,从而会造成错误。
-
互斥锁:当多个线程同时共享某数据时,需要进行同步控制,即锁定该数据当前为线程所有,直到该线程释放锁定后,才能对其他线程共享。互斥锁保证每次只能被一个线程进行写入操作,从而保证了多数据情况下数据的准确性。
- 创建锁:mutex = threading.Lock() 或 threading.RLock()
- threading.Lock()只能创建独立的锁,即相同锁之间不能嵌套。
- threading.RLock()可创建多个锁,锁之间可以嵌套。
- 上锁:mutex.acquire()
- 解锁:mutex.release()
- 创建锁:mutex = threading.Lock() 或 threading.RLock()
-
死锁:线程间对资源互相等待的关系,即两个线程各自拥有部分上锁资源,而又同时在等待对方的资源,如此就呈现出死循环状态。
- 避免方法:设置锁定超时时间 mutex.acquire(timeout)
import time, threading # 创建互斥锁mutex_A、mutex_B mutex_A = threading.Lock() mutex_B = threading.Lock() class MyThread1(threading.Thread): def run(self) -> None: # 对mutex_A上锁 mutex_A.acquire() print('__mutex_A_start_') # 对mutex_A上锁后,等待0.1s 此时另外一个线程就会对mutex_B上锁 time.sleep(0.1) # 对mutex_B上锁 mutex_B.acquire() print('__mutex_A_B__') # 对mutex_B解锁 mutex_B.release() print('__mutex_A_end__') # 对mutex_A解锁 mutex_A.release() class MyThread2(threading.Thread): def run(self) -> None: # 对mutex_B上锁 mutex_B.acquire() print('__mutex_B_start_') # 对mutex_B上锁后,等待0.1s 此时另外一个线程就会对mutex_A上锁 time.sleep(0.1) # 对mutex_A上锁 mutex_A.acquire() print('__mutex_B_A__') # 对mutex_A解锁 mutex_A.release() print('__mutex_B_end__') # 对mutex_B解锁 mutex_B.release() if __name__ == '__main__': t1 = MyThread1() t2 = MyThread2() t1.start() t2.start()
2.2 线程同步 案例
-
目标:
B: A同学 A: 在 B: 现在几点了? A: 你猜猜现在几点了。
-
threading.Condition()实现
class StuA(threading.Thread): def __init__(self, cond): super().__init__(name='A') self.cond = cond def run(self) -> None: # 相当于上锁 with self.cond: self.cond.wait() # 等待其他线程 print('{}: 在'.format(self.name)) self.cond.notify() # 唤醒B self.cond.wait() # 等待B print('{}: 你猜猜现在几点了。'.format(self.name)) self.cond.notify() class StuB(threading.Thread): def __init__(self, cond): super().__init__(name='B') self.cond = cond def run(self) -> None: # 相当于上锁 with self.cond: print('{}: A同学'.format(self.name)) self.cond.notify() # 唤醒A self.cond.wait() # 等待A print('{}: 现在几点了?'.format(self.name)) self.cond.notify() if __name__ == '__main__': cond = threading.Condition() s1 = StuA(cond) s2 = StuB(cond) s1.start() s2.start()