pythonはどのように並列しますか


pythonはどのように並列しますか
マルチスレッドデータのセキュリティを保証するため、python言語の設計では、グローバル解釈ロックGIL(global interpretor lock)があり、各スレッドは実行開始時にロックを取得しなければならず、I/Oやsleepが掛けられたときにロックを解放し、同じ時刻に1つのスレッドだけが実行されることを保証し、複数のスレッドは異なるタイムスライス上で実行され、マルチタスクの目的を達成する.pythonに同時機能を持たせ、pythonがCPUの単一コアを十分に使用できるようにします.マルチスレッドはpythonネットワーク爬虫類で同時に使用されるのが一般的で、大量のダウンロードタスクを一度に開くことができ、ほとんどのタスクはI/Oの状態を待っており、単一スレッドよりも数倍速く、また、コパスを使用してもマルチスレッドの効果を達成することができます.多くの場合、効率を向上させ、マルチコアCPUの優位性を十分に利用し、同時に複数のタスクを実行し、マルチタスクを並列に行うには、どうすればいいのでしょうか.GILを迂回できない以上、解決策は次のとおりです.
  • マルチプロセスを使用し、複数のpythonインスタンスを開き、プロセスプールを使用する
  • C/C++でマルチスレッドコードを書き出し、cythonで呼び出す.あるいはC/C++をdllファイル(linuxではsoファイル)にコンパイルし、ctypesモジュールを介して直接コードを呼び出す.pythonのCインタフェースで拡張を書くか、Boost.を使います.Pythonなど.
  • インタプリタは、C言語版のPyhtonではなく、jpython(java)、python(python)、ironpython(C#)などを使用
  • note:python 2の単一スレッドは、固定バイトコードを実行するたびにGILロックをアクティブに解放し、python 3では固定時間毎にGILロックをアクティブに解放するように変更されます.
    pythonマルチスレッドdemo
    import time
    from threading import Thread
    def countdown(n):
        while n > 0:
            n -= 1
    
    count = 2e7
    start = time.time()
    n_threads = 10 #    
    threads = [Thread(target=countdown, args=(count//n_threads,)) for i in range(n_threads)] 
    for t in threads: t.start() #       
    for t in threads: t.join() #       
    print(time.time() - start)

    マルチコアCPU上で上記コードを実行すると,スレッド数が1であろうと10であろうと,実行時間はほぼ同じであり,マルチスレッドはマルチコアの利点を利用して効率を向上させることはできない.
    pythonマルチプロセスdemo
    import time
    import os
    from multiprocessing import Pool
    def countdown(n):
        while n > 0:
            n -= 1
    
    if __name__ == "__main__":
        count = 2e7
        start = time.time()
        # n_processes = os.cpu_count()
        n_processes = 8 #     
        pool = Pool(processes=n_processes) #     
        for i in range(n_processes): 
            pool.apply_async(countdown, (count//n_processes,)) #       
        pool.close() #             
        pool.join() #        
        print(time.time() - start)

    マルチコアCPUで実行すると、2つのプロセスは1つよりも明らかに速く、効率の差は明らかですが、マルチプロセスのシステムオーバーヘッドも大きくなります.note:windows上で実行する場合、マルチプロセスコードは必ずif __name__ == '__main__':の中で実行しなければならない.そうしないと、これらのグローバル式はサブプロセスでも実行され、クラッシュするまで多くのプロセスが生成され続け、linuxでfork呼び出しでマルチプロセスを実現するのとは全く異なる.
    cythonマルチスレッドdemo
    マルチプロセスシステムのオーバーヘッドが大きく、pythonという動的言語の実行効率が高くない.すなわち、マルチプロセスを使用するとパフォーマンスのボトルネックに直面する可能性がある.この場合、C/C++言語の優位性を利用して、キー部分を書き換え、pythonから呼び出し、C/C++を通じてGILの制約を受けないマルチスレッドを直接使用し、コード性能を高めることができる.テストのために、C言語で簡単なデッドサイクルを書いて、1つのコアしか利用できないことを発見して、CPUを最大化するために、またマルチスレッドを必要として、マルチスレッドを作成するにはオペレーティングシステムが提供するカーネルの方法を利用しなければならなくて、linuxとwindowsの実現の上で違いがあります.
    linuxマルチスレッドdemo
    // mthread_linux.c
    #include 
    #include 
    
    int thread_run(int thread_id)
    {
        int s = 0;
        printf("thread %i started...
    "
    , thread_id); for (size_t i = 0; i < 4000000000; i++) for (size_t j = 0; j < 5; j++) s += j; printf("%i: %d
    "
    , thread_id, s); int ret = thread_id; return ret; } int main() { const size_t num_threads = 8; // pthread_t threads[num_threads]; pthread_t threads[8]; for (size_t i = 0; i < num_threads; i++) { int t_id = i+1; pthread_create(threads + i, NULL, (void *)&thread_run, (void *)t_id); printf("thread %lu created
    "
    , i + 1); } for (size_t i = 0; i < num_threads; i++) { void *ret_val; int exit_code = pthread_join(threads[i], &ret_val); printf("thread %lu exited with %d and returned %d.
    "
    , i + 1, exit_code, (int)ret_val); } return 0; } // && // gcc mthreads_linux.c -lpthread && ./a.out

    gccでコンパイルすればいいのに、8スレッドで降りると、家庭用CPUが100%近くになります
    Windowsマルチスレッドdemo
    // mthread_windows.cpp
    #include 
    #include 
    
    DWORD WINAPI thread_run(LPVOID lp_thread_id)
    {
    	int thread_id = *(int*)lp_thread_id;
    	int s = 0;
    	printf("thread %i started...
    "
    , thread_id); for (size_t i = 0; i < 4000000000; i++) for (size_t j = 0; j < 5; j++) s += j; printf("%i: %d
    "
    , thread_id, s); int ret = thread_id; return ret; } int main() { const size_t num_threads = 8; // HANDLE threads[num_threads]; HANDLE threads[8]; for (size_t i = 0; i < num_threads; i++) { int t_id = i + 1; threads[i] = CreateThread(NULL, 0, thread_run, &t_id, 0, NULL); printf("thread %lu created
    "
    , i + 1); } for (size_t i = 0; i < num_threads; i++) { int exit_code = WaitForSingleObject(threads[i], INFINITE); printf("thread %lu exited with %d.
    "
    , i + 1, exit_code); } getchar(); return 0; }

    Visual Studioが持参したMSVCコンパイルで実行し、同様にCPUも100%近い
    cythonのシンプルな使い方
    cythonは言語であり、文法はC言語とpythonの混合体であり、単独では実行できない.コードファイルの接尾辞は.pyx.pyxファイルに翻訳する必要がある.cファイルをC言語コンパイラで生成し、接尾辞は.so(linux)または.pyd(windows)であり、python解釈器で直接import呼び出しが可能である.略語コードの文法によって、Cモードで実行するかPythonモードで実行するか、完全にCモードで実行すると極めて効率的であり、cythonの公式ドキュメントによると、cythonを使用する最も簡単な例は4ステップである:(1).cythonファイルの作成(hello.pyx)
    def say_hello_to(name):
        print("Hello %s!" % name)

    (2). コンパイル用hello.pyxのファイルを作成する(setup.py)
    from distutils.core import setup
    from Cython.Build import cythonize
    
    setup(
        name="hello module",
        ext_modules=cythonize("hello.pyx")
    )

    (3). 実行python setup.py build_ext --inplace、このステップ生成hello.cp36-win_amd64.pydファイル(windows,python 36,64 bit)は、この列を気にせずコンパイル要求パソコンにVisual Studio(Windows)またはgcc(linux)をインストール
    (4). pythonインタプリタを開いて実行from hello import say_hello_tocythonはC言語で書かれたコードを呼び出します
    上記windowsの下のマルチスレッドコードをどのように利用してcpuを十分に使用するかは、同様に4つのステップである:(1).cythonファイル(demo.pyx)を作成し、参照mthread_windows.cppの関数をpython関数_main内に包む
    cdef extern from "mthread_windows.cpp":
        int main()
    
    def _main():
        main()

    (2). コンパイル用demo.pyxのファイルを作成する(setup.py)
    from distutils.core import setup
    from Cython.Build import cythonize
    
    setup(
        name="demo module",
        ext_modules=cythonize("demo.pyx")
    )

    (3). 実行python setup.py build_ext --inplace、このステップ生成demo.cp36-win_amd64.pydファイル(windows,python 36,64 bit)
    (4). pythonインタプリタを開いて実行from demo import _main