Python 7のプロセススレッドとコルーチンの詳細な説明

零下一度
リリース: 2017-07-19 16:07:30
オリジナル
2453 人が閲覧しました

前書き: スレッドとプロセスの関係図

以下の図からわかるように、各アプリケーションの実行中に、同時実行が必要な場合、作業を完了するためにメインプロセスとメインスレッドが生成されます。メイン プロセスは一連のサブプロセスを生成し (その後、サブプロセスを通じて一連のサブスレッドを生成し)、さまざまな CPU 呼び出しを行って同時実行効果を実現します。ただし、一般に、各プロセスは互いに独立していることに注意してください。

GILグローバルインタープリタロックはJavaやC#には存在しないPython独自のものですが、その主な機能は何でしょうか?プログラムの実行の最小単位はスレッドであることは誰もが知っていますが、CPU1 がプロセスを通じてスレッドを呼び出す場合 (CPU が呼び出す場合のみ)、スレッドはポーリング方式でのみ呼び出すことができます。つまり、各 CPU は、一度に 1 つのプロセスの 1 つのスレッドを通じてのみ特定の作業を完了できます。

一般的なプログラムでは、プロセスやスレッドを特に作成しない場合、プロセスやスレッドを作成すると、プログラムはステップごとに順番に実行されます。

プロセス

利点: 複数の CPU を同時に使用して複数の操作を実行できます

欠点: メモリ空間を再度開くと非常にリソースを消費します

プロセス数: 通常、CPU の数と同じです

使用場所:一般的にコンピューティング集中

スレッド

長所:共有メモリ(1つのプロセス内)、i/o操作を同時に実行可能

短所:リソースの占有、コンテキストの切り替えに非常に時間がかかる

数:一般に依存状況に応じて

使用場所: i/o 集中型

1. プロセス

プロセスの定義

プロセスとは、特定のデータセット上でコンピュータ内でプログラムを実行するアクティビティであり、システム単位でのリソースの割り当てとスケジューリング。実際、プロセスはプログラム実行のインスタンスです。プログラムは、プロセスを作成することによってのみ完了します。例: 今から料理をしたいと思います。まず包丁を手に取り、次に野菜を切り、火をつけて野菜を炒めます。料理というのは実はプログラムであり、包丁を握る、野菜を切る、火をつける、炒めるという一つ一つの処理がプログラムの実行フローの中で順番に実行されて、一つの作業が完了するということです。 。

1.プロセス(Processクラス)​​を作成します

私たちが書くプログラムには、デフォルトでは上から下までコードを実行するためのメインプロセスとメインスレッドが存在します。プロセスとスレッド、そしてメイン プロセスがプロセスとスレッドを作成します (その後、作成されたサブプロセスとサブスレッドが実行するコードを実行します)。 1 つは、作成が完了した後の 2 つの操作です。もう 1 つは、サブプロセスまたはサブスレッドが完了したかどうかに関係なく、メインプロセスが完了したらすぐにプログラムを終了することです。 。

# 在windows下做实验的话,第一句必须加上if __name__ == "__main__":# 创建进程,# 参数target后面的代表的是此进程要执行的函数名称# args后面跟的是一个元组,代表target后面函数所需要的参数p = multiprocessing.Process(target=foo, args=(1,))
    
    p.join(5)   # 当执行完此子进程之后再去执行其他的进程,参数5代表执行此子进程等待的最长时间,默认为s# daemon是指主进程是否要等待子进程完成之后再结束,默认是等待# True 代表不等待# False 代表等待p.daemon = True 
    p.start()   # 启动子进程
ログイン後にコピー
 1 # 下面这段代码显示结果为空,因为在主进程结束之后就结束程序了 2 # 并不会去执行foo函数 3 import multiprocessing 4 import time 5  6 def foo(args): 7     # 这是个要通过子进程执行的函数 8     time.sleep(3)   # 延迟三秒 9     print(args)10     11 if __name__ == "__main__":12     p = multiprocessing.Process(target=foo, args=(1,))13     p.daemon = True # 不等待子进程结束14     p.start()   
15 16 17 # 下面这段代码的执行结果为1 因为daemon的值为false,所以主进程要等待子进程执行完foo之后才会去结束程序18 import multiprocessing19 import time20 21 def foo(args):22     # 这是个要通过子进程执行的函数23     time.sleep(3)   # 延迟三秒24     print(args)25     26 if __name__ == "__main__":27     p = multiprocessing.Process(target=foo, args=(1,))28     p.daemon = False # 不等待子进程结束29     p.start()
ログイン後にコピー
ケース1のデーモン
# 当没有join的时候,输入结果为基本上是同时输出的123456789import multiprocessingimport timedef foo(args):# 这是个要通过子进程执行的函数time.sleep(1)print(args)if __name__ == "__main__":for i in range(10):
        p = multiprocessing.Process(target=foo, args=(i,))
        p.start()#有join的时候,他是一个一个输出的,因为join代表的就是当这个子进程执行完之后才会去执行其他的进程import multiprocessingimport timedef foo(args):# 这是个要通过子进程执行的函数time.sleep(1)print(args)if __name__ == "__main__":for i in range(10):
        p = multiprocessing.Process(target=foo, args=(i,))
        p.start()
        p.join(2)
