python multithread ( FIle read I/O ) is not parallism?

69 Views Asked by At

In python document, python multithreading is worth for task like FILE I/O , Network I/O, numpy

So I tried below code

import threading
import os
import time
import multiprocessing
# create test file

def create_test_file():
    if os.path.exists("./test.txt"):return 1
    with open("./test.txt","w") as fd :
        for i in range(100000):
            text = str(i) * 3
            text += "\n"
            fd.write(text)


create_test_file()

def file_read():
    for _ in range(100):
        with open("./test.txt","r") as fd :
            while True :
                line = fd.readline()
                if not line :  break



if __name__ == "__main__":
    start_time = time.time()
    file_read()
    file_read()
    file_read()
    file_read()
    print("sequential task %.2f"%(time.time() - start_time))

    t1 = threading.Thread(target=file_read)
    t2 = threading.Thread(target=file_read)
    t3 = threading.Thread(target=file_read)
    t4 = threading.Thread(target=file_read)

    start_time = time.time()

    t1.start()
    t2.start()
    t3.start()
    t4.start()

    t1.join()
    t2.join()
    t3.join()
    t4.join()

    print("multi_thread task %.2f"%(time.time() - start_time))


    p1 = multiprocessing.Process(target=file_read)
    p2 = multiprocessing.Process(target=file_read)
    p3 = multiprocessing.Process(target=file_read)
    p4 = multiprocessing.Process(target=file_read)

    start_time = time.time()

    p1.start()
    p2.start()
    p3.start()
    p4.start()

    p1.join()
    p2.join()
    p3.join()
    p4.join()

    print("multi_process task %.2f" % (time.time() - start_time))

But result is

sequential task 7.25 multi_thread task 7.58 multi_process task 2.45

why multi thread is not efficient ?

I know python has GIL , But in python document ( https://wiki.python.org/moin/GlobalInterpreterLock )

In hindsight, the GIL is not ideal, since it prevents multithreaded CPython programs from taking full advantage of multiprocessor systems in certain situations. Luckily, many potentially blocking or long-running operations, such as I/O, image processing,

Why multi thread is slower than sequential task??? GIL still work File read I/O??

1

There are 1 best solutions below

1
Aaron On

TLDR; file.read does indeed release the gil, but it's hard to make a file read the limiting bottleneck in python code.

Any time you really want to know if a python function (or a library function) which is written in c/c++ releases the GIL; you look for the macro: Py_BEGIN_ALLOW_THREADS, and indeed in "fileutils.c" _Py_read does indeed release the gil. Modern OS's and ssd's (even hdd's) are very fast at reading data from a file, so if this is the only part of your program which can take advantage of parallelism, you really won't see much advantage of threads. This is why multiprocessing is used which allows you to get around the GIL.

If you were reading from a socket instead of a file where the data rate may be limited by your network hardware or internet service provider however, that read call may indeed be the limiting factor, and putting it in parallel may indeed help noticably. Ultimately many of these "waiting on IO" tasks are just as well served by using asyncio which basically allows you to queue a number of IO tasks, then respond to them as they complete. This is done in an explicitly single threaded manner, so there is no illusion of parallel processing, only that you can do parallel waiting on things which may take a while to respond.

numpy as you mentioned does in certain calculations also release the GIL, however it should be noted that the underlying BLAS library of numpy may already be using multi-threading, and it is often very difficult to improve on the performance by implementing your own multi-threading on top of that. This video I recently watched goes into some of the advanced topics needed to actually extract the maximum performance out of a general matrix multiplication (dot product) function. It basically concludes with "just use the existing library" because tons of incredibly smart people have already optimized it to an extreme degree. In this case that library was the "Intel Math Kernel Library" which is possibly the same library used by numpy depending on how it was compiled.