Network programming as much as a thread --GIL Global Interpreter Lock

Network programming as much as a thread --GIL Global Interpreter Lock

First, the primer

定义:
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.)

结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

First need to clear is not Python's GIL characteristics, it is a concept at the time of implementation of the Python parser (CPython) introduced. It is a C ++ like language (grammar) standard, but may be a different compiler into executable code. Well-known compilers such as GCC, INTEL C ++, Visual C ++ and so on. Python is the same, the same piece of code may be executed by different execution environments Python CPython, PyPy, Psyco like. Like JPython which there is no GIL. However, because CPython is the default under most environmental Python execution environment. So CPython Python is in many people's concept, will assume the defects attributed to the GIL Python language. So here must first be clear: GIL is not a Python features, Python can not rely on the GIL.

Two, GIL introduction

GIL is essentially a mutex, mutex since it is the essence of all mutex are the same, all become serial will run concurrently, in order to control the sharing of data within the same time can only be modified by a task , thus ensuring data security.

To be sure of is this: to protect the safety of different data, you should add different locks.

To understand GIL, first determine one thing: each time the python program, it will have a separate process. For example python test.py, python aaa.py, python bbb.py will produce three different processes python

Verify python test.py only produce a process:

#test.py内容
import os,time
print(os.getpid())
time.sleep(1000)
#打开终端执行
python3 test.py
#在windows下查看
tasklist |findstr python
#在linux下下查看
ps aux |grep python

A python in the process, not only the interpreter level test.py thread the main thread or other threads Chengkai Qi from the main line, as well as the interpreter turned on garbage collection, etc. In short, all the threads are running in this process inside, no doubt.

1、所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。

2、所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

In summary:

If multiple threads of target = work, then the process is executed

Multiple threads first visit to the interpreter code that is executed to get the permissions, and then to the target code interpreter to execute code

The interpreter code is shared by all threads, so the garbage collector thread may also have access to the interpreter code execution away, which leads to a question: 100 for the same data, may execute thread 1 x = 100 at the same time, and garbage collection is performed by a clever way to recovery operations 100, this problem is not solved, that is, the lock processing, as shown in GIL, ensure python interpreter at the same time a task can execute code.

Three, GIL and Lock

Witty students might ask this question: Python has a GIL same time to ensure that only one thread to execute, why there is also need to lock?

First, we need to reach a consensus: The purpose of the lock is to protect the shared data, at the same time only one thread to modify the shared data

Then, we can conclude that: different data protection should be added to different locks.

Finally, it is clear the problem, GIL and Lock are two locks, protection of data is not the same, the former interpreter level (of course, is to protect the interpreter-level data, such as data garbage collection), which is the protection of data of the user's own application development, it is clear that GIL is not responsible for this matter, can only handle user-defined lock that Lock, as shown below:

analysis:

1、100个线程去抢GIL锁,即抢执行权限
2、肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3、极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
4、直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程

Demonstrates:

from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()
    print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全,不加锁则结果可能为99

Four, GIL and multithreading

With the presence of the GIL, the same process in the same time only one thread is executed.

Hearing this, some students immediately asked: The process can take advantage of multi-core, but the big overhead, and python multithreading small overhead, but could not take advantage of multi-core advantage, that is useless python, php is the Best of language ?

Do not worry, not finished yet.

To solve this problem, we need to agree on several points:

1、cpu到底是用来做计算的,还是用来做I/O的?
2、多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
3、每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处

Corresponds to a worker cpu, calculated at this time corresponds to the worker at work, I / O corresponding to the blocking process to provide the necessary raw materials for the worker, and the worker who process the raw materials, if not, the process is for the worker We need to stop, wait until the arrival of raw materials.

If the majority of tasks you should have factories dry raw material preparation process (I / O-intensive), then you have more workers, and not be very meaningful, not like a person, so that workers in the process such as materials to do otherwise live.

Conversely, if you plant raw materials are complete, of course, is that the more the worker, the more efficient

in conclusion:

1、对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
2、当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地

Suppose we have four tasks need to be addressed, treatment will definitely have to play a concurrent effect, the solution can be:

方案一:开启四个进程
方案二:一个进程下,开启四个线程

Single-core cases, the results:

1、如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
2、如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

in conclusion:

现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

Fifth, multi-threaded performance test

If multiple concurrent tasks are computationally intensive: high-efficiency multi-process

from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i
if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(4):
        p=Process(target=work) #耗时5s多
        p=Thread(target=work) #耗时18s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

If multiple concurrent tasks is I / O intensive: high-efficiency multi-threaded

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print('===>')
if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
        p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

application:

1、多线程用于IO密集型,如socket,爬虫,web
2、多进程用于计算密集型,如金融分析

Guess you like

Origin www.cnblogs.com/Kwan-C/p/11589756.html