初识多线程
1 import threading 2 import time 3 def coding(): 4 for i in range(3): 5 print('输入代码%s' %i) 6 time.sleep(1) 7 def drawing(): 8 for i in range(3): 9 print('开始画画%s'%i) 10 time.sleep(1) 11 def main(): 12 t1 = threading.Thread(target=coding) 13 t2 = threading.Thread(target=drawing) 14 15 t1.start() 16 t2.start() 17 if __name__ == '__main__': 18 main() 19 20 Output: 21 22 D:\PyCharm\Anaconda3\python.exe D:/PyCharm/Py_work/test2.py 23 输入代码0 24 开始画画0 25 输入代码1 26 开始画画1 27 输入代码2 28 开始画画2 29 30 Process finished with exit code 0
还可以通过继承Thread类来创建多线程:
1 import threading 2 import time 3 class codingThread(threading.Thread): 4 def run(self): 5 for i in range(3): 6 print('输入代码%s' %threading.current_thread()) 7 time.sleep(1) 8 class drawingThread(threading.Thread): 9 def run(self): 10 for i in range(3): 11 print('开始画画%s'%threading.current_thread()) 12 time.sleep(1) 13 def main(): 14 t1 = codingThread() 15 t2 = drawingThread() 16 17 t1.start() 18 t2.start() 19 if __name__ == '__main__': 20 main() 21 22 Output 23 24 D:\PyCharm\Anaconda3\python.exe D:/PyCharm/Py_work/test2.py 25 输入代码<codingThread(Thread-1, started 8836)> 26 开始画画<drawingThread(Thread-2, started 9080)> 27 开始画画<drawingThread(Thread-2, started 9080)> 28 输入代码<codingThread(Thread-1, started 8836)> 29 输入代码<codingThread(Thread-1, started 8836)> 30 开始画画<drawingThread(Thread-2, started 9080)> 31 32 Process finished with exit code 0
多线程共享全局变量的问题:
多线程都是在同一个进程中运行的。因此在进程中的全局变量所有线程都是共享的。这就造成了一个问题,因为线程执行的顺序是无序的,有可能会造成数据错误。
1 ###当数据量小的时候,可能不会出错,如下: 2 import threading 3 value = 0 4 def add_value(): 5 global value 6 for i in range(1000): 7 value += 1 8 print(value) 9 def main(): 10 for x in range(2): 11 t = threading.Thread(target=add_value) 12 t.start() 13 14 if __name__ == '__main__': 15 main() 16 17 Output 18 19 1000 20 2000
第一个线程先加到1000,第二线程再加到2000,这里看起来没什么问题。但是当数据量大的时候,问题就出现了,如下:
1 import threading 2 value = 0 3 def add_value(): 4 global value 5 for i in range(1000000): 6 value += 1 7 print(value) 8 def main(): 9 for x in range(2): 10 t = threading.Thread(target=add_value) 11 t.start() 12 13 if __name__ == '__main__': 14 main() 15 16 Output 17 18 1222047 19 1246006
按理说应该先输出1000000,再输出2000000。但是结果却不是,这就是多线程执行顺序无序的后果。举个例子:当value=10的时候,线程1和线程2同时启动,本来value经过两个线程执行后应该是value=12。但是由于两个线程是同时执行,导致value +=1 被同时执行了两次,最后的结果就是value还是等于11.
这时候,就需要一个锁来消除这种不好的后果。就是如果线程1比线程2线对value先执行的时候,那么就先把value给锁起来,不让其他线程对value进行任何操作,等线程1操作完后再对value 进行解锁,这时候其他线程才可以对value进行操作。(这样看起来,好像和单线程没啥区别~~~~~)。
注意:这个锁一般只用于多个线程修改全局变量的代码段。比如下面代码的7行和8行。
1 import threading 2 value = 0 3 gLock = threading.Lock() 4 def add_value(): 5 global value 6 gLock.acquire() #上锁 7 for i in range(1000000): 8 value += 1 9 gLock.release() #解锁 10 print(value) 11 def main(): 12 for x in range(2): 13 t = threading.Thread(target=add_value) 14 t.start() 15 16 if __name__ == '__main__': 17 main() 18 19 Output 20 21 1000000 22 2000000
Lock版本生产者和消费者模式
生产者和消费者模式是多线程开发中经常见到的一种模式。生产者的线程专门用来生产一些数据,然后存放到一个中间变量中。消费者再从这个中间的变量中取出来数据进行消费。但是因为要使用中间变量,中间变量经常是一些全局变量,因此余姚使用锁来保证数据完整性。
1 import threading,random,time 2 3 gMoney = 1000 #此全局变量用来保存生产和消费的钱 4 gLock = threading.Lock() 5 gTotalTimes = 10 #此全局变量用来保存需要消费多少次 6 gtimes = 0 #此全局变量用来保存生产了多少次 7 8 class producer(threading.Thread): #生产者 9 def run(self): 10 global gMoney,gtimes 11 while True: 12 money = random.randint(100,1000) 13 gLock.acquire() #上锁 14 if gtimes >= gTotalTimes: #如果生产了10次,就退出循环 15 gLock.release() 16 break 17 gMoney += money 18 print('%s生产了%d元钱,剩余%d元钱' %(threading.current_thread(),money,gMoney)) 19 gtimes += 1 20 gLock.release() #解锁 21 time.sleep(0.5) 22 23 class consumer(threading.Thread): #消费者 24 def run(self): 25 global gMoney 26 while True: 27 money = random.randint(100,1000) 28 gLock.acquire() 29 if gMoney >= money: 30 gMoney -= money 31 print('%s消费了%d元钱,剩余%d元钱' % (threading.current_thread(), money, gMoney)) 32 else: 33 if gtimes >= gTotalTimes: 34 gLock.release() 35 break 36 print('%s消费者准备消费%d元钱,剩余%d元钱,余额不足' % (threading.current_thread(), money, gMoney)) 37 38 gLock.release() #此处的解锁如果放在if代码块里面,可能会造成死锁 39 time.sleep(0.5) 40 41 def main(): 42 for i in range(5): 43 t = consumer(name='消费者线程%d' %i) 44 t.start() 45 46 for i in range(5): 47 t = producer(name='生产者线程%d' %i) 48 t.start() 49 50 if __name__ == '__main__': 51 main()
Condition版的生产者和消费者模式:
Lock版本的生产者和消费者模式可以正常的运行。但是存在一个不足,在消费者中,总是通过while Ture死循环并且上锁的方式去判断钱够不够。上锁是一个很费CPU资源的行为,因此这种方式不是最好的。
还有一种更好的方式便是使用threading.Condition来实现。threading.Condition可以在没有数据的时候处于阻塞等待状态。一旦有合适的数据了,还可以使用notify相关的函数来同时其他处于等待状态的线程。这样就可以不用做一些无用的上锁和解锁操作,可以提高程序的性能。threading.Condition和threading.Lock部分功能类似,其相关函数介绍如下:
1、acquire:上锁
2、release:解锁
3、wait:将当前线程处于等待状态,并且会释放锁,可以被其他线程使用notify或者notify_all函数唤醒。被唤醒后会继续等待上锁,上锁后继续执行下面的代码。
4、notify:通知某个正在等待的线程。默认是第1个等待的线程。
5、notify_all:通知所有正在等待的线程。notify和notify_all不会释放锁,因此需要在release之前调用
1 import threading,random,time 2 3 gMoney = 1000 #此全局变量用来保存生产和消费的钱 4 gCondition = threading.Condition() 5 gTotalTimes = 10 #此全局变量用来保存需要消费多少次 6 gtimes = 0 #此全局变量用来保存生产了多少次 7 8 class producer(threading.Thread): #生产者 9 def run(self): 10 global gMoney,gtimes 11 while True: 12 money = random.randint(100,1000) 13 gCondition.acquire() #上锁 14 if gtimes >= gTotalTimes: #如果生产了10次,就退出循环 15 gCondition.release() 16 break 17 gMoney += money 18 print('%s生产了%d元钱,剩余%d元钱' %(threading.current_thread(),money,gMoney)) 19 gtimes += 1 20 gCondition.notify_all() 21 gCondition.release() #解锁 22 time.sleep(0.5) 23 24 class consumer(threading.Thread): #消费者 25 def run(self): 26 global gMoney 27 while True: 28 money = random.randint(100,1000) 29 gCondition.acquire() 30 while gMoney < money: 31 if gtimes >= gTotalTimes: 32 gCondition.release() 33 return 34 else: 35 print('%s准备消费%d元钱,剩余%d元钱,余额不足' % (threading.current_thread(), money, gMoney)) 36 gCondition.wait() 37 gMoney -= money 38 print('%s消费了%d元钱,剩余%d元钱' %(threading.current_thread(),money,gMoney)) 39 gCondition.release() #此处的解锁如果放在if代码块里面,可能会造成死锁 40 time.sleep(0.5) 41 42 def main(): 43 for i in range(5): 44 t = consumer(name='消费者线程%d' %i) 45 t.start() 46 47 for i in range(5): 48 t = producer(name='生产者线程%d' %i) 49 t.start() 50 51 if __name__ == '__main__': 52 main()