python的进程,python中进程,线程,协程详细介绍

  python的进程,python中进程,线程,协程详细介绍

  本文主要介绍Python线程和进程的相关知识,包括线程和进程的区别,并通过示例代码介绍进程和线程的操作方法。有需要的朋友可以参考一下。

  00-1010什么是流程?线程和进程的区别是什么?Python中的并行并发多进程操作?Python的线程模块锁:全局解释器锁(GIL)参考文章:

  

目录

  进程是在操作系统中执行的程序。操作系统按进程分配存储空间。每个进程都有自己的地址空间、数据堆栈和其他用于跟踪进程执行的辅助数据。操作系统管理所有进程的执行,并为它们合理分配资源。

  每个进程都有自己独立的内存空间,不同的进程通过进程间通信进行通信。因为进程很重并且占用独立的内存,所以交换开销(堆栈、寄存器、虚拟内存、文件句柄等)很大。)相对较大,但相对稳定和安全。

  

什么是进程

  一个进程也可以有多个并发执行线程。简单来说,它有多个可以被CPU调度的执行单元,称为线程。

  CPU调度和分派线程的基本单位是一个进程的实体,CPU调度和分派的基本单位,它比一个进程小,可以独立运行。基本上,线程本身没有系统资源,只有一些在其运行中必不可少的资源(如程序计数器、一组寄存器和堆栈)。

  因为线程在同一个进程中,可以共享同一个上下文,所以和进程相比,线程之间的信息共享和通信更容易,上下文切换快,资源开销少,但是和足够不稳定的进程相比,容易丢失数据。

  注:,当然,在单核CPU系统中,真正的并发是不可能的,因为在某个时刻只有一个线程可以获得CPU,多个线程共享CPU的执行时间。

  线程缺点:

  多线程并非没有危害。从其他进程的角度来看,多线程程序对其他程序并不友好,因为它占用了更多的CPU执行时间,导致其他程序得不到足够的CPU执行时间;另一方面,从开发者的角度来说,编写和调试多线程程序对开发者的要求更高,对于初学者来说难度更大。

  

什么是线程

  地址和其他资源:进程相互独立,在同一个进程中的线程之间共享。我想用一根线来爱你,但是其他的过程是看不见的。通信:进程间通信IPC,线程可以直接读写进程数据段进行通信。3354它需要进程同步和互斥的辅助来保证数据的一致性。和调度切换:线程上下文切换比进程上下文切换快得多。在多线程操作系统中,进程不是可执行的实体。

  

线程与进程的区别

  并行(Parallelism)

  并行:是指两个或两个以上的事件(或线程)同时发生,是指真正意义上的不同事件或线程同时在不同的CPU资源(多核)上执行。

  特性

  同时发生,同时执行。没有并发之类的竞争和等待的概念。并发(Concurrency)

  它是指一个物理CPU(或多个物理CPU)在几个程序(或线程)之间的多路复用。并发性是强制多用户共享有限的物理资源以提高效率。

  特性

  微观角度:所有并发进程都有排队等待、唤醒、执行等步骤。微观上,都是按顺序处理的。如果请求(或线程)同时到达,它们将根据不同的优先级进入队列等待执行。宏观视角:几乎同时到达的多个请求(或线程)看起来像是同时被处理。

  

并行与并发

  

Python中的多进程

  流程模块是创建流程的模块。使用该模块,可以创建流程。

  方法:进程([组[,目标[,名称[,参数[,kwar]

  gs]]]]])

  由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)。

  注意:

  

  1. 必须使用关键字方式来指定参数;
  2. args指定的为传给target函数的位置参数,是一个元祖形式,必须有逗号。

  参数介绍:

  

  • group:参数未使用,默认值为None。
  • target:表示调用对象,即子进程要执行的任务。
  • args:表示调用的位置参数元祖。
  • kwargs:表示调用对象的字典。如kwargs = {'name':Jack, 'age':18}。
  • name:子进程名称。

  代码展示:

  

import os

  from multiprocessing import Process

  def func_one():

   print("第一个子进程")

   print("子进程(一)大儿子:%s 父进程:%s" % (os.getpid(), os.getppid()))

  def func_two():

   print("第二个子进程")

   print("子进程(二)二儿子:%s 父进程:%s" % (os.getpid(), os.getppid()))

  if __name__ == __main__:

   p_one = Process(target=func_one)

   P_two = Process(target=func_two)

   p_one.start()

   P_two.start()

   print("子进程:%s 父进程:%s" % (os.getpid(), os.getppid()))

  继承Process的方式开启进程的方式:

  

import os

  from multiprocessing import Process

  def func_one():

   print("第一个子进程")

   print("子进程(一)大儿子:%s 父进程:%s" % (os.getpid(), os.getppid()))

  def func_two():

   print("第二个子进程")

   print("子进程(二)二儿子:%s 父进程:%s" % (os.getpid(), os.getppid()))

  if __name__ == __main__:

   p_one = Process(target=func_one)

   P_two = Process(target=func_two)

   p_one.start()

   P_two.start()

   print("子进程:%s 父进程:%s" % (os.getpid(), os.getppid()))

  

  

线程

  

  

Python的threading模块

  在Python早期的版本中就引入了thread模块(现在名为_thread)来实现多线程编程,然而该模块过于底层,而且很多功能都没有提供,因此目前的多线程开发我们推荐使用threading模块,该模块对多线程编程提供了更好的面向对象的封装。

  

