Python マルチタスク実行: プロセス [① マルチプロセスの実行順序は順序付けされておらず、オペレーティング システムによってスケジュールされます]、スレッド [① マルチスレッドの実行順序は順序付けされておらず、CPU によってスケジュールされます]; ② メインスレッドはすべてのサブスレッドの実行が完了するまで待機し、終了します; ③ スレッド間で共有されるグローバル変数; ④ ミューテックスロック; ⑤ デッドロック]

1. マルチタスクの概要

1. 質問する

現在の知識を使用して 2 つの関数またはメソッドを同時に実行できますか?

いいえ、以前に作成したプログラムはすべてシングルタスクであるため、ある関数またはメソッドは別の関数またはメソッドが実行された後にのみ実行できます。この操作を実現するには、マルチタスクを使用する必要があります
マルチタスクの最大のメリットは、CPU リソースを最大限に活用し、プログラムの実行効率を向上させることです。

2. マルチタスクの概念

マルチタスクとは、同時に複数のタスクを実行することを指します。例: 今日のコンピュータにインストールされているオペレーティング システムは、複数のソフトウェアを同時に実行できるマルチタスク オペレーティング システムです。

マルチタスクレンダリング:

3. 複数のタスクを実行する方法

  • 同時
  • 平行

同時:

一定期間にわたってタスクを交互に実行します。

例えば:

シングルコア CPU がマルチタスクを処理できるように、オペレーティング システムは各ソフトウェアを交互に実行します。たとえば、ソフトウェア 1 は 0.01 秒間実行し、ソフトウェア 2 に切り替え、ソフトウェア 2 は 0.01 秒間実行し、その後ソフトウェア 3 に切り替えます。 0.01秒間実行...これを繰り返します。一見、各ソフトウェアが交互に実行されていますが、CPU の実行速度が速いため、あたかもこれらのソフトウェアが同時に実行されているように感じられます。タスクを同時に実行します。

平行:

マルチコア CPU がマルチタスクを処理できるように、オペレーティング システムは CPU の各コアが実行するソフトウェアを配置し、実際には複数のコアがソフトウェアを一緒に実行しますここで、マルチコア CPU は複数のタスクを並行して実行し、複数のソフトウェアが常に一緒に実行されることに注意してください

2. プロセス

1. プロセスの概要

Python プログラムでマルチタスクを実現したい場合は、プロセスを使用できます。プロセスはマルチタスクを実現する手段です。

2. プロセスの概念

実行中のプログラムまたはソフトウェアはプロセスであり、オペレーティング システムによるリソース割り当ての基本単位です。つまり、プロセスが開始されるたびに、オペレーティング システムはプロセスに特定の実行リソース (メモリ リソース) を割り当て、プロセスの実行。

たとえば、現実の会社はプロセスとして理解できます。会社はオフィス リソース (コンピューター、机、椅子など) を提供し、実際の作業は従業員によって行われ、従業員はスレッドとして理解できます。

知らせ:

プログラムの実行後は、少なくとも 1 つのプロセスが存在します。デフォルトでは、プロセスには 1 つのスレッドがあります。プロセス内に複数のスレッドを作成できます。スレッドはプロセスに接続されます。プロセスがなければ、スレッドは存在しません

3. プロセスの役割

単一プロセスのレンダリング:

マルチプロセスレンダリング:

例証します:

複数のプロセスで複数のタスクを実行できます。各プロセスは独立した会社のようなものです。各会社は独立して動作し、各プロセスは独立して実行され、独自のタスクを実行します。

3. マルチプロセスの利用

1 インポートプロセスパッケージ

#导入进程包
import multiprocessing

2. Process プロセスクラスの説明

プロセス([グループ [, ターゲット [, 名前 [, 引数 [, kwargs]]]]])

  • group: プロセス グループを指定します。現在は「なし」のみを使用できます。
  • target: 実行するターゲットタスクの名前
  • 名前: プロセス名
  • args: 引数をタプル形式で実行タスクに渡します。
  • kwargs: パラメータを辞書形式で実行タスクに渡します

