轻松把握多线程和多进程-Python编程进阶 (多线是什么意思)
1、简介
咱们将讨论如何应用/target=_blankclass=infotextkey>Python口头多线程和多进程义务。它们提供了在单个进程或多个进程之间口头并发操作的方法。并行和并发口头可以提高系统的速度和效率。
在讨论多线程和多进程的基础常识之后,咱们还将讨论经常使用Python库成功它们的实践方法。
首先简明讨论并行系统的好处。
并行计算的长处
以上是须要并发或并行口头的一些经常出现要素。如今,回到主题,即多线程和多进程,并讨论它们的关键区别。
2、什么是多线程?
多线程是在单个进程中成功并行性的一种方法,能够口头同时启动的义务。在单个进程内可以创立多个线程,并在该进程内并行口头较小的义务。
单个进程中的线程共享一个公共内存空间,但它们的堆栈跟踪和寄存器是独立的。由于共享内存,它们的计算老本较低。
复线程和多线程Env.
Python中的多线程关键用于口头I/O操作,即假设程序的某个局部正在口头I/O操作,则其他程序可以坚持照应。但是,在Python的成功中,由于全局解释器锁(GIL)的存在,多线程无法成功真正的并行性。
简而言之,GIL是一个互斥锁,一次性只准许一个线程与Python字节码交互,即使在多线程形式下,一次性也只能有一个线程口头字节码。
这样做是为了在CPython中坚持线程安保,但它限度了多线程的性能长处。为了处置这个疑问,Python有一个独自的多进程库,咱们将在之后启动讨论。
什么是守护线程?
始终在后盾运转的线程称为守护线程。它们的关键上班是支持主线程或非守护线程。守护线程不会阻塞主线程的口头,甚至会在主线程口头终了后继续运转。
在Python中,守护线程关键用作渣滓回收器。它会自动销毁一切无用的对象并监禁内存,以便主线程可以反经常常使用和口头。
3、什么是多进程?
多进程用于口头多个进程的并行口头。它可以协助成功真正的并行性,由于可以同时口头不同的进程,并且每个进程都领有自己的内存空间。它经常使用CPU的独立外围,并且在口头进程间的数据替换时也很有协助。
与多线程相比,多进程的计算老本更高,由于不经常使用共享内存空间。不过,它准许启动独立口头,并克制了全局解释器锁的限度。
多进程环境
上图展现了一个多进程环境,在该环境中,一个主进程创立了两个独立的进程,并为它们调配了不同的上班。
4、多线程成功
如今,咱们经常使用Python成功一个基本的多线程示例。Python有一个内置的threading模块用于多线程成功。
importthreadingimportos
这是一个用于计算数字平方的便捷函数,它接受一个数字列表作为输入,并输出列表中每个数字的平方,同时输入经常使用的线程称号和与该线程关联的进程ID。
defcalculate_squares(numbers):fornuminnumbers:square=num*numprint(f"Squareofthenumber{num}is{square}|ThreadName{threading.current_thread().name}|PIDoftheprocess{os.getpid()}")
本示例有一个数字列表,将其平均分红两半,并区分命名为first_half和second_half。如今,将为这些列表调配两个独立的线程t1和t2。
Thread函数创立一个新线程,该线程接受一个带有参数列表的函数作为输入。还可认为线程调配一个独自的称号。
.start()函数将开局口头这些线程,而.join()函数将阻塞主线程的口头,直到给定的线程齐全口头终了。
if__name__=="__mn__":numbers=[1,2,3,4,5,6,7,8]half=len(numbers)//2first_half=numbers[:half]second_half=numbers[half:]t1=threading.Thread(target=calculate_squares,,args=(first_half,))t2=threading.Thread(target=calculate_squares,,args=(second_half,))t1.start()t2.start()t1.join()t2.join()
输入:
Squareofthenumber1is1|ThreadNamet1|PIDoftheprocess345Squareofthenumber2is4|ThreadNamet1|PIDoftheprocess345Squareofthenumber5is25|ThreadNamet2|PIDoftheprocess345Squareofthenumber3is9|ThreadNamet1|PIDoftheprocess345Squareofthenumber6is36|ThreadNamet2|PIDoftheprocess345Squareofthenumber4is16|ThreadNamet1|PIDoftheprocess345Squareofthenumber7is49|ThreadNamet2|PIDoftheprocess345Squareofthenumber8is64|ThreadNamet2|PIDoftheprocess345
如今来了解一下上述代码生成的输入结果。可以观察到两个线程的进程ID(即PID)坚持不变,这象征着这两个线程属于同一个进程。
还可以观察到输入并非按顺序生成。第一行中可以看到是线程1生成的输入,而后在第三行是线程2生成的输入,接着在第四行,再次是线程1生成的输入。这分明地标明这些线程是同时上班的。
并发并不象征着这两个线程并行口头,由于一次性只要一个线程被口头。它不会缩小口头期间,与顺序口头所需的期间相反。CPU开局口头一个线程,但在中途退出,并切换到另一个线程,过一段期间后,又回到主线程,并从上次退出的中央开局口头。
5、多进程成功
目前对多线程及其成功形式和限度曾经有基本的了解。如今,是时刻学习多进程的成功以及如何克制这些限度了。
在这里将沿用相反的示例,但不再创立两个独立的线程,而是创立两个独立的进程,并讨论观察结果。
frommultiprocessingimportProcessimportos
本例将经常使用multiprocessing模块来创立独立的进程。
该函数将坚持不变。只是在这里删除了无关线程消息的打印语句。
defcalculate_squares(numbers):fornuminnumbers:square=num*numprint(f"Squareofthenumber{num}is{square}|PIDoftheprocess{os.getpid()}")
主函数有一些修正。只是创立了一个独立的进程,而不是线程。
if__name__=="__main__":numbers=[1,2,3,4,5,6,7,8]half=len(numbers)//2first_half=numbers[:half]second_half=numbers[half:]p1=Process(target=calculate_squares,args=(first_half,))p2=Process(target=calculate_squares,args=(second_half,))p1.start()p2.start()p1.join()p2.join()
输入:
Squareofthenumber1is1|PIDoftheprocess1125Squareofthenumber2is4|PIDoftheprocess1125Squareofthenumber3is9|PIDoftheprocess1125Squareofthenumber4is16|PIDoftheprocess1125Squareofthenumber5is25|PIDoftheprocess1126Squareofthenumber6is36|PIDoftheprocess1126Squareofthenumber7is49|PIDoftheprocess1126Squareofthenumber8is64|PIDoftheprocess1126
可以观察到,每个列表都由一个独立的进程口头。它们具备不同的进程ID。为了审核进程能否已并行口头,须要创立一个独自的环境,上方咱们将讨论这一点。
计算能否经常使用多进程的运转期间
为了审核能否取得了真正的并行性,在这里将计算经常使用和不经常使用多进程的算法运转期间。
为此,须要一个蕴含超越10^6个整数的大型整数列表。可以经常使用random库生成一个列表。此处将经常使用Python的time模块来计算运转期间。上方是成功的代码,代码自身很容易了解,也可以随时检查代码注释。
frommultiprocessingimportProcessimportosimporttimeimportrandomdefcalculate_squares(numbers):fornuminnumbers:square=num*numif__name__=="__main__":numbers=[random.randrange(1,50,1)foriinrange(10000000)]#创立一个蕴含10^7个整数的随机列表。half=len(numbers)//2first_half=numbers[:half]second_half=numbers[half:]#-----------------创立单进程环境------------------------#start_time=time.time()#开局计时(不经常使用多进程)p1=Process(target=calculate_squares,args=(numbers,))#单进程P1口头整个列表p1.start()p1.join()end_time=time.time()#完结计时(不经常使用多进程)print(f"ExecutionTimeWithoutMultiprocessing:{(end_time-start_time)*10**3}ms")#-----------------创立多进程环境------------------------#start_time=time.time()#开局计时(经常使用多进程)p2=Process(target=calculate_squares,args=(first_half,))p3=Process(target=calculate_squares,args=(second_half,))p2.start()p3.start()p2.join()p3.join()end_time=time.time()#完结计时(经常使用多进程)print(f"ExecutionTimeWithMultiprocessing:{(end_time-start_time)*10**3}ms")
输入:
ExecutionTimeWithoutMultiprocessing:619.8039054870605msExecutionTimeWithMultiprocessing:321.70287895202637ms
可以观察到,经常使用多进程的期间简直是不经常使用多进程期间的一半。这标明这两个进程在同一期间内并行口头,并展现了真正的并行性行为。
python 多线程和多进程的区别 mutiprocessing theading
多线程可以共享全局变量,多进程不能。 多线程中,所有子线程的进程号相同;多进程中,不同的子进程进程号不同。 950#!/usr/bin/python# -*- coding:utf-8 -*-import osimport threadingimport multiprocessingcount_thread = 0count_process = 0 # worker functiondef worker1(sign, lock):global count_()count_thread += 1print(sign, ())() def worker2(sign, lock):global count_()count_process += 1print(sign, ())()# Mainprint(Main:,()) # Multi-threadrecord = []lock= ()for i in range(5):thread = (target=worker1,args=(thread,lock))()(thread) for thread in () # Multi-processrecord = []lock = ()for i in range(5):process = (target=worker2,args=(process,lock))()(process) for process in ()print count_threadprint count_process运行结果(Main:, 3142)(thread, 3142)(thread, 3142)(thread, 3142)(thread, 3142)(thread, 3142)(process, 3148)(process, 3149)(process, 3150)(process, 3151)(process, 3152)50应该尽量避免多进程共享资源。 多进程共享资源必然会带来进程间相互竞争。 而这种竞争又会造成race condition,我们的结果有可能被竞争的不确定性所影响。 但如果需要,我们依然可以通过共享内存和Manager对象这么做。 1) 共享内存用Python实现的例子import multiprocessing def f(n, a) = 3.14a[0]= 5 num = (d, 0.0)arr = (i, range(10)) p = (target=f, args=(num, arr))()() print arr[:]这里我们实际上只有主进程和Process对象代表的进程。 我们在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。 对象Value被设置成为双精度数(d), 并初始化为0.0。 而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。 在Process进程中,我们修改了Value和Array对象。 回到主程序,打印出结果,主程序也看到了两个对象的改变,说明资源确实在两个进程之间共享。 2)ManagerManager对象类似于服务器与客户之间的通信 (server-client),与我们在Internet上的活动很类似。 我们用一个进程作为服务器,建立Manager来真正存放资源。 其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。 在防火墙允许的情况下,我们完全可以将Manager运用于多计算机,从而模仿了一个真实的网络情境。 下面的例子中,我们对Manager的使用类似于shared memory,但可以共享更丰富的对象类型。 import multiprocessing def f(x, arr, l) = 3.14arr[0] = (Hello) server = ()x= (d, 0.0)arr= (i, range(10))l= () proc = (target=f, args=(x, arr, l))()() print()print(arr)print(l)Manager利用list()方法提供了表的共享方式。 实际上你可以利用dict()来共享词典,Lock()来共享(注意,我们共享的是,而不是进程的。 后者本身已经实现了进程共享)等。 这样Manager就允许我们共享更多样的对象。 参考资料:
如何进行Python多线程编程,一文读懂Python多线程
本文介绍的是Python多线程,想了解Python多线程,得先了解什么是线程。 线程是操作系统能够进行运算调度的最小单位。 它被包含在进程之中,是进程中的实际运作单位。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 而多线程类似于同时执行多个不同程序,多线程运行有如下优点:1.使用线程可以把占据长时间的程序中的任务放到后台去处理。 2.用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度程序的运行速度可能加快3.在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。 在这种情况下我们可以释放一些珍贵的资源如内存占用等等。 4.线程在执行过程中与进程还是有区别的。 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。 但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。 指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。 5.线程可以被抢占(中断)。 6.在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。 开始学习Python多线程Python中使用线程有两种方式:函数或者用类来包装线程对象。 函数式:调用thread模块中的start_new_thread()函数来产生新线程。 语法如下_new_thread ( function, args[, kwargs] )参数说明 - 线程函数。 - 传递给线程函数的参数,他必须是个tuple类型。 - 可选参数。 附上实例#!/usr/bin/python# -*- coding: UTF-8 -*-import threadimport time# 为线程定义一个函数def print_time( threadName, delay):count = 0while count < (delay)count += 1print %s: %s % ( threadName, (()) )# 创建两个线程_new_thread( print_time, (Thread-1, 2, ) )_new_thread( print_time, (Thread-2, 4, ) )except:print Error: unable to start threadwhile 1:pass执行以上程序输出结果如下:Thread-1: Thu Jan 22 15:42:17 2009Thread-1: Thu Jan 22 15:42:19 2009Thread-2: Thu Jan 22 15:42:19 2009Thread-1: Thu Jan 22 15:42:21 2009Thread-2: Thu Jan 22 15:42:23 2009Thread-1: Thu Jan 22 15:42:23 2009Thread-1: Thu Jan 22 15:42:25 2009Thread-2: Thu Jan 22 15:42:27 2009Thread-2: Thu Jan 22 15:42:31 2009Thread-2: Thu Jan 22 15:42:35 2009线程的结束一般依靠线程函数的自然结束;也可以在线程函数中调用(),他抛出SystemExit exception,达到退出线程的目的
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。