Python屏蔽GIL锁实践(调研+转载+整理)

环境:

Ubuntu19.10

四核八线程.

题外话,八线程属于超线程概念,程序员不可控制,属于操作系统调度的工作.

基本概念:

什么时候使用并发/并行?

资料中有两种说法:

一种是:

涉及到多核就是并行,单核IO异步就是并发

另一种是:

整个系统服务于IO异步任务就是并发,涉及到CPU密集就是并行

本文以第一种为准

--------------------------------

消除GIL锁为什么会导致单线程变慢?

参考[6]的回答

Python多线程相当於单核多线程[7]

--------------------------------

网上资料的多线程包含:

单核多线程和多核多线程.

单核多线程就IO异步.

多核多线程大多数情况下指的是多核情况.

根据[4]

I'd welcome a set of patches into Py3k only if the performance for a single-threaded program (and for a multi-threaded but I/O-bound program) does not decrease”(这个意思是如果移除GIL锁,那么单线程性能会下降,也就是I/O异步性能会下降)

 

Each Python process gets its own Python interpreter and memory space so the GIL won't be a problem. (每个进程都有一个GIL锁)

网上说的"Python的多线程是鸡肋"的意思指的是多核多线程

有了多线程执行异步为啥还要多协程执行IO异步?

根据[5]:

好处是协程的开销成本更低.

 

GIL锁影响的是IO密集还是CPU密集任务?

The GIL does not have much impact on the performance of I/O-bound multi-threaded programs as the lock is shared between threads while they are waiting for I/O.[4](影响的是CPU密集任务而不是IO密集任务)

 

GIL锁在你调用开源库的时候,依然存在吗?

取决于底层是不是C写的,如果是C写的,屏蔽了GIL锁,那就不存在,否则就存在

----------------------------------------------------------------------------------------------------------------------

网上关于屏蔽GIL锁的资料如下:

[1]屏蔽了GIL锁,没有完整代码

[3]讲了原理,但是没有实操

本篇博客重点解析[2].

GIL锁每个进程都有一个,锁的到底是什么?

锁的是多线程进行CPU密集计算时候的场景.案例如下[4]:

单线程:

# single_threaded.py
import time

COUNT = 50000000

def countdown(n):
    while n>0:
        n -= 1

start = time.time()
countdown(COUNT)
end = time.time()

print('Time taken in seconds -', end - start)

Time taken in seconds - 2.2484254837036133


多线程:

# multi_threaded.py
import time
from threading import Thread

COUNT = 50000000

def countdown(n):
    while n>0:
        n -= 1

t1 = Thread(target=countdown, args=(COUNT//2,))
t2 = Thread(target=countdown, args=(COUNT//2,))

start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()

print('Time taken in seconds -', end - start)

Time taken in seconds - 3.074549674987793

 

多进程:

from multiprocessing import Pool
import time

COUNT = 50000000
def countdown(n):
    while n>0:
        n -= 1

if __name__ == '__main__':
    pool = Pool(processes=2)
    start = time.time()
    r1 = pool.apply_async(countdown, [COUNT//2])
    r2 = pool.apply_async(countdown, [COUNT//2])
    pool.close()
    pool.join()
    end = time.time()
    print('Time taken in seconds -', end - start)

结论:

CPU密集任务速度上,

多核多进程>多核多线程>单线程

----------------------------------------------下面是屏蔽GIL锁实践------------------------------------------------------------------------------

回到前面一个话题,python的多线程真的是鸡肋吗?

并不是,请看下面实验,下面实验受到[2]的启发,但是因为[2]的代码不完整,所以我自己想出了一个demo

我们把count提升100倍,下面是三个代码文件:

pycall.c

#include <stdio.h>  
#include <stdlib.h>  
// #include <iostream.h>  
int foo(int a, int b)  
{  
	long long int count=5000000000;
	   while( count > 0 )
   {
       // cout << "a 的值:" << a << endl;
       count--;
       if(count==2500000000)
            printf("计数中%lld\n", count);
       if(count==0)
            printf("计数结束%lld\n", count);
   };
  printf("you input %d and %d\n", a, b);  
  return 1;  
}

因为我们等下要进行多线程测试,

所以我们在上面代码中故意加入一些if语句用来监控计数过程,如果多线程生效 那么就会打印两次,

如果GIL锁依然存在,那么整个运行过程会比单线程还要慢一倍左右

 

pycall-1thread.py

import ctypes  
import threading
import time

if __name__ == '__main__':
    start=time.time()
    my_lib=ctypes.cdll.LoadLibrary("./libpycall.so")  
    t1=threading.Thread(target=my_lib.foo,args=(1,1))
    t1.start()
    t1.join()
    end=time.time()
    print('耗时=',end-start)

 

pycall-2thread.py

import ctypes  
import threading
import time

if __name__ == '__main__':
    start=time.time()
    my_lib=ctypes.cdll.LoadLibrary("./libpycall.so")  
    t1=threading.Thread(target=my_lib.foo,args=(1,1))
    t2=threading.Thread(target=my_lib.foo,args=(2,2))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end=time.time()
    print('耗时=',end-start)

 

运行方法:

先生成.so文件

gcc -o libpycall.so -shared -fPIC pycall.c

然后:

命令 运行结果
python pycall-1thread.py 计数中2500000000
计数结束0
you input 1 and 1
耗时= 10.828569173812866
python pycall-2thread.py 计数中2500000000
计数中2500000000
计数结束0
you input 1 and 1
计数结束0
you input 2 and 2
耗时= 10.749252796173096

可以看到,双线程运行与单线程运行速度一致,真正发挥了多核的威力.

所以明白如何使用python的多线程吗?

在关键部位使用C/C++代码来撰写,屏蔽GIL锁,大大提升计算速度.

C/C++写起来如果觉得繁琐,可以参考opencv,很多可以直接拿来用喔


[1]Python解决GIL锁的办法
[2]python的GIL锁优化方案及性能对比
[3]GIL锁

[4]What is the Python Global Interpreter Lock (GIL)?

[5]线程和进程的区别是什么? - 无与童比的回答 - 知乎

[6]python中去掉gil会大幅降低单线程的执行速度,怎么理解?

[7]为什么有人说 Python 的多线程是鸡肋呢? - DarrenChan陈驰的回答 - 知乎

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章