python多线程代码,python多线程网络编程
这一次,我想用以下标题介绍Python的多线程编程:
Python多线程简介Python多线程线程模块Python多线程锁线程锁Python多线程GIL锁Python多线程线程本地多进程与多线程执行特征多进程与多线程切换多进程与多线程计算和IO密集型与多线程
Python多线程简介一个进程由几个线程组成。在Python标准库中,有两个模块,thread和threading,为调度线程提供接口。由于thread是一个低级模块,很多功能并不完善,我们通常只使用threading这种相对完善的高级模块,所以这里只讨论线程模块的使用。
要启动Threading Python的多线程模块,我们只需要将一个函数传递到thread实例中,然后调用start()来运行它。这与我们用来操作流程以调用流程实例的方式相同。
函数的作用是:返回当前线程的实例。MainThread实例的名字是MainThread,子线程的名字可以在创建时给定,也可以默认给定Thread-1、Thread-2这样的名字。
多进程和多线程最大的区别在于,对于多进程来说,同一个变量的副本存在于每个进程中,互不影响,而对于多线程来说,所有线程共享所有变量,所以任何变量都可以被任何线程修改。为了避免多线程同时修改同一个变量的危险情况。
首先,我们需要了解多线程是如何同时修改一个变量的。
理论上,无论我们怎么调用函数change(),共享变量A的值都应该是0,但实际上,由于两个线程t1和t2交替运行的次数太多,A的结果不一定是0。
要了解这种情况,我们首先要简单了解一下CPU执行代码时的底层工作原理:
在编程语言中,运行在底层的一行代码不一定是完整的一行。例如,上面的代码a=a 1,CPU实际上运行在一个临时变量中,先存储一个。
1,然后把这个临时变量的值赋给A,如果你学过arm开发,就能明白,CPU在工作的时候,实际上是把A和1的值存储在两个寄存器里,然后把两个寄存器的值相加,把结果存储在第三个寄存器里,然后把第三个寄存器的值存储并覆盖在原来存储A的值的寄存器里,代码语言可以理解如下:
因此,因为两个线程都调用各自的寄存器或拥有自己的临时变量c3,所以当t1和t2交替运行时,可能会出现下面代码中描述的情况:
为了避免这种情况,我们需要提供线程锁来保证当一个线程获得change()的调用权时,另一个线程不能同时执行change()方法。直到锁被释放,它将不能继续修改。
我们用threading.lock()方法创建一个线程锁。
这样无论怎么跑,结果都会如我们所料是0。
当多个线程同时执行lock.acquire()时,只有一个线程能够成功获取线程锁然后继续执行代码,其他线程只能等待锁的释放。同时,获得锁的线程必须记得释放它,否则它将成为死线程。因此,我们将努力.最后.以确保锁的释放。但是锁的问题在于,一方面,原来的多线程任务实际上变成了单线程的运行模式(虽然对于Python的伪多线程来说,这不会造成任何性能下降);另一方面,由于可以有多个锁,不同的线程可能持有不同的锁,并试图获取对方的锁,这可能会导致死锁,导致所有的多线程都挂起,然后只能被操作系统强制终止。
Python的多线程GIL锁对于一个多核CPU来说,它可以同时执行多个线程。我们可以通过Windows提供的任务管理器看到CPU的资源利用率。所以,当我们提供一个死线程无限循环时,CPU的一个核的利用率会提高到100%,如果我们提供两个,另一个核的利用率会达到100%。如果我们在java或C中这样做,就会发生这种情况,但如果我们在Python中尝试。
你可以看到,我们从multiprocessing.cpu_count()知道我们有4个cpu,然后打印4行显示已经执行了4个线程。这个时候我们的CPU利用率应该是满的,但实际上,
我们可以从红框中看到,事实并非如此。事实上,即使我们启用更多的线程,CPU利用率也不会增加多少。这是因为虽然Python使用了一个真实的线程,但是Python的解释器在执行代码时有一个GIL锁。无论执行什么Python代码,都必然会得到GIL锁,然后每执行100行代码就会释放GIL锁,让其他线程有机会执行。GIL锁实际上锁定了一个Python进程的所有线程,所以即使是更多的线程也只能在一个Python进程中交替执行,也就是只能使用一个内核。
Python多线程的ThreadLocal既然我们已经知道一个全局变量会受到所有线程的影响,那么应该如何构建一个只属于这个线程的“全局变量”呢?换句话说,我们希望这个变量在这个线程中有一个类似全局变量的功能,不希望其他线程调用它来防止上述问题。我们做什么呢
如您所见,在这个子线程中,如果我们希望函数do_task1()和do_task2()能够使用变量A,我们必须将其作为参数传入。
使用ThreadLocal对象是解决这个问题的方法,不需要繁琐的操作,它是由threading.local()方法创建的:
我们可以认为ThreadLocal的原理类似于创建一个字典。当我们创建一个变量local _ variable时。A、我们实际上在local _ variable字典中创建了若干个dict,这些dict是由threading.current_thread()作为关键字(当前线程),A作为不同线程中的值的键值对组成的。可以参考以下套路:
结果与上面带有ThreadLocal的例程相同。当然,我在这里只是尝试简单描述一下ThreadLocal的工作原理,因为其实它的工作原理和我们上面使用dict的例程并不完全一样,因为ThreadLocal对象可以传递的变量完全不止一个:
甚至local_varient.c,local _ variable.d …都可以用。没有一定的数量限制。而且在dict中,只能有一个以threading.current_thread()为key的键值对,对吗?
进程和线程的比较在初步了解了进程和线程以及它们在Python中的使用方式之后,现在我们来讨论一下它们之间的区别和优缺点。
多进程与多线程相比的性能特点。首先我们简单了解一下多任务的工作模式:通常我们设计为Master-Worker模式,Master负责分配任务,Worker负责执行任务。在多任务环境中,一个主设备通常对应多个工人。
然后多进程任务实现Master-Worker,主进程是Master,其他进程是Worker。多线程任务、主线程主控器、子线程工作器。
先说多流程。多进程的优势在于稳定性高。因为一个子进程的崩溃不会影响到其他子进程和主进程(主进程死了也会崩溃)。但是多进程的问题是创建进程的开销太高,尤其是在Windows系统中,比Unix/Linux系统使用fork()的开销要高很多。此外,对于操作系统本身来说,它可以同时运行的进程数量是有限的。
多线程模式消耗的资源没有多进程多,所以往往更快(但好像也快不了多少?但是,至少在Windows中,多线程的效率往往高于多进程。而且多线程模式和多进程模式正好相反。如果一个线程挂了,进程中的所有线程,包括主线程,都会直接崩溃,因为所有线程都是共享进程内存的。在Windows系统中,如果我们看到“本程序执行了非法操作,即将关闭”的提示,往往是因为某个线程出了问题,整个进程崩溃。
比较多进程和多线程,在使用多进程或多线程时,要考虑线程数或进程数切换的开销。无论是进程还是线程,如果太多,那么效率肯定上不去。
因为当操作系统切换进程和线程时,需要保存当前的执行环境(包括CPU寄存器状态、内存页面等。)首先,然后准备另一个任务的执行环境(恢复最后的寄存器状态,切换内存页面等。)开始新任务之前。虽然这个过程很快,但是再快,也需要时间。因此,一旦任务数量过多,准备环境所浪费的时间将是巨大的。
多进程与多线程相比,是计算密集型和IO密集型的。考虑多任务的类型也是我们判断如何构建工作模式的重要一点。我们可以简单地将任务分为两类:计算密集型和IO密集型。
计算密集型任务的特点是运算量大,消耗CPU资源,比如一些复杂的数学运算,或者视频的一些高清解码运算等。和是仅由CPU的计算能力执行的任务。虽然这类任务也可以通过多任务模式完成,但是任务之间切换的消耗往往比较大。因此,如果我们想要高效地计算这类任务,同时计算密集型任务的数量不应超过CPU核心的数量。
至于语言,代码运行的效率对于计算密集型任务也至关重要。所以Python这样的高级语言往往不适合,而C这样的低级语言效率更高。好在Python在处理这类任务的时候经常会用到用C写的库,但是如果你想自己实现这类任务的底层计算功能,还是以C为主的好。
IO密集型的特点是大量的输入输出。涉及网络和磁盘IO的任务通常是IO密集型的。这些任务并不会消耗太多的CPU资源,往往会花时间等待IO操作完成,因为IO操作的速度往往比CPU和内存慢很多。对于IO密集型的任务,多任务的效率会非常高,但是当然,任务的数量是有限制的。
至于用于这类任务的编程语言,Python这种开发效率高的语言更适合,因为可以减少代码量,而C语言因为写起来麻烦,效果不好。
现代操作系统对IO操作有了很大的改进,提供异步IO操作,实现单进程单线程多任务。在单核CPU上采用单进程模型,可以有效地支持多任务。在多核CPU上,还可以运行多个进程(数量与CPU核数相同),充分利用多核CPU。通过异步IO编程模型实现多任务是主流趋势。在Python中,单进程的异步编程模型被称为协程。
涉及
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。