from random import randint

  from threading import Thread

  from time import time, sleep

  def download(filename):

   print(开始下载%s... % filename)

   time_to_download = randint(5, 10)

   sleep(time_to_download)

   print(%s下载完成! 耗费了%d秒 % (filename, time_to_download))

  def main():

   start = time()

   t1 = Thread(target=download, args=(Python从入门到住院.pdf,))

   t1.start()

   t2 = Thread(target=download, args=(Peking Hot.avi,))

   t2.start()

   #join阻塞完成任务

   t1.join()

   t2.join()

   end = time()

   print(总共耗费了%.3f秒 % (end - start))

  if __name__ == __main__:

   main()

  我们可以直接使用threading模块的Thread类来创建线程,但是我们之前讲过一个非常重要的概念叫继承,我们可以从已有的类创建新类,因此也可以通过继承Thread类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。

  代码如下所示:

  

from random import randint

  from threading import Thread

  from time import time, sleep

  class DownloadTask(Thread):

   def __init__(self, filename):

   super().__init__()

   self._filename = filename

   def run(self):

   print(开始下载%s... % self._filename)

   time_to_download = randint(5, 10)

   sleep(time_to_download)

   print(%s下载完成! 耗费了%d秒 % (self._filename, time_to_download))

  def main():

   start = time()

   t1 = DownloadTask(Python从入门到住院.pdf)

   t1.start()

   t2 = DownloadTask(Peking Hot.avi)

   t2.start()

   t1.join()

   t2.join()

   end = time()

   print(总共耗费了%.2f秒. % (end - start))

  if __name__ == __main__:

   main()

  

  

锁Lock:

  模拟场景:

  演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果。

  

from time import sleep

  from threading import Thread

  class Account(object):

   #初始化账户余额为0元

   def __init__(self):

   self._balance = 0

   # 存款函数

   def deposit(self, money):

   # 计算存款后的余额

   new_balance = self._balance + money

   # 模拟受理存款业务需要0.01秒的时间

   sleep(0.01)

   # 修改账户余额

   self._balance = new_balance

   # set和get

   @property

   def balance(self):

   return self._balance

  class AddMoneyThread(Thread):

   def __init__(self, account, money):

   super().__init__()

   self._account = account

   self._money = money

   def run(self):

   self._account.deposit(self._money)

  def main():

   # 创建对象

   account = Account()

   threads = []

   # 创建100个存款的线程向同一个账户中存钱

   for _ in range(100):

   t = AddMoneyThread(account, 1)

   threads.append(t)

   t.start()

   # 等所有存款的线程都执行完毕

   for t in threads:

   t.join()

   print(账户余额为: ¥%d元 % account.balance)

  if __name__ == __main__:

   main()

  运行结果:

  100个线程分别向账户中转入1元钱,结果居然远远小于100元。

  之所以出现这种情况是因为我们没有对银行账户这个临界资源加以保护,多个线程同时向账户中存钱时,会一起执行到new_balance = self._balance + money这行代码,多个线程得到的账户余额都是初始状态下的0,所以都是0上面做了+1的操作,因此得到了错误的结果。

  在这种情况下,锁就可以派上用场了。我们可以通过锁来保护临界资源,只有获得锁的线程才能访问临界资源,而其他没有得到锁的线程只能被阻塞起来,直到获得锁的线程释放了锁,其他线程才有机会获得锁,进而访问被保护的临界资源。

  加锁:

  

from time import sleep

  from threading import Thread, Lock

  class Account(object):

   def __init__(self):

   self._balance = 0

   self._lock = Lock()

   def deposit(self, money):

   # 先获取锁才能执行后续的代码

   self._lock.acquire()

   try:

   new_balance = self._balance + money

   sleep(0.01)

   self._balance = new_balance

   finally:

   # 在finally中执行释放锁的操作保证正常异常锁都能释放

   self._lock.release()

   @property

   def balance(self):

   return self._balance

  class AddMoneyThread(Thread):

   def __init__(self, account, money):

   super().__init__()

   self._account = account

   self._money = money

   def run(self):

   # 运行存钱业务,只有获取锁的才能执行

   self._account.deposit(self._money)

  def main():

   account = Account()

   threads = []

   #创建100个线程

   for _ in range(100):

   # 线程加钱

   t = AddMoneyThread(account, 1)

   threads.append(t)

   t.start()

   for t in threads:

   t.join()

   print(账户余额为: ¥%d元 % account.balance)

  if __name__ == __main__:

   main()

  结果:账户余额为: ¥100元

  比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,因为Python的解释器有一个全局解释器锁(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。

  

  

全局解释器锁(GIL)

  GIL是一个互斥锁,它防止多个线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的 尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的。

  因此,解释器实际上被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。在多线程环境中,Python 虚拟机按以下方式执行:

  

  • 设置GIL
  • 切换到一个线程去执行
  • 运行

  由于GIL的存在,Python的多线程不能称之为严格的多线程。因为多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行。

  由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行,既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?

  由于以前的电脑基本都是单核CPU,多线程和单线程几乎看不出差别,可是由于计算机的迅速发展,现在的电脑几乎都是多核CPU了,最少也是两个核心数的,这时差别就出来了:通过之前的案例我们已经知道,即使在多核CPU中,多线程同一时刻也只有一个线程在运行,这样不仅不能利用多核CPU的优势,反而由于每个线程在多个CPU上是交替执行的,导致在不同CPU上切换时造成资源的浪费,反而会更慢。即原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源。

  但是在使用多线程抓取网页内容时,遇到IO阻塞时,正在执行的线程会暂时释放GIL锁,这时其它线程会利用这个空隙时间,执行自己的代码,因此多线程抓取比单线程抓取性能要好,所以我们还是要使用多线程的。

  

  

参考文章:

  深度解析Python线程和进程 - wyh草样 - 博客园

  Python-100-Days/13.进程和线程.md at master · jackfrued/Python-100-Days · GitHub

  到此这篇关于Python深度解析线程和进程的文章就介绍到这了,更多相关python线程和进程内容请搜索盛行IT软件开发工作室以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT软件开发工作室!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: