真正的python 多线程!一个修饰符让你的多线程和C语言同样快

> Python 多线程由于GIL的存在,致使其速度比单线程还要慢。可是近期我发现了一个至关好用的库,这个库只须要增长一个修饰符就可使原生的python多线程实现真正意义上的并发。本文将和你们一块儿回顾下GIL对于多线程的影响,以及了解经过一个修饰符就能够实现和C++同样的多线程。html


## GIL的定义
GIL的全称是global interpreter lock,官方的定义以下:python

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)git

从官方的解释来看,这个全局锁是用来防止多线程同时执行底层计算代码的。之因此这么作,是由于底层库Cpython,在内存管理这块是线程不安全的。github

## GIL有好处吗
对GIL的第一印象是这东西限制了多线程并发,对python而言是个弊大于利的存在。可是从stackoverflow上的讨论来看,这个存在仍是至关有必要的。
- 增长了单线程的运行速度
- 能够更方便地整合一些线程不安全的C语言库到python里面去
首先单线程的运行速度更快了,由于有这个全局锁的存在,在执行单线程计算的时候不须要再额外增长锁,减小了没必要要的开支。第二个则是能够更好地整合用C语言所写的python库。如今其实挺多用C语言写好底层计算而后封装提供python接口的,好比数据处理领域的pandas库,人工智能领域的计算框架Tensorflow或者pytorch,他们的底层计算都是用C语言写的。因为这个全局锁的存在,咱们能够更方便(安全)地把这些C语言的计算库整合成一个python包,对外提供python接口。shell

## GIL对性能的影响大吗
对于须要作大量计算的任务而言,影响是至关大的。咱们先来看一段单线程代码:安全

```python
class A(object):
def run(self):
ans = 0
for i in range(100000000):
ans += i
a = A()
for _ in range(5):
a.run()
```
以上这段代码是跑5次计算,每次计算是从1累加到1千万,跑这段代码须要17.46s。
紧接着,咱们用python的多线程库来实现一个多线程计算:多线程

```python
import threading并发

class A(object):
def run(self):
ans = 0
for i in range(100000000):
ans += i
threads = []
for _ in range(5):
a = A()
th = threading.Thread(target=a.run)
th.start()
threads.append(th)
for th in threads:
th.join()
```
这里咱们启动了5个线程同时计算,而后咱们又测试下时间: **41.35**秒!!!这个时候GIL的问题就体现出来了,咱们经过多线程来实现并发,结果比单线程慢了2倍多。
### 一个神奇的修饰符
话很少说,咱们先来看下代码。如下这段代码和上面的多线程代码几乎同样。可是咱们要注意到,在类A的定义上面,咱们增长了一个修饰符*@parl.remote_class*。
```python
import threading
import parlapp

@parl.remote_class
class A(object):
def run(self):
ans = 0
for i in range(100000000):
ans += i
threads = []
parl.connect("localhost:6006")
for _ in range(5):
a = A()
th = threading.Thread(target=a.run)
th.start()
threads.append(th)
for th in threads:
th.join()
```
如今咱们来看下计算时间:**3.74秒**!!!相比于单线程的17.46s,这里只用了接近1/5的时间(由于咱们开了5个线程)。这里是我以为比较神奇的地方,并无作太多的改动,只是在个人单线程类上面增长了一个修饰符,而后用原生的python多线程继续跑代码就变得至关快了。框架

### 完整的使用说明:

1. 安装这个库:

```shell
pip install --upgrade git+https://github.com/PaddlePaddle/PARL.git
```
2. 在本地经过命令启动一个并发服务(只须要启动一次)
```shell
xparl start --port 6006
```
3. 写代码的时候经过修饰符修饰你要并发的类@parl.remote。
这里须要注意的是只有通过这个修饰符修饰的类才能够实现并发。
4. 在代码最开始的时候经过parl.connect('localhost:6006')来初始化这个包。

最后贴下这个库的使用文档:
https://parl.readthedocs.io/en/latest/parallel_training/setup.html
源码在这里:
https://github.com/PaddlePaddle/PARL/tree/develop/parl/remote

后续会继续研究源码,看下是怎么作到一个修饰符就能加速的。你们若是读过了源码能够一块儿讨论下:)

相关文章
相关标签/搜索