### 线程实现多任务 + 使用 threading 模块创建线程 `import threading` ```python import threading # thrading.Thread(target=任务) threading.Thread(self, group=None, target=任务, name=函数名, args=(参数), kwargs=None, *, daemon=None) ``` 1. 传递参数 (线程关键字 args=() 传递参数) `threading.Thread(target=函数名, args=())` 2. **join()** 方法 : 当前线程执行完后其他线程才会继续执行 3. **setDaemon()** 方法 : 将当前线程设置为守护线程来守护主线程 (需要在子线程开启的时候设置成守护线程,否则无效) `线程名.setDaemon(True)` + 当主线程结束后,守护线程也随之结束,无论是否执行完成 + eg: QQ 多个聊天窗口 4. 实例方法 + **getName()** : 获取当前线程名称 + **setName()** : 设置当前线程名称 + **is_alive()** : 判断的当前线程存活状态 (曾经使用 isAlive() 判断) 5. threading 模块提供的方法 + **threading.currentThread() <==> threading.current_thread()** : 返回当前线程变量 + **threading.enumerate()** : 返回一个 包含正在运行线程的 list 。(正在运行: 线程启动后结束前, 不包括启动前与结束后) + **threading.activeCount() <==> threading.active_count()** : 返回正在运行线程的数量, 与 len(threading.enumerate()) 结果相同 + 使用继承方式开启线程 (定义一个类 继承 threading.Thread 类,复写父类的 run() 方法) ```python import threading import time class MyThread(threading.Thread): def __init__(self, num): super(MyThread, self).__init__() self.num = num def run(self): for i in range(self.num): print(f"---run\t{i}---") if __name__ == "__main__": my_thread = MyThread(3) my_thread.run() ``` + 线程之间共享全局变量 + 共享全局变量导致的 "线程不安全" 问题 + 加锁 + 延迟加载 + 同步和异步 + 同步 : 协同步调,按预定的先后次序进行运行 ### 互斥锁 GIL + 互斥锁 : 多个线程几乎同时修改一个共享数据时,同步控制,保证多个线程安全的访问竞争资源(全局内容), + 在线程需要更改共享数据时,先将其锁定,仅有此线程可对数据进行操作,直到该线程将资源状态更改为非锁定状态 (释放资源),其他进程才可以再次锁定。 + 保证每次只有一个线程进行写入操作,保证多线程下数据的安全性 + threading 模块中的 Lock 类,方便的处理锁定 + `threading.Lock()` : 创建锁 + `acquire(self, blocking: bool = ..., timeout: float = ...) -> bool` : 锁定 + blocking : True 当前线程堵塞,直到获取到这个锁为止 (默认为 True),设定为 False 当前线程不会堵塞 ```python import threading import time def func1(): print('t1开始执行') if t1_lock.acquire(): time.sleep(1) print('t1 获取 t1 锁') if t2_lock.acquire(blocking=False): print('t1 获取 t2 锁') t2_lock.release() print('t1 释放 t2 锁') t1_lock.release() print('t1 释放 t1 锁') def func2(): print('t2开始执行') if t2_lock.acquire(): time.sleep(1) print('t2 获取 t2 锁') if t1_lock.acquire(blocking=False): print('t2 获取 t1 锁') t1_lock.release() print('t2 释放 t1 锁') t2_lock.release() print('t2 释放 t2 锁') if __name__ == "__main__": t1_lock = threading.Lock() t2_lock = threading.Lock() t1 = threading.Thread(target=func1) t2 = threading.Thread(target=func2) t1.start() t2.start() """ t1开始执行 t2开始执行 t2 获取 t2 锁t1 获取 t1 锁 t2 释放 t2 锁 t1 获取 t2 锁 t1 释放 t2 锁 t1 释放 t1 锁 """ ``` + 上锁解锁过程 + 当一个线程调用锁的 acquire() 方法获得锁时,锁就进入 locked 状态 + 每次只有一个线程可以获得锁,此时另一个线程试图获得这个锁,该线程就会变成 blocked 阻塞状态,直到有用锁的线程调用 release() 方法释放锁,锁进入 unlocked 状态 + 线程调度程序从处于同步阻塞状态的线程中选择一个获得锁,并使得该线程进入运行 (running) 状态 ```python import threading import time tickes = 1000 def fun1(window): global tickes # mutexFlag = mutex.acquire(True) # if mutexFlag: while tickes > 1: # 创建锁 if mutex.acquire(): tickes -= 1 time.sleep(0.01) print(f'{window}卖出一张票,剩余{tickes}') # 释放锁 mutex.release() if __name__ == "__main__": # 添加锁 mutex = threading.Lock() t1 = threading.Thread(target=fun1, args=('窗口1', )) t2 = threading.Thread(target=fun1, args=("窗口2", )) t1.start() t2.start() ``` + 锁的好处 + 确保了某段关键代码只能有一个线程从头到尾完整的运行 + 锁的缺点 + 阻止了多线程并发执行,包含锁的代码只能以单线程模式运行,效率大大下降 + 存在多个锁,不同线程持有不用锁,并试图获取其他线程的锁,可能死锁 ### 死锁 + 多个线程共享资源时,如果两个线程分别占有一部分资源,并且同时等待对方资源,会造成死锁。 + 锁之间相互嵌套,可能出现死锁 + 解决 + 添加休眠时间 + 使用 blocking ### 非共享数据 (全局变量在多线程中,容易被多个线程调用修改,容易发生数据错乱) + 非全局变量加锁 (私有变量与锁无关) + 局部变量属于各自线程的,非共享 ### 生产者和消费者模式 + 队列 (先进先出) `from queue import Queue` + python 的 **Queue 模块**提供了同步的、线程安全的队列类,都采用了锁原语 (要么不做,要么做完),能在多线程直接使用,可以使用队列实现线程同步 + FIFO : 先进先出 + **queue.Queue(self, maxsize=0)** ```python import queue # 先进后出 q = queue.Queue() q.put(1) q.put(2) q.put(3) i = 0 while i **使用 multiprocess多进程 代替 Thread** + multiprocess 库 : 使用多进程,每个进程都由独立的 GIL + 缺点是会增加程序实现时线程间数据通讯和同步的难度