Python并发编程-线程
Python作为一种解释型语言,由于使用了全局解释锁(GIL)的原因,其代码不能同时在多核CPU上并发的运行。这也导致在Python中使用多线程编程并不能实现并发,我们得使用其他的方法在Python中实现并发编程。
一、全局解释锁(GIL)
Python中不能通过使用多线程实现并发编程主要是因为全局解释锁的机制,所以首先解释一下全局解释锁的概念。
首先,我们知道C++和Java是编译型语言,而Python则是一种解释型语言。对于Python程序来说,它是直接被输入到解释器中直接运行的。解释器在程序执行之前对其并不了解;它所知道的只是Python的规则,以及在执行过程中怎样去动态的应用这些规则。它也有一些优化,但是这基本上只是另一个级别的优化。由于解释器没法很好的对程序进行推导,Python的大部分优化其实是解释器自身的优化。更快的解释器自然意味着程序的运行也能“免费”的更快。也就是说,解释器优化后,Python程序不用做修改就可以享受优化后的好处。
为了利用多核系统,Python必须支持多线程运行。但作为解释型语言,Python的解释器需要做到既安全又高效。解释器要注意避免在不同的线程操作内部共享的数据,同时还要保证在管理用户线程时保证总是有最大化的计算资源。为了保证不同线程同时访问数据时的安全性,Python使用了全局解释器锁(GIL)的机制。从名字上我们很容易明白,它是一个加在解释器上的全局(从解释器的角度看)锁(从互斥或者类似角度看)。这种方式当然很安全,但它也意味着:对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行。即:只有获得了全局解释器锁的线程才能操作Python对象或者调用Python/C API函数。
所以,在Python中”不要使用多线程,请使用多进程”。具体来说,如果你的代码是IO密集型的,使用多线程或者多进程都是可以的,多进程比线程更易用,但是会消耗更多的内存;如果你的代码是CPU密集型的,多进程(multiprocessing模块)就明显是更好的选择——特别是所使用的机器是多核或多CPU的时候。
另外,Python的官方实现CPython带有GIL,但并不是所有的Python实现版本都是这样的。IronPython,Jython,还有使用.NET框架实现的Python就没有GIL。所以如果你不能忍受GIL,也可以尝试用一下其他实现版本的Python。
如果是一个计算型的任务,GIL就会让多线程变慢。我们举个计算斐波那契数列的例子:
import time
import threading def text(name):
def profile(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
end = time.time()
print('{} cost:{}'.format(name,end-start))
return res
return wrapper
return profile def fib(n):
if n <= 2:
return 1
return fib(n-1) + fib(n-2) @text('nothread')
def nothread():
fib(35)
fib(35) @text('hasthread')
def hasthread():
for i in range(2):
t = threading.Thread(target=fib,args=(35,))
t.start()
main_thread = threading.current_thread()
for t in threading.enumerate():
if t is main_thread:
continue
t.join() nothread()
hasthread() ##输出结果###
nothread cost:6.141353607177734
hasthread cost:6.15336275100708
这种情况还不如不用多线程!
GIL是必须的,这是Python设计的问题:Python解释器是非线程安全的。这意味着当从线程内尝试安全的访问Python对象的时候将有一个全局的强制锁。 在任何时候,仅仅一个单一的线程能够获取Python对象或者C API。每100个字节的Python指令解释器将重新获取锁,这(潜在的)阻塞了I/O操作。因为锁,CPU密集型的代码使用线程库时,不会获得性能的提高。
那是不是由于GIL的存在,多线程库就是个「鸡肋」呢?当然不是。事实上我们平时会接触非常多的和网络通信或者数据输入/输出相关的程序,比如网络爬虫、文本处理等等。这时候由于网络情况和I/O的性能的限制,Python解释器会等待读写数据的函数调用返回,这个时候就可以利用多线程库提高并发效率了。
2.同步机制
A. Semaphore(信号量)
在多线程编程中,为了防止不同的线程同时对一个公用的资源(比如全部变量)进行修改,需要进行同时访问的数量(通常是1)的限制。信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。
import time
from random import random
from threading import Thread,Semaphore,current_thread,enumerate sema = Semaphore(3) def foo(tid):
with sema:
print('{} acquire sema'.format(tid))
wt = random() * 2 time.sleep(wt)
print('{} release sema'.format(tid)) for i in range(5):
t = Thread(target=foo,args=(i,))
t.start() main_thread = current_thread()
for t in enumerate():
if t is main_thread:
continue
t.join() ####输出结果#####
0 acquire sema
1 acquire sema
2 acquire sema
0 release sema
3 acquire sema
1 release sema
4 acquire sema
2 release sema
3 release sema
4 release sema
B. Lock(互斥锁)
Lock也可以叫做互斥锁,其实相当于信号量为1。我们先看一个不加锁的例子:
import time
import threading value = 0 def getlock():
global value
new = value + 1
time.sleep(0.001) # 让线程有机会切换
value = new for i in range(100):
t = threading.Thread(target=getlock)
t.start() main_thread = threading.current_thread() for t in threading.enumerate():
if t == main_thread:
continue
t.join() print(value) ####输出结果#####
不确定(刷新值会发生改变)
现在,我们来看看加锁之后的情况:
import time
import threading value = 0
lock = threading.Lock() def getlock():
global value
with lock:
new = value + 1
time.sleep(0.001) # 让线程有机会切换
value = new for i in range(100):
t = threading.Thread(target=getlock)
t.start() main_thread = threading.current_thread() for t in threading.enumerate():
if t == main_thread:
continue
t.join() print(value) ####输出结果为#############
我们对value的自增加了锁,就可以保证了结果了。
3. RLock(递归锁)
先来说说死锁,所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
import threading
import time mutexA = threading.Lock()
mutexB = threading.Lock() class MyThread(threading.Thread): def __init__(self):
threading.Thread.__init__(self) def run(self):
self.fun1()
self.fun2() def fun1(self): mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
mutexB.release()
mutexA.release() def fun2(self): mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
time.sleep(0.2) mutexA.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutexA.release() mutexB.release() if __name__ == "__main__": print("start---------------------------%s"%time.time()) for i in range(0, 10):
my_thread = MyThread()
my_thread.start()
解决方案:
import threading
import time mutex = threading.RLock() class MyThread(threading.Thread): def __init__(self):
threading.Thread.__init__(self) def run(self):
self.fun1()
self.fun2() def fun1(self):
mutex.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放 print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time())) mutex.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
mutex.release()
mutex.release() def fun2(self):
mutex.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
time.sleep(0.2) mutex.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutex.release() mutex.release() if __name__ == "__main__": print("start---------------------------%s"%time.time()) for i in range(0, 10):
my_thread = MyThread()
my_thread.start()
递归锁内部维护了一个计数器,当有线程拿到了Lock以后,这个计数器会自动加1,只要这计数器的值大于0,那么其他线程就不能抢到改锁,这就保证了,在同一时刻,仅有一个线程使用该锁,从而避免了死锁的方法。关于递归锁内部实现,有兴趣的可以看看源码。
4. Condition(条件)
一个线程等待特定条件,而另一个线程发出特定条件满足的信号。最好说明的例子就是「生产者/消费者」模型:
import time
import threading def consumer(cond):
t = threading.current_thread()
with cond:
cond.wait() # 创建了一个锁,等待producer解锁
print('{}: Resource is available to consumer'.format(t.name)) def producer(cond):
t = threading.current_thread()
with cond:
print('{}:Making resource available'.format(t.name))
cond.notifyAll() # 释放锁,唤醒消费者 condition = threading.Condition() c1 = threading.Thread(name='c1',target=consumer,args=(condition,))
p = threading.Thread(name='p',target=producer,args=(condition,))
c2 = threading.Thread(name='c2',target=consumer,args=(condition,)) c1.start()
time.sleep(1)
c2.start()
time.sleep(1)
p.start()
5. Event
一个线程发送/传递事件,另外的线程等待事件的触发。我们同样的用「生产者/消费者」模型的例子:
import time
import threading
from random import randint TIMEOUT = 2 def consumer(event, l):
t = threading.currentThread()
while 1:
event_is_set = event.wait(TIMEOUT)
if event_is_set:
try:
integer = l.pop()
print('{} popped from list by {}'.format(integer,t.name))
event.clear() # 重置状态
except IndexError:
pass def producer(event, l):
t = threading.currentThread()
while 1:
integer = randint(10,100)
l.append(integer)
print('{} append to list by {}'.format(integer, t.name))
event.set()
time.sleep(1) event = threading.Event() l = []
threads = [] p = threading.Thread(name='producer1', target=producer, args=(event, l))
p.start()
threads.append(p) for name in ('consumer1','consumer2'):
t = threading.Thread(target=consumer, name=name, args=(event, l))
t.start()
threads.append(t) for t in threads:
t.join()
print('ending')
可以看到事件被2个消费者比较平均的接收并处理了。如果使用了wait方法,线程就会等待我们设置事件,这也有助于保证任务的完成。
6. Queue
队列在并发开发中最常用的。我们借助「生产者/消费者」模式来理解:生产者把生产的「消息」放入队列,消费者从这个队列中对去对应的消息执行。
大家主要关心如下4个方法就好了:
put: 向队列中添加一个消息。
get: 从队列中删除并返回一个消息。
task_done: 当某一项任务完成时调用。
join: 阻塞直到所有的项目都被处理完。
import time
import threading
import random
import queue q = queue.Queue() def double(n):
return n*2 def producer():
while 1:
wt = random.randint(1,10)
time.sleep(random.random())
q.put((double, wt)) def consumer():
while 1:
task, arg = q.get()
print(arg, task(arg)) q.task_done() for target in (producer, consumer):
t = threading.Thread(target=target)
t.start()
Queue模块还自带了PriorityQueue(带有优先级)和LifoQueue(先进先出)2种特殊队列。我们这里展示下线程安全的优先级队列的用法,
PriorityQueue要求我们put的数据的格式是(priority_number, data)
,我们看看下面的例子:
import time
import threading
from random import randint
import queue q = queue.PriorityQueue()
def double(n):
return n * 2 def producer():
count = 0
while 1:
if count > 5:
break
prit = randint(0,100)
print("put :{}".format(prit))
q.put((prit, double, prit)) # (优先级,函数,参数)
count += 1 def consumer():
while 1:
if q.empty():
break
pri,task,arg = q.get()
print('[PRI:{}] {} * 2 = {}'.format(pri,arg,task(arg)))
q.task_done()
time.sleep(0.1) t = threading.Thread(target=producer)
t.start()
time.sleep(1)
t = threading.Thread(target=consumer)
t.start()
7.线程池
面向对象开发中,大家知道创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。无节制的创建和销毁线程是一种极大的浪费。那我们可不可以把执行完任务的线程不销毁而重复利用呢?仿佛就是把这些线程放进一个池子,一方面我们可以控制同时工作的线程数量,一方面也避免了创建和销毁产生的开销。
import time
import threading
from random import random
import queue def double(n):
return n * 2 class Worker(threading.Thread):
def __init__(self, queue):
super(Worker, self).__init__()
self._q = queue
self.daemon = True
self.start() def run(self):
while 1:
f, args, kwargs = self._q.get()
try:
print('USE:{}'.format(self.name))
print(f(*args, **kwargs))
except Exception as e:
print(e)
self._q.task_done() class ThreadPool(object):
def __init__(self, max_num=5):
self._q = queue.Queue(max_num)
for _ in range(max_num):
Worker(self._q) # create worker thread def add_task(self, f, *args, **kwargs):
self._q.put((f, args, kwargs)) def wait_compelete(self):
self._q.join() pool = ThreadPool()
for _ in range(8):
wt = random()
pool.add_task(double, wt)
time.sleep(wt) pool.wait_compelete()
Python并发编程-线程的更多相关文章
- Python并发编程-线程同步(线程安全)
Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...
- 15.python并发编程(线程--进程--协程)
一.进程:1.定义:进程最小的资源单位,本质就是一个程序在一个数据集上的一次动态执行(运行)的过程2.组成:进程一般由程序,数据集,进程控制三部分组成:(1)程序:用来描述进程要完成哪些功能以及如何完 ...
- python并发编程-线程和锁
什么是线程 进程:资源分配单位 线程:cpu执行单位(实体),每一个py文件中就是一个进程,一个进程中至少有一个线程 线程的两种创建方式: from multiprocessing import Pr ...
- python并发编程-线程池
from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(2) print(n) re ...
- Python并发编程-线程-一个简单的例子
from threading import Thread import time def func(n): #子线程完成的 time.sleep(1) print(n) #多线程示例 for i in ...
- Python并发编程-线程锁
互斥锁-Lock #多线程中虽然有GIL,但是还是有可能产生数据不安全,故还需加锁 from threading import Lock, Thread #互斥锁 import time def ea ...
- 《转载》Python并发编程之线程池/进程池--concurrent.futures模块
本文转载自Python并发编程之线程池/进程池--concurrent.futures模块 一.关于concurrent.futures模块 Python标准库为我们提供了threading和mult ...
- python并发编程之进程、线程、协程的调度原理(六)
进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...
- Python进阶(4)_进程与线程 (python并发编程之多进程)
一.python并发编程之多进程 1.1 multiprocessing模块介绍 由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大 ...
随机推荐
- 从0开始简单使用git进行项目开发【SourceTree+Coding.net】
一.什么是git? 含义:Git 是 Linux 发明者 Linus 开发的一款新时代的版本控制系统,相比于原来的svn系统更加简单和实用 作用: 熟悉编程的知道,我们在软件开发中源代码其实是最重要的 ...
- Android NDK开发调试
ndk-stack: https://developer.android.com/ndk/guides/ndk-stack?hl=zh-cn JNI开发: https://developer.andr ...
- C#default关键字(泛型代码中的默认关键字)
在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:T 是引用类型还是值类型.如果 T 为值类型,则它是数值还是结构.给定参数化类型 T 的一个变量 t,只有 ...
- 发送Json数据,WebApi查看时为Null的问题(已解决)
1. PostMan :发送请求的Body中选择form-data是不行的.,body中的内容也要选择raw json格式. 2.如果是代码中填写的对象,api中解析为null,说明字段的值未对 ...
- 基于spring security 实现前后端分离项目权限控制
前后端分离的项目,前端有菜单(menu),后端有API(backendApi),一个menu对应的页面有N个API接口来支持,本文介绍如何基于spring security实现前后端的同步权限控制. ...
- springboot创建错误
1.Unknown system variable 'query_cache_size' 解决:https://www.cnblogs.com/nicknailo/articles/9074804.h ...
- List接口相对于Collection接口的特有方法
[添加功能] 1 void add(int index,Object element); // 在指定位置添加一个元素. [获取功能] 1 Object get(int index); // 获取指定 ...
- 阿里巴巴Java开发规范手册
Java开发手册 版本号 制定团队 更新日期 备 注 1.0.0 阿里巴巴集团技术部 2016.12.7 首次向Java业界公开 一.编程规约 (一) 命名规约 1. [强制]所有编程相关命 ...
- There is no setter for property named 可能产生的原因!
There is no setter for property named 'operateIP ' in 'class com.chinaunicom.wsp.facade.entity.User ...
- 使用PHPStorm 配置自定义的Apache与PHP环境
使用PHPStorm 配置自定义的Apache与PHP环境之一 关于phpstorm配置php开发环境,大多数资料都是直接推荐安装wapmserver.而对于如何配置自定义的PHP环境和Apach ...