ログイン後にコピー
ケース2の参加
# 下面这个代码不会输出任何值,当程序执行了1s之后就会结束原因是join默认等待的时间为1s中,但是你的子进程却需要10s的时间,所以子进程还没有执行完主进程就结束了import multiprocessingimport timedef foo(args):# 这是个要通过子进程执行的函数time.sleep(10)print(args)if __name__ == "__main__":
    p = multiprocessing.Process(target=foo, args=(1,))
    p.daemon = True
    p.start()
    p.join(1)
ログイン後にコピー
事例3 参加

  2. 进程池(pool模块)

    什么叫做进程池呢?通俗点就是装进程的容器,在我们写程序的时候,我们不可能来一个程序,我们就去创建一个进程,进程是非常耗费资源的,因此我们通过事先定义一个装进程的容器(进程的个数是固定的),当我们程序需要的时候就会自动的去进程池中区取,如果进程池中的子进程数被取完了,我们就只有等待其他的程序释放了之后我们才能够继续使用。

if __name__ == "__main__":# 创建进程池proc_pool = multiprocessing.Pool(5)# 以下两个都是使用进程池的方式# apply:他内部使用了join方法,每一个子进程进行了完了之后才会去进行下一个子进程的使用# apply_async:他内部没有使用join方法,因此是所有的子进程并发的执行    proc_pool.apply()
    proc_pool.apply_async()
ログイン後にコピー
 1 # 从结果可以看出来,每一个子进程完成了之后才会打印出最后的子进程创建完成 2 import multiprocessing 3 import time 4  5 def foo(s1): 6     time.sleep(1) 7     print(s1) 8 if __name__ == "__main__": 9     # 创建进程池,进程的个数为510     proc_pool = multiprocessing.Pool(5)11     for i in range(10):12         # 创建十个子进程,每个子进程都去执行foo函数,传入的参数为i13         proc_pool.apply(foo, args=(i, ))14     print("子进程创建完成")15 16 输出结果:17 018 119 220 321 422 523 624 725 826 927 子进程创建完成
ログイン後にコピー
事例一apply
# 结果是先打印了进程创建完毕,从执行结果可以看出来,apply_async函数会使所有的子进程并发执行,后面的join函数要使主进程等待子进程完成之后在关闭程序import multiprocessingimport time# 执行的函数def foo(s1):
    time.sleep(1)return s1# 回调函数def foo2(s1):print(s1)if __name__ == "__main__":# 创建进程池,进程的个数为5proc_pool = multiprocessing.Pool(5)for i in range(10):# 创建十个子进程,每个子进程都去执行foo函数,传入的参数为i,把foo函数的返回值当做参数给foo2,然后执行foo2函数proc_pool.apply_async(foo, args=(i, ), callback=foo2)print("子进程创建完成")# 关闭进程池    proc_pool.close()# 等待子进程执行完毕之后返回    proc_pool.join()

输出结果:
子进程创建完成
01
2
3
4
5
6
7
8
ログイン後にコピー
事例2 apply_aysnc

   3. 进程之间的共享

    进程之间本来是独立,互不影响的,如果实在想要在进程之间进行通信的话有两种方法。

      <1>. 数组

      <2>. manage模块创建特殊的数据类型

import multiprocessingimport multiprocessingdef f1(s1, dic):
    dic[s1] = s1if __name__ == "__main__":# 创建一个manage的对象manage = multiprocessing.Manager()# 通过manage创建一个特殊类型的dict,供进程之间进行使用dic = manage.dict()print("没有修改之前的dic:",dic)for i in range(10):
        p = multiprocessing.Process(target=f1, args=(i, dic))
        p.start()
        p.join()print("修改之后的dic:",dic)

