说起python并发编程,就不得不说著名的全局解释锁(GIL)了。有兴趣的同学可以我查找一下相关的资料了解一下GIL,在这里大家只要知道一点,因为GIL的存在, 对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行。
下面先看一个例子:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on Mon Jun 5 16:12:14 2017 @author: 80002419 """ import threading import time def cost(fun):##定义一个装饰器,用来计算函数运行的时间 def wrapper(*args,**kargs): before_tm = time.time() fun(*args,**kargs); after_tm = time.time() fun.__doc__ print("{0} cost:{1}".format(fun.__name__,after_tm-before_tm)) return wrapper def fibs1(n): if (n == 1): return 0 elif(n == 2): return 1 else: return fibs1(n-2)+fibs1(n-1) def fibs2(n): list = [] if (n == 1): list = [0] return list[-1] elif(n == 2): list = [0,1] return list[-1] else: list = [0,1] for i in range(2,n): list.append(list[i-1]+list[i-2]) return list[-1] @cost def nothread(): fibs1(35) fibs1(35) #@cost #def nothread1(): # print(fibs2(40)) # print(fibs2(40)) @cost #使用多线程的程序 def inthread(): threads = [] for i in range(2): t = threading.Thread(target = fibs1,args =(35,)) t.start() threads.append(t) for t in threads: t.join() @cost def inthread1(): for i in range(2): t = threading.Thread(target=fibs1, args=(35,)) t.start() main_thread = threading.currentThread() for t in threading.enumerate(): if t is main_thread: continue t.join() nothread() inthread()
打印结果:
nothread cost:5.41788887978 inthread cost:14.6728241444
上面的例子对比了一个cpu密集型程序分别使用多线程和不使用多线程的情况,我们可以得到结论: 对于cpu密集型任务,使用多线程反而会减慢程序的运行。
这是为什么呢?
因为GIL在任何时候,仅仅允许一个单一的线程能够获取Python对象或者C API。每100个字节的Python指令解释器将重新获取锁,这(潜在的)阻塞了I/O操作
同时 GIL是必须的,这是Python设计的问题:Python解释器是非线程安全的。这意味着当从线程内尝试安全的访问Python对象的时候将有一个全局的强制锁。当然python 多线程也有可以加快程序运行的时候:当我们开发和网络通信或者数据输入/输出相关的程序,比如网络爬虫、文本处理等等。这时候由于网络情况和I/O的性能的限制,Python解释器会等待读写数据的函数调用返回,这个时候就可以利用多线程库提高并发效率了。这类任务我们称为IO(密集型任务)
在多线程编程中,为了防止不同的线程同时对一个公用的资源(比如全部变量)进行修改,需要进行同时访问的数量(通常是1)。信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。
下面来看一个例子:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on Mon Jun 5 16:12:14 2017 @author: 80002419 """ from threading import Thread, Semaphore import time,threading sema = Semaphore(3) # 定义一个信号量为3的Semaphone对象, #定义一个测试函数作为,被访问的对象 def test(tid): with sema: print("信号量:{0}".format(tid)) time.sleep(0.5) print('release!') thds = [] for i in range(6): t = Thread(target = test,args=(i,)) t.start() thds.append(t) for t in thds: t.join()
信号量:2 信号量:1 信号量:0 release! 信号量:0 release! 信号量:0 release! 信号量:0 release! release! release!
结果分析 :
当信号量sema信号为0时,此时不再不允许进程对test 再进行访问了,release 之后才能再继续生成亲的进程对其访问, Semaphore类的作用就是限制公共资源的访问量
3 锁——lock 与 Rlock
先看一段代码:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on Mon Jun 5 16:12:14 2017 @author: 80002419 """ from threading import Thread, Lock import time,threading value = 0; def changeValue(): global value new = value + 1 time.sleep(0.001)#让其它进程可以切换进来 value = new thds = [] for _ in range(100): t = Thread(target = changeValue) t.start() thds.append(t) for t in thds: t.join() print(value)
18
结果分析: 多个线程同时访问 value 变量,经过cpu存储后,再写回内存,如果进程A,进程B,在访问value时 值为0 ,进程A经过函数处理后,value = 1 ,再写回内存,value =1 ,进程B 执行完成后,会刷新value 但是写回的value 还是1, 所以并不能保护执行了100次changeValue后,结果为100
那么我们怎么能保证执行的结果就是100呢:再看一段代码:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on Mon Jun 5 16:12:14 2017 @author: 80002419 """ from threading import Thread, Lock import time,threading value = 0 lock = Lock() def changeValue(): global value with lock: new = value + 1 time.sleep(0.001)#让其它进程可以切换进来 value = new thds = [] for _ in range(100): t = Thread(target = changeValue) t.start() thds.append(t) for t in thds: t.join() print(value)
100
Lock也可以叫做互斥锁,其实相当于信号量为1,上面例子lock 也就可以用 sema = Semaphore()代替
原文链接:https://blog.csdn.net/u014770372/article/details/73549404?utm_medium=distribute.pc_category.none-task-blog-hot-4.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-4.nonecase