1.线程:
能被操作系统调度(给cpu执行)的最小单位
同一个进程中的多个线程可以同时被CPU执行
数据共享、可以利用多核;数据不安全
开启关闭切换时间开销小
在CPython中的多线程
gc:垃圾回收机制
引用计数 + 分代回收
全局解释器锁的出现主要是为了完成gc的回收机制,对不同线程的引用计数的变化记录更加准确
GIL:global interpreter lock
导致了同一个进程中的多个线程只能有一个线程真正被cpu执行
节省的是io操作的时间,而不是cpu计算的时间;因为cpu计算速度非常快,我们没有办法把一条进程中的所有io操作都规避掉
JPython中: 垃圾回收机制不同;能利用多核
2.开启线程
import os from threading import Thread, current_thread, enumerate, active_count import time def func(i): print(f'start{i}', current_thread().ident) time.sleep(1) print(f'end{i}') if __name__ == '__main__': t_ls = [] for i in range(10): t = Thread(target=func, args=(i, )) t.start() print(t.ident, os.getpid()) t_ls.append(t) print(enumerate(), active_count()) for t in t_ls: t.join() print('所有的线程都执行完了') """ 线程是不能从外部terminate的 所有的子线程只能是自己执行完代码之后就关闭 current_thread 获取当前代码所在的线程的对象,通过.ident获取线程的id enumerate 列表,存储了所有活着的线程对象,包括主线程 active_count就是这个列表的长度 """
3.开启线程的另一种方法
ading import Thread class MyThread(Thread): def __init__(self, a, b): self.a = a self.b = b super().__init__() def run(self): print(self.ident) t = MyThread(1, 2) t.start() # 开启线程 才在线程中执行run方法 print(t.ident)
4.线程中的数据共享
n = 100 def func(): global n n -= 1 t_ls = [] for i in range(100): t = Thread(target=func) t.start() t_ls.append(t) for t in t_ls: t.join() print(n)
5.守护线程
import time from threading import Thread def son(): while True: print('in son') time.sleep(1) def son2(): for i in range(3): print('in son2 ****') time.sleep(1) # t1 = time.time() t = Thread(target=son) t.daemon = True t.start() # t2 = time.time() # print(t2-t1) Thread(target=son2).start() """ 主线程会等待子线程结束之后才结束;因为主线程线束进程就会结束 守护线程会随着主线程的结束而结束,即等待其他子线程都结束后才结束 其他子线程 --> 主线程结束 --> 主进程结束 --> 整个进程中的所有资源被回收 --> 守护线程也被回收 """
6.线程中数据不安全的现象
from threading import Thread import dis import time # n = 0 # def add(): # for i in range(200000): # global n # n += 1 # # # def sub(): # for i in range(200000): # global n # n -= 1 # # # t1 = Thread(target=add) # t1.start() # # t2 = Thread(target=sub) # t2.start() # # t1.join() # t2.join() # # print(n) # a = 0 # def func(): # global a # a += 1 # # # dis.dis(func) """ += -= /= while if 等都是数据不安全 + 和=赋值是分开的两个操作 append pop 数据安全 列表中的方法或字典中的方法去操作全局变量时,数据是安全的 一般单线程不会出现数据安全的问题 33 0 LOAD_GLOBAL 0 (a) 2 LOAD_CONST 1 (1) 4 INPLACE_ADD 如果在存储新的a值之前恰好GIL锁轮转了,就会造成数据不安全的问题 6 STORE_GLOBAL 0 (a) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE """ n = [] def add(): for i in range(500000): n.append(1) def sub(): for i in range(500000): if not n: time.sleep(0.0000001) n.pop() t_ls = [] for i in range(20): t1 = Thread(target=add) t1.start() t2 = Thread(target=sub) t2.start() t_ls.append(t1) t_ls.append(t2) for t in t_ls: t.join() print(n)
7.线程锁
from threading import Thread, Lock import time # n = 0 # def add(lock): # for i in range(200000): # global n # with lock: # n += 1 # # # def sub(lock): # for i in range(200000): # global n # with lock: # n -= 1 # # # lock = Lock() # # t1 = Thread(target=add, args=(lock, )) # t1.start() # # t2 = Thread(target=sub, args=(lock, )) # t2.start() # # t1.join() # t2.join() # # print(n) n = [] def add(): for i in range(500000): n.append(1) def sub(lock): for i in range(500000): with lock: if not n: # 判断列表是否为空 time.sleep(0.0000001) # 堵塞,强制cpu轮转 n.pop() lock = Lock() t_ls = [] for i in range(20): t1 = Thread(target=add) t1.start() t2 = Thread(target=sub, args=(lock, )) t2.start() t_ls.append(t1) t_ls.append(t2) for t in t_ls: t.join() print(n)
8.单例模式
from threading import Thread, Lock import time class A: __instance = None lock = Lock() def __new__(cls, *args, **kwargs): with cls.lock: if not cls.__instance: time.sleep(0.0000001) # 这里cpu轮转可能会造成单例模式失效,所以需要加锁 cls.__instance = super().__new__(cls) return cls.__instance def func(): a = A() print(a) lock = Lock() for i in range(10): Thread(target=func).start()
9.递归锁
from threading import Lock, RLock, Thread """ Lock:互斥锁 效率较高 RLock:递归锁 在同一个线程中可以连续acquire多次,但是必须以相同次数的release """ # lock = Lock() # with lock: # pass # # # rlock = RLock() # with rlock: # pass def func(i, rlock): rlock.acquire() rlock.acquire() print(i, ': start') rlock.release() print(i, ': end') rlock.release() rlock = RLock() for i in range(5): Thread(target=func, args=(i, rlock)).start()
10.死锁现象
""" 死锁现象是怎么产生的? 多把锁,不管是互斥锁还是递归锁 并且在多个线程中 交叉使用 如果是互斥锁,出现了死锁现象,最快速的解决方案把所有的互斥锁都改成同一把递归锁 但是程序的效率会降低 一般情况下,一把互斥锁就足够了 """ from threading import Lock, RLock, Thread import time noodle_lock = Lock() fork_lock = Lock() # noodle_lock = fork_lock = RLock() def eat(name): noodle_lock.acquire() print(f'{name}抢到面了') fork_lock.acquire() print(f'{name}抢到叉子了') print(f'{name}可以开始吃面了') time.sleep(0.0001) fork_lock.release() print(f'{name}放下了叉子') noodle_lock.release() print(f'{name}放下了面') def eat2(name): fork_lock.acquire() print(f'{name}抢到叉子了') noodle_lock.acquire() print(f'{name}抢到面了') print(f'{name}可以开始吃面了') noodle_lock.release() print(f'{name}放下了面') fork_lock.release() print(f'{name}放下了叉子') Thread(target=eat, args=('alex', )).start() Thread(target=eat2, args=('wusir', )).start() Thread(target=eat, args=('日杂', )).start() Thread(target=eat2, args=('大黑', )).start()
11.队列
import queue # 线程之间数据安全的容器队列 原理:加锁 + 链表 # q = queue.Queue(4) # fifo 先进先出的队列 # # for i in range(4): # q.put(i) # q.put(5) # 这里会堵塞,因为上面队列中只允许存储4个值 # q.put_nowait(5) # 几乎不用 # for i in range(4): # print(q.get()) # # print(q.get()) # 这里会堵塞,因为队列中的值已被取完 # try: # q.get_nowait() # except queue.Empty: # 不是内置的错误类型,而是queue模块中的错误 # print("队列为空") # # from queue import LifoQueue # last in first out 后进先出 栈 # # # lq = LifoQueue() # lq.put(1) # lq.put(2) # lq.put(3) # # for i in range(3): # print(lq.get()) from queue import PriorityQueue # 优先级队列,根据放入数据的ascii码来从小到大输出 priq = PriorityQueue() priq.put((2, 'alex')) priq.put((1, 'wusir')) priq.put((3, 'lgq')) for i in range(3): print(priq.get())