Process によって作成されるインスタンス オブジェクトの一般的なメソッド:

  • start(): サブプロセスインスタンスを開始します(サブプロセスを作成します)
  • join(): 子プロセスの実行が終了するのを待ちます。
  • terminate(): タスクが完了したかどうかに関係なく、子プロセスを直ちに終了します。

プロセスによって作成されるインスタンス オブジェクトの共通プロパティ:

name: 現在のプロセスのエイリアス、デフォルトは Process-N、N は 1 から増加する整数です

3. マルチタスクのマルチプロセス完了のコード

import multiprocessing
import time


# 跳舞任务
def dance():
    for i in range(5):
        print("跳舞中...")
        time.sleep(0.2)


# 唱歌任务
def sing():
    for i in range(5):
        print("唱歌中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建跳舞的子进程
    # group: 表示进程组,目前只能使用None
    # target: 表示执行的目标任务名(函数名、方法名)
    # name: 进程名称, 默认是Process-1, .....
    dance_process = multiprocessing.Process(target=dance, name="myprocess1")
    sing_process = multiprocessing.Process(target=sing)

    # 启动子进程执行对应的任务
    dance_process.start()
    sing_process.start()

結果:

唱歌中...
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...
唱歌中...
跳舞中...

4. プロセス番号を取得する

1. プロセス番号取得の目的

プロセス番号を取得する目的は、メインプロセスと子プロセスの関係を確認することであり、子プロセスがメインプロセスによって作成されたことを知ることができます。

プロセス番号を取得するための 2 つの操作

  • 現在のプロセス番号を取得する
  • 現在の親プロセス番号を取得します

2. 現在のプロセス番号を取得する

os.getpid() は 現在のプロセス番号を取得することを意味します

サンプルコード:

import multiprocessing
import time
import os


# 跳舞任务
def dance():
    # 获取当前进程的编号
    print("dance:", os.getpid())
    # 获取当前进程
    print("dance:", multiprocessing.current_process())
    for i in range(5):
        print("跳舞中...")
        time.sleep(0.2)
        # 扩展:根据进程编号杀死指定进程
        os.kill(os.getpid(), 9)


# 唱歌任务
def sing():
    # 获取当前进程的编号
    print("sing:", os.getpid())
    # 获取当前进程
    print("sing:", multiprocessing.current_process())
    for i in range(5):
        print("唱歌中...")
        time.sleep(0.2)


if __name__ == '__main__':

    # 获取当前进程的编号
    print("main:", os.getpid())
    # 获取当前进程
    print("main:", multiprocessing.current_process())
    # 创建跳舞的子进程
    # group: 表示进程组,目前只能使用None
    # target: 表示执行的目标任务名(函数名、方法名)
    # name: 进程名称, 默认是Process-1, .....
    dance_process = multiprocessing.Process(target=dance, name="myprocess1")
    sing_process = multiprocessing.Process(target=sing)

    # 启动子进程执行对应的任务
    dance_process.start()
    sing_process.start()

結果:

main: 70763
main: <_MainProcess(MainProcess, started)>
dance: 70768
dance: <Process(myprocess1, started)>
跳舞中...
sing: 70769
sing: <Process(Process-2, started)>
唱歌中...
唱歌中...
唱歌中...
唱歌中...
唱歌中...

3. 現在の親プロセス番号を取得します。

os.getppid() は、 現在の親プロセス番号を取得することを意味します

サンプルコード:

import multiprocessing
import time
import os


# 跳舞任务
def dance():
    # 获取当前进程的编号
    print("dance:", os.getpid())
    # 获取当前进程
    print("dance:", multiprocessing.current_process())
    # 获取父进程的编号
    print("dance的父进程编号:", os.getppid())
    for i in range(5):
        print("跳舞中...")
        time.sleep(0.2)
        # 扩展:根据进程编号杀死指定进程
        os.kill(os.getpid(), 9)


# 唱歌任务
def sing():
    # 获取当前进程的编号
    print("sing:", os.getpid())
    # 获取当前进程
    print("sing:", multiprocessing.current_process())
    # 获取父进程的编号
    print("sing的父进程编号:", os.getppid())
    for i in range(5):
        print("唱歌中...")
        time.sleep(0.2)


if __name__ == '__main__':

    # 获取当前进程的编号
    print("main:", os.getpid())
    # 获取当前进程
    print("main:", multiprocessing.current_process())
    # 创建跳舞的子进程
    # group: 表示进程组,目前只能使用None
    # target: 表示执行的目标任务名(函数名、方法名)
    # name: 进程名称, 默认是Process-1, .....
    dance_process = multiprocessing.Process(target=dance, name="myprocess1")
    sing_process = multiprocessing.Process(target=sing)

    # 启动子进程执行对应的任务
    dance_process.start()
    sing_process.start()
main: 70860
main: <_MainProcess(MainProcess, started)>
dance: 70861
dance: <Process(myprocess1, started)>
dance的父进程编号: 70860
跳舞中...
sing: 70862
sing: <Process(Process-2, started)>
sing的父进程编号: 70860
唱歌中...
唱歌中...
唱歌中...
唱歌中...
唱歌中...

5. プロセスはパラメータを使用してタスクを実行します

1. パラメータを使用してタスクを実行するプロセスの概要

プロセスを使用して以前に実行したタスクにはパラメータがありません。プロセスを使用して実行したタスクにパラメータがある場合、関数にパラメータを渡すにはどうすればよいでしょうか?

Process クラスがタスクを実行し、タスクにパラメーターを渡すには 2 つの方法があります。

  • args は、タプルの形式で実行タスクにパラメータを渡すことを意味します。
  • kwargs は、辞書の形式で実行タスクにパラメータを渡すことを意味します

2. args パラメータの使用

サンプルコード:

import multiprocessing
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..")
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子进程
    # args: 以元组的方式给任务传入参数
    sub_process = multiprocessing.Process(target=task, args=(5,))
    sub_process.start()

結果:

任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成

3. kwargs パラメータの使用

サンプルコード:

import multiprocessing
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..")
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子进程

    # kwargs: 表示以字典方式传入参数
    sub_process = multiprocessing.Process(target=task, kwargs={"count": 3})
    sub_process.start()

結果:

任务执行中..
任务执行中..
任务执行中..
任务执行完成

6. 手続き上の注意点

1. 手続き上の注意点のご紹介

  1. グローバル変数はプロセス間で共有されません
  2. メインプロセスは、すべての子プロセスの実行が完了するまで待ってから終了します。

2. グローバル変数はプロセス間で共有されません

import multiprocessing
import time

# 定义全局变量
g_list = list()


# 添加数据的任务
def add_data():
    for i in range(5):
        g_list.append(i)
        print("add:", i)
        time.sleep(0.2)

    # 代码执行到此,说明数据添加完成
    print("add_data:", g_list)


def read_data():
    print("read_data", g_list)


if __name__ == '__main__':
    # 创建添加数据的子进程
    add_data_process = multiprocessing.Process(target=add_data)
    # 创建读取数据的子进程
    read_data_process = multiprocessing.Process(target=read_data)

    # 启动子进程执行对应的任务
    add_data_process.start()
    # 主进程等待添加数据的子进程执行完成以后程序再继续往下执行,读取数据
    add_data_process.join()
    read_data_process.start()

    print("main:", g_list)

    # 总结: 多进程之间不共享全局变量

結果:

add: 0
add: 1
add: 2
add: 3
add: 4
add_data: [0, 1, 2, 3, 4]
main: []
read_data []

プロセス間で共有されないグローバル変数の解釈レンダリング:

3. プロセス間で共有されないグローバル変数の概要

  • サブプロセスを作成すると、メイン プロセスのリソースがコピーされます。つまり、サブプロセスは双子のようにメイン プロセスのコピーになります。グローバル変数がプロセス間で共有されない理由は、グローバル変数が同一プロセス内の変数は操作されません、変数ですが、異なるプロセスのグローバル変数の名前は同じです。

4. メインプロセスは、すべての子プロセスの実行が完了するまで待ってから終了します。

ここでサブプロセスを作成すると、サブプロセスの実行が完了するまでに約 2 秒かかります。次に、メインプロセスを 0.5 秒間実行し、プログラムを終了して実行結果を確認します。サンプルコードは次のとおりです。以下に続きます:

import multiprocessing
import time


# 定义进程所需要执行的任务
def task():
    for i in range(10):
        print("任务执行中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子进程
    sub_process = multiprocessing.Process(target=task)
    sub_process.start()

    # 主进程延时0.5秒钟
    time.sleep(0.5)
    print("over")
    exit()

    # 总结: 主进程会等待所有的子进程执行完成以后程序再退出

結果:

任务执行中...
任务执行中...
任务执行中...
over
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...

例証します:

上記のコードの実行結果から、メインプロセスはすべての子プロセスの実行が完了するのを待ってから終了することがわかります 。

メインプロセスを 0.5 秒間実行し、子プロセスが破棄されて実行されなくなったらどうなるでしょうか?

  • メインプロセスにデーモンを設定することも、 メインプロセスが終了する前に 子プロセスを破棄させることもできます。

ガーディアンのメインプロセス:

  • メインプロセスを保護するということは、メインプロセスが終了し、子プロセスが破棄されて実行されなくなることを意味します。

子プロセスが破棄されます。

  • 子プロセスの実行が終了する

メインプロセスが正常に終了することを確認するサンプルコード:

import multiprocessing
import time


# 定义进程所需要执行的任务
def task():
    for i in range(10):
        print("任务执行中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子进程
    sub_process = multiprocessing.Process(target=task)
    # 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程
    # sub_process.daemon = True
    sub_process.start()

    time.sleep(0.5)
    print("over")
    # 让子进程销毁
    sub_process.terminate()
    exit()

    # 总结: 主进程会等待所有的子进程执行完成以后程序再退出
    # 如果想要主进程退出子进程销毁,可以设置守护主进程或者在主进程退出之前让子进程销毁

結果:

任务执行中...
任务执行中...
任务执行中...
over

5. メインプロセスは、すべての子プロセスの実行が完了するまで待ってから終了することを要約します。

  • 子プロセスが正常に実行できることを保証するために、メイン プロセスは、すべての子プロセスが実行されるまで待ってから破棄されます。ガーディアン メイン プロセスを設定する目的は、メイン プロセスが終了し、子プロセスが破棄されることです。そのため、メインプロセスは子プロセスの実行を待機しません。
  • デーモンのメインプロセスモードを設定します: 子プロセス object.daemon = True
  • 子プロセスを破棄する方法: 子プロセス object.terminate()

7. スレッド

1. スレッドの概要

Python では、プロセスの使用に加えて、スレッドを使用してマルチタスクを実現することもできます。スレッドはマルチタスクを実現するもう 1 つの方法です。

2. スレッドの概念

スレッドはプロセス内の実行コードのブランチです。各実行ブランチ (スレッド) が動作してコードを実行するには、CPU によってスケジュールされる必要があります。つまり、スレッドは CPU の基本単位です。各プロセスには少なくとも 1 つのスレッドがあり、このスレッドは通常メイン スレッドと呼ばれるものです。

3. スレッドの役割

マルチスレッドにより複数のタスクを完了できる

マルチスレッドレンダリング:

8. マルチスレッドの使用

1. スレッドモジュールをインポートする

#导入线程模块
import threading

2. スレッドパラメータの説明

Thread([グループ [, ターゲット [, 名前 [, 引数 [, kwargs]]]]])

  • group: スレッド グループ、現在は None のみ使用可能
  • target: 実行するターゲットタスクの名前
  • args: タプルの形式で実行タスクに引数を渡します。
  • kwargs: パラメータを辞書形式で実行タスクに渡します
  • name: スレッド名、通常は設定する必要はありません

3. スレッドを開始します

start メソッドを使用してスレッドを開始する

4. マルチタスクを完了するためのマルチスレッド コード

import threading
import time

# 唱歌任务
def sing():
    # 扩展: 获取当前线程
    # print("sing当前执行的线程为:", threading.current_thread())
    for i in range(3):
        print("正在唱歌...%d" % i)
        time.sleep(1)

# 跳舞任务
def dance():
    # 扩展: 获取当前线程
    # print("dance当前执行的线程为:", threading.current_thread())
    for i in range(3):
        print("正在跳舞...%d" % i)
        time.sleep(1)


if __name__ == '__main__':
    # 扩展: 获取当前线程
    # print("当前执行的线程为:", threading.current_thread())
    # 创建唱歌的线程
    # target: 线程执行的函数名
    sing_thread = threading.Thread(target=sing)

    # 创建跳舞的线程
    dance_thread = threading.Thread(target=dance)

    # 开启线程
    sing_thread.start()
    dance_thread.start()

結果:

正在唱歌...0
正在跳舞...0
正在唱歌...1
正在跳舞...1
正在唱歌...2
正在跳舞...2

9. スレッドはパラメータを使用してタスクを実行します

1. パラメータを使用したタスクのスレッド実行の概要

先ほどスレッドを使用して実行したタスクにはパラメータがありませんでしたが、スレッドを使用して実行したタスクにパラメータがあった場合、パラメータを関数に渡すにはどうすればよいでしょうか?

Thread クラスがタスクを実行し、タスクにパラメータを渡すには 2 つの方法があります。

  • args は、タプルの形式で実行タスクにパラメータを渡すことを意味します。
  • kwargs は、辞書の形式で実行タスクにパラメータを渡すことを意味します

2. args パラメータの使用

サンプルコード:

import threading
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..")
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子线程
    # args: 以元组的方式给任务传入参数
    sub_thread = threading.Thread(target=task, args=(5,))
    sub_thread.start()

結果:

任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成

3. kwargs パラメータの使用

サンプルコード:

import threading
import time


# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务执行中..")
        time.sleep(0.2)
    else:
        print("任务执行完成")


if __name__ == '__main__':
    # 创建子线程
    # kwargs: 表示以字典方式传入参数
    sub_thread = threading.Thread(target=task, kwargs={"count": 3})
    sub_thread.start()

結果:

任务执行中..
任务执行中..
任务执行中..
任务执行完成

10. スレッドに関する注意事項

1. スレッドの注意点のご紹介

  1. スレッド間の実行順序が乱れている
  2. メインスレッドは、すべての子スレッドの実行が完了するまで待ってから終了します。
  3. スレッド間でグローバル変数を共有する
  4. スレッド間でグローバル変数データを共有するとエラーが発生する

2. スレッド間の実行順序が乱れている

import threading
import time


def task():
    time.sleep(1)
    print("当前线程:", threading.current_thread().name)


if __name__ == '__main__':

   for _ in range(5):
       sub_thread = threading.Thread(target=task)
       sub_thread.start()

結果:

当前线程: Thread-1
当前线程: Thread-2
当前线程: Thread-4
当前线程: Thread-5
当前线程: Thread-3

例証します:

  • スレッド間の実行順序は CPU のスケジューリングによって決まります。CPU によってスケジュールされたスレッドが最初に実行されます。スケジューリングのないスレッドは実行できません。
  • プロセス間の実行も順不同です。これはオペレーティング システムのスケジューリングによって決まります。オペレーティング システムによってスケジュールされたプロセスが最初に実行されます。スケジューリングされていないプロセスは実行できません。

3. メインスレッドは、すべてのサブスレッドの実行が完了するまで待ってから終了します。

ここでサブスレッドを作成すると、このサブスレッドの実行が完了するまでに約 2.5 秒かかります。メインスレッドを 1 秒間実行し、プログラムを終了して実行結果を確認します。サンプルコードは次のとおりです。以下に続きます:

import threading
import time


# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
    for i in range(5):
        print("test:", i)
        time.sleep(0.5)


if __name__ == '__main__':
    sub_thread = threading.Thread(target=show_info)
    sub_thread.start()

    # 主线程延时1秒
    time.sleep(1)
    print("over")

結果:

test: 0
test: 1
over
test: 2
test: 3
test: 4

例証します:

上記のコードの実行結果から、メインスレッドはすべてのサブスレッドの実行が完了するまで待ってから終了することがわかります 。

メインスレッドを 1 秒間実行し、サブスレッドが破棄されて実行されなくなったらどうなるでしょうか?

  • デーモンのメインスレッドをセットアップできます

メインスレッドを保護します。

  • メイン スレッドを保護するということは、メイン スレッドが終了し、サブスレッドが破棄されて実行されなくなることを意味します。

デーモンのメインスレッドを設定するには 2 つの方法があります。

  1. threading.Thread(target=show_info、daemon=True)
  2. スレッド object.setDaemon(True)

デーモンのメインスレッドを設定するためのサンプルコード:

import threading
import time


# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():
    for i in range(5):
        print("test:", i)
        time.sleep(0.5)


if __name__ == '__main__':
    # 创建子线程守护主线程 
    # daemon=True 守护主线程
    # 守护主线程方式1
    sub_thread = threading.Thread(target=show_info, daemon=True)
    # 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码
    # 守护主线程方式2
    # sub_thread.setDaemon(True)
    sub_thread.start()

    # 主线程延时1秒
    time.sleep(1)
    print("over")

結果:

test: 0
test: 1
over

3. スレッド間でグローバル変数を共有する

必要:

  1. リスト型のグローバル変数を定義する
  2. グローバル変数にデータを追加するタスクとグローバル変数にデータを読み取るタスクをそれぞれ実行する 2 つのサブスレッドを作成します。
  3. グローバル変数データが​​スレッド間で共有されているかどうかを確認する
import threading
import time


# 定义全局变量
my_list = list()

# 写入数据任务
def write_data():
    for i in range(5):
        my_list.append(i)
        time.sleep(0.1)
    print("write_data:", my_list)


# 读取数据任务
def read_data():
    print("read_data:", my_list)


if __name__ == '__main__':
    # 创建写入数据的线程
    write_thread = threading.Thread(target=write_data)
    # 创建读取数据的线程
    read_thread = threading.Thread(target=read_data)

    write_thread.start()
    # 延时
    # time.sleep(1)
    # 主线程等待写入线程执行完成以后代码在继续往下执行
    write_thread.join()
    print("开始读取数据啦")
    read_thread.start()

結果:

write_data: [0, 1, 2, 3, 4]
开始读取数据啦
read_data: [0, 1, 2, 3, 4]

4. スレッド間でグローバル変数データを共有するとエラーが発生する

必要:

  1. 100 万回ループする 2 つの関数を定義し、ループするたびにグローバル変数に 1 を追加します。
  2. 対応する 2 つの関数を実行し、計算結果を表示する 2 つのサブスレッドを作成します。
import threading

# 定义全局变量
g_num = 0


# 循环一次给全局变量加1
def sum_num1():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)


# 循环一次给全局变量加1
def sum_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)

    # 启动线程
    first_thread.start()
    # 启动线程
    second_thread.start()

結果:

sum1: 1210949
sum2: 1496035

注意点:

複数のスレッドがグローバル変数のデータを同時に操作すると、エラーが発生しました。

エラー分析:

first_thread と Second_thread の両方のスレッドは、グローバル変数 g_num (デフォルトは 0) に 1 を追加する必要がありますが、同時にマルチスレッドが動作するため、次の状況が発生する可能性があります。

  1. g_num=0 の場合、first_thread は g_num=0 を取得します。このとき、システムは first_thread を「スリープ」状態にスケジュールし、2 番目のスレッドを「実行中」状態に変換し、t2 も g_num=0 を取得します。
  2. 次に、 Second_thread は取得した値に 1 を加えて g_num に代入し、 g_num=1 とします。
  3. 次に、システムは Second_thread を「スリープ」に、first_thread を「実行」にスケジュールします。スレッド t1 は、前に取得した 0 に 1 を加算し、それを g_num に代入します。
  4. この結果、first_thread と first_thread は両方とも g_num に 1 を加算しますが、結果は依然として g_num=1 になります。

グローバル変数データエラーの解決策:

スレッド同期: グローバル変数を同時に操作できるのは 1 つのスレッドだけであることを確認します。 同期: 調整されたペースで、あらかじめ決められた順序で実行されます。例: あなたが話し終えたら、現実のトランシーバーのように、もう一度話します。

スレッド同期方法:

  1. スレッド待機(参加)
  2. ミューテックスロック

待機中のスレッドのサンプルコード:

import threading

# 定义全局变量
g_num = 0


# 循环1000000次每次给全局变量加1
def sum_num1():
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)