结果:
没有修改之前的dic: {}
修改之后的dic: {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
ログイン後にコピー

 

二.线程

   线程是程序最下的执行单元,他本质上也是一个进程,只不过是把进程更加的细微化的一个东西,也是用来执行程序的。

  1. 创建线程

# 线程的创建和进程的创建都差不多,因为从形式上来将线程就是进程# 下面的方法和进程的方法是一样的,就是把daemon变成了setDaemon而已if __name__ == "__main__":# 创建线程foo函数为要用子线程执行的函数,args为传递的参数thread = threading.Thread(target=foo, args=(1, ))# 启动线程    thread.start()# 子线程等的最长时间thread.join(5)# 设置主进程完成之后是否要等待子线程完成,默认是不等待的thread.setDaemon(True)
ログイン後にコピー
 1 # 下面这段代码是没有结果的,因为线程和进程不太一样,线程默认是不等待子线程的 2 import threading 3 import time 4 # 执行的函数 5 def foo(s1): 6     time.sleep(1) 7     print(s1) 8  9 if __name__ == "__main__":10     thread = threading.Thread(target=foo, args=(1, ))11     thread.start()12 13 # 修改成下面这段代码,就可以显示结果了,14 import threading15 import time16 # 执行的函数17 def foo(s1):18     time.sleep(1)19     print(s1)20 21 if __name__ == "__main__":22     thread = threading.Thread(target=foo, args=(1, ))23     thread.setDaemon(False)24     thread.start()
ログイン後にコピー
事例一setDaemon
 1 import threading 2 import time 3  4 # 执行的函数 5 def foo(s1): 6     time.sleep(1) 7     print(s1) 8  9 if __name__ == "__main__":10     for i in range(5):11         thread = threading.Thread(target=foo, args=(i, ))12         thread.start()13         thread.join(2)
ログイン後にコピー
事例二join

  2. Rlock模块

    Rlock模块从名字就可以看出来是一个锁模块,我们都知道线程之间是内存共享的,因此当两个线程同时修改某个值的时候,就会出现脏值(也就是我们预期不到的值),因为我们不知道到底哪个线程修改的有效,因此这个模块就应运而生了,当我们想去修改某个值的时候,就可以用到锁模块,把值锁定起来

 1 # 其实这个例子看不出来数据的混乱。。。。 2 # 只是简单的说了一下rlock模块的使用方法 3 import threading 4 import time 5  6 # 创建一个全局变量,要运用线程对其进行修改 7 num = [] 8 # 创建一个锁对象 9 lock = threading.RLock()10 # 执行的函数11 def foo(s1):12     # 加锁13     # lock.acquire()14     global num15     num.append(s1)16     print(num)17     # 释放锁18     # lock.release()19 if __name__ == "__main__":20     for i in range(40):21         thread = threading.Thread(target=foo, args=(i, ))22         thread.start()23     print(num)
ログイン後にコピー
rlock

  3. event模块

    event模块其实就是暂停的意思,当我们使用了此模块之后,线程就会停在此处,当我们设置了相应的值之后,就会继续执行。

 1 import threading 2 import time 3  4 # 创建一个全局变量,要运用线程对其进行修改 5 num = [] 6 # 创建一个锁对象 7 lock = threading.RLock() 8 event = threading.Event() 9 # 执行的函数10 def foo(s1):11     # 加锁12     lock.acquire()13     # 线程在此暂停(红灯)14     event.wait()15     global num16     num.append(s1)17     print(num)18     # 释放锁19     lock.release()20 if __name__ == "__main__":21     for i in range(5):22         thread = threading.Thread(target=foo, args=(i, ))23         thread.start()24     event.clear()   # 设置为红灯25     inp = input("输入q继续:")26     if inp == 'q':27         # 如果输入的为q,就把event的等待状态改变,继续执行28         event.set() 
29 30 31 结果输出32 输入True继续:q33 [0]34 [0, 1]35 [0, 1, 2]36 [0, 1, 2, 3]37 [0, 1, 2, 3, 4]
ログイン後にコピー
event+lock之后的状态
 1 import threading 2 import time 3  4 # 创建一个全局变量,要运用线程对其进行修改 5 num = [] 6 # 创建一个锁对象 7 lock = threading.RLock() 8 event = threading.Event() 9 # 执行的函数10 def foo(s1):11     # 加锁12     # lock.acquire()13     # 线程在此暂停(红灯)14     event.wait()15     global num16     num.append(s1)17     print(num)18     # 释放锁19     # lock.release()20 if __name__ == "__main__":21     for i in range(5):22         thread = threading.Thread(target=foo, args=(i, ))23         thread.start()24     event.clear()   # 设置为红灯25     inp = input("输入q继续:")26     if inp == 'q':27         # 如果输入的为q,就把event的等待状态改变,继续执行28         event.set()29 30 输出结果:31 输入q继续:q32 [0]33 [0, 2]34 [0, 2, 1]35 [0, 2, 1, 4]36 [0, 2, 1, 4, 3]
ログイン後にコピー
event模型

  4. 生产者消费者模型(queue模块)

    生产者消费者模型其实说的就是队列,队列我们只需要记住先进先出就可以了。

# 导入队列的模块import queue# 创建一个队列,队列的长度最多为5obj = queue.Queue(5)# 从队列中获取值,如果队列为空,则等待obj.get()# 从队列中获取值,如果队列为空,则放弃取值(不等待)obj.get_nowait()# 给队列中上传一个值obj.put("value")
ログイン後にコピー

  5. 线程池

    在Python中默认没有创建线程池的方法,因此在此处总结了wupeiqi老师的两个方法,方法的地址如下  

    

    这段代码的有些地方是比较难懂的,主要的原因是之前写的代码都是顺序执行的,而对于线程和进程而言,都是可以并发执行的,因此对于执行流还是需要注意的。

 1 import queue 2 import threading 3 import time 4  5 class ThreadPool: 6     def __init__(self, max_num): 7         self.ThreadQueue = queue.Queue(max_num) 8         for i in range(max_num): 9             self.ThreadQueue.put(threading.Thread)10     def get_Thread(self):11         return self.ThreadQueue.get()12 13     def add_Thread(self):14         self.ThreadQueue.put(threading.Thread)15 16 def func(pool, args):17     time.sleep(2)18     print(args)19     pool.add_Thread()
ログイン後にコピー
线程池实现--简单的方法
  1 # -*- coding:utf-8 -*-  2 # zhou  3 # 2017/7/5  4   5 import threading  6 import queue  7 import time  8   9 # 列表退出标志位 10 StopEvent = object() 11  12 class ThreadPool: 13     def __init__(self, max_num): 14         # 创建一个空的队列用来存放任务而不是线程 15         self.q = queue.Queue() 16         # 设置空闲的线程数为0 17         self.free_list = [] 18         # 已经创建的线程数 19         self.generate_list = [] 20         # 创建线程的最大个数 21         self.max_num = max_num 22         # 创建任务列表为空 23         self.task = [] 24         self.terminal_flag = False 25  26     def apply(self, target, args, callback=None): 27         # 得到任务列表 28         task = (target, args, callback, ) 29         # print('***', args) 30         # 把任务列表加入队列中 31         self.q.put(task) 32         # 去执行 33         if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: 34             # 如果没有空闲的线程并且创建的线程数小于最大线程数,就创建一个线程 35             self.generate_thread() 36  37     def generate_thread(self): 38         t = threading.Thread(target=self.run) 39         t.start() 40  41     def run(self): 42         current_thread = threading.currentThread 43         self.generate_list.append(current_thread) 44         event = self.q.get() 45         while event != StopEvent: 46             # 是任务,解开任务包,执行任务 47             func1, argument, func2 = event 48             # print("++",argument) 49             try: 50                 ret = func1(*argument) 51                 state = True 52             except Exception as e: 53                 state = False 54                 ret = e 55             if func2 is not None: 56                 try: 57                     func2(state, ret) 58                 except Exception as e: 59                     pass 60             if not self.terminal_flag: 61                 self.free_list.append(current_thread) 62                 event = self.q.get() 63                 self.free_list.remove(current_thread) 64             else: 65                 event = StopEvent 66         else: 67             # 不是任务,就移除 68             self.generate_list.remove(current_thread) 69  70     def close(self): 71         # StopEvent作为循环结束的标志,有多少个线程就会给他创建多少个标志位 72         num = len(self.generate_list) 73         while num: 74             self.q.put(StopEvent) 75             num -= 1 76  77     def terminal(self): 78         self.terminal_flag = True 79         while self.generate_list: 80             self.q.put(StopEvent) 81         # self.close() 82         self.q.empty() 83 # 执行函数 84 def foo(s1): 85     # time.sleep(0.5) 86     print(s1) 87 # 回调函数 88 def f2(state, s2): 89     print(s2) 90  91 if __name__ == "__main__": 92     # 创建一个线程池 93     pool = ThreadPool(5) 94     for i in range(40): 95         # 应用线程池 96         # print('___',i) 97         pool.apply(target=foo, args=(i, )) 98     time.sleep(4) 99     pool.terminal()100 101
ログイン後にコピー
线程池实现--复杂的方法

 

三.协程

  协程是什么呢?协程其实就是微线程,如下图,协程一般用在web页面请求上面,使用协程要导入模块gevent,下面贴一个简单的使用例子

 1 # -*- coding:utf-8 -*- 2 # zhou 3 # 2017/7/5 4 import gevent 5 import requests 6  7 def f1(url): 8     requests.get(url) 9 10 gevent.joinall([11     gevent.spawn(f1, ""),12     gevent.spawn(f1, ""),13 ]14 )
ログイン後にコピー
协程使用方法

 

四. 上下文切换(contextlib)

  其实这个上下文切换和装饰器有点类似,也是在一个操作的前后在去加上一点操作。

  下面代码执行流程

  

import contextlib

@contextlib.contextmanagerdef file_open(file_name, mode):
    f = open(file_name, mode)try:yield ffinally:
        f.close()

with file_open('te', 'r') as obj_f:print(obj_f.read())
ログイン後にコピー

 

以上がPython 7のプロセススレッドとコルーチンの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!