1 import threading 2 3 # 多线程本质上是在一个 Python 程序里做的一个资源再分配,把几段代码的运行顺序进行先后调整达到 CPU 资源利用的最大化。 4 # 但是这么做的一个缺点就是资源竞争Resource Contention,意思就是有可能几段代码同时在读写一个参数的时候,把这个参数的数值搞混。 5 # 所以在多线程共享资源的情况下,需要在共享资源外部添加锁 Lock。 6 7 # 直接继承线程类,然后覆盖继承类函数的方法 8 class ThreadChild(threading.Thread): 9 # 初始化 init: 通常继承线程类会扩写父类的初始化,来传递参数等。 10 def __init__(self, times, name, ret_dic, ret_lock): 11 # 扩写父类的初始化,首先调用父类的初始化 12 threading.Thread.__init__(self) 13 self.times = times 14 self.name = name 15 self.ret_dic = ret_dic 16 self.ret_lock = ret_lock 17 return 18 # 运行 run: 这是一个必须要覆盖的函数。启动线程调用的 start() 函数就是运行这个函数,这里是需要运行的核心代码。 19 def run(self): 20 # 覆盖重写函数 run 21 for i in range(self.times): 22 print(self.name + ' run: ' + str(i)) 23 self.ret_lock.acquire()# 锁住, 锁住之后的代码将只能被一个线程执行下去,直到开锁 24 # 进入有可能竞争的共享资源,锁住 25 self.ret_dic[self.name] = self.name + " finished with " + str(self.times) + " times printed" 26 # 共享资源读写结束,开锁 27 self.ret_lock.release()# 开锁, 开锁之后,被锁住的资源和代码行又可以重新被其他线程读写 28 return 29 30 if __name__ == '__main__': 31 32 thread_pool = [] 33 ret_dic = {} 34 # 锁类 Lock: 在线程中需要读写一个共享资源的时候,通过锁类来锁住资源,防止另外的线程读写修改。 35 # 这里在主程序中创建了一个锁 ret_lock, 并且传入了两个线程中。 36 # 这个锁本身是可以在多个线程中共享的,因为锁本身不存在资源竞争的问题,否则就没有意义了。 37 ret_lock = threading.Lock() 38 th_1 = ThreadChild(times=3, name='th_1', ret_dic=ret_dic, ret_lock=ret_lock) 39 th_2 = ThreadChild(times=5, name='th_2', ret_dic=ret_dic, ret_lock=ret_lock) 40 thread_pool.append(th_1) 41 thread_pool.append(th_2) 42 43 # 非阻塞 start() 44 for th in thread_pool: 45 th.start() 46 47 # 阻塞 join() 48 for th in thread_pool: 49 th.join() 50 51 print(ret_dic) 52 53 # 锁类 Lock: 在线程中需要读写一个共享资源的时候,通过锁类来锁住资源,防止另外的线程读写修改。 54 # 这里在主程序中创建了一个锁 ret_lock, 并且传入了两个线程中。 55 # 这个锁本身是可以在多个线程中共享的,因为锁本身不存在资源竞争的问题,否则就没有意义了。 56 # 锁住 Acquire: Acquire() 是锁的一个函数,表示获得资源,也可以表示锁住资源。在这个函数之后的代码将只能被一个线程执行下去,直到开锁。 57 # 开锁 Release: Release() 是锁的一个函数,表示释放资源,也可以表示开锁。开锁之后,被锁住的资源和代码行又可以重新被其他线程读写,运行。 58 59 # 全局变量也是 Python 程序的共享资源,在多线程运算中,接触到全局变量的地方都需要加锁,或者也可以直接把锁变成全局变量: 60 # import threading 61 # glob_lock = threading.Lock() 62 # glob_a = 100 63 # def increase_by_a(num): # 不好的例子,虽然对全局变量加了锁,但是在函数内部运用了全局变量 64 # glob_lock.acquire() 65 # result = glob_a + num 66 # glob_lock.release() 67 # return result 68 # if __name__ == '__main__': 69 # print(increase_by_a(100)) # 返回 200 70 # 71 # glob_lock.acquire() 72 # glob_a += 1 73 # glob_lock.release() 74 # 75 # print(increase_by_a(100)) # 返回 201,glob_a的改变破坏了函数 increase_by_a 的一致性 76 # 这里的锁是在全局变量中的,并且锁住了一个全局变量。 77 # 但是我们会发现,两次调用 increase_by_a 函数的过程中, 78 # 返回数值因为一个全局变量而变化了,尽管在函数传入的参数是一致的,这就破坏了函数的一致性: 79 # 函数的一致性:每次在同一个函数传入相同的参数,返回相同的结果 80 # 因此,尽量在函数中避免调用全局变量,或全局锁,以防破坏函数的一致性。