# 循环1000000次每次给全局变量加1
def sum_num2():
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)

    # 启动线程
    first_thread.start()
    # 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程
    # 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行
    first_thread.join()
    # 启动线程
    second_thread.start()

結果:

sum1: 1000000
sum2: 2000000

5. まとめ

  • スレッド実行の実行順序が狂っている
  • デフォルトでは、メイン スレッドはすべてのサブスレッドの実行が完了するまで待機してから終了します。ガード メイン スレッドを設定する目的は、メイン スレッドが終了してサブスレッドを破棄することです。
  • スレッド間でグローバル変数を共有する利点は、グローバル変数のデータを共有できることです。
  • スレッド間でグローバル変数を共有するとデータ エラーが発生する可能性がありますが、スレッド同期を使用するとこの問題を解決できます。
    • スレッド待機(参加)

11. ミューテックスロック

1. ミューテックスロックの概念

ミューテックス ロック: 共有データをロックして、同時に 1 つのスレッドのみが動作できるようにします。

知らせ:

  • ミューテックス ロックは複数のスレッドによって同時に取得されます。ロックを取得したスレッドが最初に実行され、ロックを取得しなかったスレッドは待機する必要があります。ミューテックス ロックが使用されて解放された後、待機中の他のスレッドがロックを取得します。

ミューテックス ロックをよりよく理解するには、次の図を参照してください。

3. ミューテックスロックの使用

Lock 変数はスレッド モジュールで定義されており、この変数は本質的には関数であり、この関数を呼び出すことでミューテックス ロックを取得できます。

ミューテックス ロックの使用手順:

# 创建锁
mutex = threading.Lock()

# 上锁
mutex.acquire()

...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...

# 释放锁
mutex.release()

注意点:

  • 取得メソッドと解放メソッドの間のコードは、一度に 1 つのスレッドによってのみ操作できます。
  • 取得メソッドが呼び出されたときに他のスレッドがすでにミューテックスを使用していた場合、取得メソッドはこの時点でブロックされ、ミューテックスが解放されるまで再度ロックすることはできません。

4. ミューテックス ロックを使用して、同じグローバル変数にそれぞれ 100 万回追加する 2 つのスレッドの操作を完了します。

import threading


# 定义全局变量
g_num = 0

# 创建全局互斥锁
lock = threading.Lock()


# 循环一次给全局变量加1
def sum_num1():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)
    # 释放锁
    lock.release()


# 循环一次给全局变量加1
def sum_num2():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)
    # 启动线程
    first_thread.start()
    second_thread.start()

    # 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待
    # 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行

結果:

sum1: 1000000
sum2: 2000000

例証します:

実行結果を通じて、アドレスミューテックス ロックにより、複数のスレッドがデータ エラーなしで共有データにアクセスすることが保証されます。

5. まとめ

  • ミューテックス ロックの機能は、共有データを同時に操作できるスレッドが 1 つだけであることを保証し、共有データにエラーがないことを保証することです。
  • ミューテックス ロックを使用する利点は、重要なコードの特定の部分が最初から最後まで 1 つのスレッドによってのみ完全に実行されるようにすることです。
  • ミューテックス ロックの使用はコードの実行効率に影響し、マルチタスクはシングルタスクの実行に変更されます。
  • ミューテックスロックを使用しない場合、デッドロックが発生しやすくなります。

12. デッドロック

1. デッドロックの概念

デッドロック: 相手がロックを解除するのを待っている状態がデッドロックです。

デッドロックをより深く理解するために、実際のレンダリングを見てみましょう。

例証します:

現実社会では、男女がお互いに先に謝ってくれるのを待つという行動は行き詰まりのようなものです。

デッドロックの結果

  • これにより、アプリケーションが応答を停止し、他のタスクを処理できなくなります。

2. デッドロックの例

必要:

添字に従ってリスト内の値を取得し、同時に 1 つのスレッドのみが値を取得できるようにします。

import threading
import time

# 创建互斥锁
lock = threading.Lock()


# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def get_value(index):

    # 上锁
    lock.acquire()
    print(threading.current_thread())
    my_list = [3,6,8,1]
    # 判断下标释放越界
    if index >= len(my_list):
        print("下标越界:", index)
        return
    value = my_list[index]
    print(value)
    time.sleep(0.2)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 模拟大量线程去执行取值操作
    for i in range(30):
        sub_thread = threading.Thread(target=get_value, args=(i,))
        sub_thread.start()

3. デッドロックを回避する

  • 適切な場所でロックを解除します
import threading
import time

# 创建互斥锁
lock = threading.Lock()


# 根据下标去取值, 保证同一时刻只能有一个线程去取值
def get_value(index):

    # 上锁
    lock.acquire()
    print(threading.current_thread())
    my_list = [3,6,8,1]
    if index >= len(my_list):
        print("下标越界:", index)
        # 当下标越界需要释放锁,让后面的线程还可以取值
        lock.release()
        return
    value = my_list[index]
    print(value)
    time.sleep(0.2)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 模拟大量线程去执行取值操作
    for i in range(30):
        sub_thread = threading.Thread(target=get_value, args=(i,))
        sub_thread.start()

4. まとめ

  • ミューテックスロックを使用する場合は、デッドロックの問題に注意し、適切な場所でロックを解除する必要があります。
  • デッドロックが発生すると、アプリケーションは応答を停止し、実行を継続できなくなります。

13. プロセスとスレッドの比較

1. プロセスとスレッド間の 3 つの方向の比較

  1. 関係の比較
  2. 違いの比較
  3. メリットとデメリットの比較

2. 関係の比較

  1. スレッドはプロセスに接続されており、プロセスがなければスレッドは存在しません。
  2. プロセスはデフォルトで 1 つのスレッドを提供し、プロセスは複数のスレッドを作成できます。

2. 違いと比較

  1. グローバル変数はプロセス間で共有されません

  2. グローバル変数はスレッド間で共有されますが、リソースの競合に注意する必要があります。解決策: ミューテックス ロックまたはスレッド同期

  3. プロセス作成時のリソース オーバーヘッドは、スレッド作成時のリソース オーバーヘッドよりも大きくなります。

  4. プロセスはオペレーティング システムのリソース割り当ての基本単位であり、スレッドは CPU スケジューリングの基本単位です。

  5. スレッドは独立して実行できず、プロセスに依存する必要があります。

  6. マルチプロセス開発は、単一プロセスおよびマルチスレッド開発よりも安定しています

3. メリットとデメリットの比較

  • プロセスの長所と短所:
    • 利点: 複数のコアを使用できる
    • 欠点: リソースのオーバーヘッドが高い
  • スレッドの長所と短所:
    • 利点: リソースのオーバーヘッドが低い
    • デメリット:マルチコアが使えない

4. まとめ

  • プロセスとスレッドはどちらもマルチタスクを実現する方法です
  • マルチプロセスはマルチスレッドより多くのリソースを消費しますが、単一プロセスおよびマルチスレッド開発よりも安定しており、1 つのプロセスが失敗しても他のプロセスに影響を及ぼしません。
  • CPU の複数のコアを使用して複数のプロセスを実行でき、複数のスレッドがグローバル変数を共有できます。
  • スレッドは単独で実行できないため、プロセスに接続する必要があります。




https://www.cnblogs.com/qianqiannian/p/7010909.html

プロセススレッド(1) ~基礎知識、プロセスとは?スレッドとは何ですか? _Thread Process_PocketのInitのブログ-CSDNブログ

おすすめ

転載: blog.csdn.net/u013250861/article/details/132783032