一个进程包含:运行进程的程序、数据集以及进程控制块。其中进程控制块是保证系统可以进行多任务并发的关键,它控制着进程间的切换活动。
进程是系统中最小的资源分配单位,而线程是系统中最小的执行单位。多个线程可以共享同一个进程内的数据集,很多情况下,线程访问数据集的速度过快,而数据集的更新却跟不上被访问的速度,导致了访问出现错误。这就是线程安全问题。
假设有一百个线程同时来操纵一个数,每个线程运行一次减1的操作。代码如下:
1 import time 2 import threading 3 4 num = 100 5 6 def redNum(): 7 8 global num # 引用的是全局变量num 9 num = num - 1 # 对num进行-1操作 10 11 thread_list = [] # 存放多个线程的列表 12 13 for i in range(100): 14 t = threading.Thread(target=redNum) 15 t.start() 16 thread_list.append(t) 17 18 for t in thread_list: # 阻塞主线程,等待子线程执行完毕 19 t.join() 20 21 print('num的最终值:', num)
打印结果:
num的最终值: 0
我们获取了想要的结果,这个结果与串行执行程序的结果并无区别。但是当我们加上时间延迟后,情况就变得不一样了:
1 import time 2 import threading 3 4 num = 100 5 6 def redNum(): 7 8 global num 9 10 temp = num # 将num存入临时变量 11 time.sleep(0.000001) # 设置时间延迟 12 num = temp - 1 13 14 thread_list = [] 15 16 for i in range(100): 17 t = threading.Thread(target=redNum) 18 t.start() 19 thread_list.append(t) 20 21 for t in thread_list: 22 t.join() 23 24 print('num的最终值:', num)
打印结果并不为0:
num的最终值: 91
如果重新运行程序,就会发现每次打印出的数字都会有所不同。
为什么最终的结果不是0呢?
如上图所示,每个线程都在访问num这个变量。但是由于我们在线程内部设置了时间延迟,所以读取num的速度要远大于在线程内部处理num的速度,所以很多线程获取的依然是未作修改的num值。子线程访问num的速度快,修改后将num重新写入的速度慢,导致了所谓的线程不安全——当某个线程访问共享数据时,未对这次访问进行保护,其他线程仍有机会访问该数据,出现了数据前后不一致的情况。例如我们在网上买火车票,信息显示余票还有100张,这时A访问了网站并购买了一张票,在网站处理A的购买信息时,B也访问了网站。如果网站没有对A的访问进行保护,就会出现B看到余票仍有100张的情况。而实际上,余票只有99张了。
那么怎样才能使线程安全呢?这就要用到“锁”了。
我们可以为某一段代码加上“锁”,加上“锁”后,每次只允许一个线程运行这段代码,也就是每个时间只能有一个线程访问共享数据。在python中,这种限制线程访问的锁称为“同步锁”。可以用threading.Lock()来实例化一个同步锁对象。
1 import time 2 import threading 3 4 num = 100 5 6 lock = threading.Lock() # 实例化一个Lock对象,该对象是一个同步锁 7 8 def redNum(): 9 10 global num 11 12 lock.acquire() # 为下面的运行代码加上同步锁 13 temp = num 14 time.sleep(0.000001) 15 num = temp - 1 16 lock.release() # 释放同步锁 17 18 thread_list = [] 19 20 for i in range(100): 21 t = threading.Thread(target=redNum) 22 t.start() 23 thread_list.append(t) 24 25 for t in thread_list: 26 t.join() 27 28 print('num的最终值:', num)
这时,多线程的程序获得的结果就是我们期望的了:
num的最终值: 0
当在每个线程内部使用lock.acquire()与lock.release()时,这两个方法间的代码相当于串行执行。每个时刻都只能有1个线程运行这两个方法间的代码。当某个线程内的锁被释放后,其他线程才能去竞争这把“锁”,获得锁的线程才能被执行。
参考博客:www.cnblogs.com/yuanchenqi/articles/6248025.html