python多线程全局变量和锁1.python中数据类型,int,float,复数 , 字符,元组,做全局变量时需要在函数里面用global申明变量,才能对变量进行操作 。
而 , 对象,列表,词典,不需要声明,直接就是全局的 。
2.线程锁mutex=threading.Lock()
创建后就是全局的 。线程调用函数可以直接在函数中使用 。
mutex.acquire()开启锁
mutex=release()关闭锁
要注意 , 死锁的情况发生 。
注意运行效率的变化:
正常1秒,完成56997921
加锁之后,1秒只运行了531187,相差10倍多 。
3.继承.threading.Thread的类,无法调用__init__函数,无法在创建对象时初始化新建的属性 。
4.线程在cpu的执行,有随机性
5. 新建线程时 , 需要传参数时,args是一个元组 , 如果只有一个参数,一定后面要加一个,符号 。不能只有一个参数否则线程会报创建参数错误 。threading.Thread(target=fuc,args=(arg,))
python之多线程 进程的概念:以一个整体的形式暴露给操作系统管理,里面包含各种资源的调用 。对各种资源管理的集合就可以称为进程 。
线程的概念:是操作系统能够进行运算调度的最小单位 。本质上就是一串指令的集合 。
进程和线程的区别:
1、线程共享内存空间,进程有独立的内存空间 。
2、线程启动速度快,进程启动速度慢 。注意:二者的运行速度是无法比较的 。
3、线程是执行的指令集,进程是资源的集合
4、两个子进程之间数据不共享,完全独立 。同一个进程下的线程共享同一份数据 。
5、创建新的线程很简单 , 创建新的进程需要对他的父进程进行一次克隆 。
6、一个线程可以操作(控制)同一进程里的其他线程,但是进程只能操作子进程
7、同一个进程的线程可以直接交流,两个进程想要通信,必须通过一个中间代理来实现 。
8、对于线程的修改,可能会影响到其他线程的行为 。但是对于父进程的修改不会影响到子进程 。
第一个程序 , 使用循环来创建线程,但是这个程序中一共有51个线程,我们创建了50个线程 , 但是还有一个程序本身的线程,是主线程 。这51个线程是并行的 。注意:这个程序中是主线程启动了子线程 。
相比上个程序,这个程序多了一步计算时间,但是我们观察结果会发现,程序显示的执行时间只有0.007秒,这是因为最后一个print函数它存在于主线程,而整个程序主线程和所有子线程是并行的,那么可想而知,在子线程还没有执行完毕的时候print函数就已经执行了,总的来说,这个时间只是执行了一个线程也就是主线程所用的时间 。
接下来这个程序,吸取了上面这个程序的缺点,创建了一个列表 , 把所有的线程实例都存进去,然后使用一个for循环依次对线程实例调用join方法,这样就可以使得主线程等待所创建的所有子线程执行完毕才能往下走 。注意实验结果:和两个线程的结果都是两秒多一点
注意观察实验结果 , 并没有执行打印task has done,并且程序执行时间极其短 。
这是因为在主线程启动子线程前把子线程设置为守护线程 。
只要主线程执行完毕,不管子线程是否执行完毕,就结束 。但是会等待非守护线程执行完毕
主线程退出,守护线程全部强制退出 。皇帝死了,仆人也跟着殉葬
应用的场景 : socket-server
注意:gil只是为了减低程序开发复杂度 。但是在2.几的版本上,需要加用户态的锁(gil的缺陷)而在3点几的版本上,加锁不加锁都一样 。
下面这个程序是一个典型的生产者消费者模型 。
生产者消费者模型是经典的在开发架构中使用的模型
运维中的集群就是生产者消费者模型,生活中很多都是
那么,多线程的使用场景是什么?
python中的多线程实质上是对上下文的不断切换,可以说是假的多线程 。而我们知道 , io操作不占用cpu,计算占用cpu,那么python的多线程适合io操作密集的任务 , 比如socket-server,那么cpu密集型的任务,python怎么处理?python可以折中的利用计算机的多核:启动八个进程 , 每个进程有一个线程 。这样就可以利用多进程解决多核问题 。
python之多线程原理并发:逻辑上具备同时处理多个任务的能力 。
并行:物理上在同一时刻执行多个并发任务 。
举例:开个QQ,开python线程函数了一个进程,开了微信,开了一个进程 。在QQ这个进程里面 , 传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程 。
总结:开一个软件 , 相当于开了一个进程 。在这个软件运行的过程里,多个工作同时运转,完成了QQ的运行,那么这个多个工作分别有多个线程 。
线程和进程之间的区别:
进程在python中的使用,对模块threading进行操作,调用的这个三方库 。可以通过 help(threading) 了解其中的方法、变量使用情况 。也可以使用 dir(threading) 查看目录结构 。
current_thread_num = threading.active_count() # 返回正在运行的线程数量
run_thread_len = len(threading.enumerate()) # 返回正在运行的线程数量
run_thread_list = threading.enumerate()# 返回当前运行线程的列表
t1=threading.Thread(target=dance) #创建两个子线程,参数传递为函数名
t1.setDaemon(True) # 设置守护进程,守护进程:主线程结束时自动退出子线程 。
t1.start() # 启动子线程
t1.join() # 等待进程结束exit()`# 主线程退出 , t1子线程设置了守护进程,会自动退出 。其python线程函数他子线程会继续执行 。
python 线程池的使用最近在做一个爬虫相关的项目 , 单线程的整站爬虫,耗时真的不是一般的巨大,运行一次也是心累,,,所以 , 要想实现整站爬虫,多线程是不可避免的,那么python多线程又应该怎样实现呢?这里主要要几个问题(关于python多线程的GIL问题就不再说了 , 网上太多了) 。
一、 既然多线程可以缩短程序运行时间,那么,是不是线程数量越多越好呢?
显然 , 并不是,每一个线程的从生成到消亡也是需要时间和资源的,太多的线程会占用过多的系统资源(内存开销,cpu开销),而且生成太多的线程时间也是可观的,很可能会得不偿失 , 这里给出一个最佳线程数量的计算方式:
最佳线程数的获?。?
1、通过用户慢慢递增来进行性能压测,观察QPS(即每秒的响应请求数,也即是最大吞吐能力 。),响应时间
2、根据公式计算:服务器端最佳线程数量=((线程等待时间 线程cpu时间)/线程cpu时间) * cpu数量
3、单用户压测,查看CPU的消耗,然后直接乘以百分比,再进行压测 , 一般这个值的附近应该就是最佳线程数量 。
二、为什么要使用线程池?
对于任务数量不断增加的程序 , 每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫 , 假设初始只有一个链接a,那么,这个时候只启动一个线程,运行之后 , 得到这个链接对应页面上的b,c,d,,,等等新的链接 , 作为新任务,这个时候 , 就要为这些新的链接生成新的线程,线程数量暴涨 。在之后的运行中 , 线程数量还会不停的增加,完全无法控制 。所以 , 对于任务数量不端增加的程序,固定线程数量的线程池是必要的 。
三、如何使用线程池
过去使用threadpool模块,现在一般使用concurrent.futures模块,这个模块是python3中自带的模块,但是,python2.7以上版本也可以安装使用,具体使用方式如下:
注意到:
concurrent.futures.ThreadPoolExecutor , 在提交任务的时候,有两种方式,一种是submit()函数 , 另一种是map()函数 , 两者的主要区别在于:
一篇文章带你深度解析Python线程和进程使用Python中的线程模块,能够同时运行程序的不同部分,并简化设计 。如果你已经入门Python,并且想用线程来提升程序运行速度的话,希望这篇教程会对你有所帮助 。
线程与进程
什么是进程
进程是系统进行资源分配和调度的一个独立单位 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位 。每个进程都有自己的独立内存空间 , 不同进程通过进程间通信来通信 。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全 。
什么是线程
CPU调度和分派的基本单位 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源 。线程间通信主要通过共享内存 , 上下文切换很快 , 资源开销较少,但相比进程不够稳定容易丢失数据 。
进程与线程的关系图
线程与进程的区别:
进程
现实生活中,有很多的场景中的事情是同时进行的,比如开车的时候 手和脚共同来驾驶 汽车 , 比如唱歌跳舞也是同时进行的,再比如边吃饭边打电话;试想如果我们吃饭的时候有一个领导来电,我们肯定是立刻就接听了 。但是如果你吃完饭再接听或者回电话,很可能会被开除 。
注意:
多任务的概念
什么叫多任务呢?简单地说,就是操作系统可以同时运行多个任务 。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务 , 至少同时有3个任务正在运行 。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已 。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务 。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2 , 任务2执行0.01秒,再切换到任务3,执行0.01秒 , 这样反复执行下去 。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样 。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量 , 所以,操作系统也会自动把很多任务轮流调度到每个核心上执行 。其实就是CPU执行速度太快啦!以至于我们感受不到在轮流调度 。
并行与并发
并行(Parallelism)
并行:指两个或两个以上事件(或线程)在同一时刻发生,是真正意义上的不同事件或线程在同一时刻,在不同CPU资源呢上(多核) , 同时执行 。
特点
并发(Concurrency)
指一个物理CPU(也可以多个物理CPU) 在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率 。
特点
multiprocess.Process模块
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建 。
语法:Process([group [, target [, name [, args [, kwargs]]]]])
由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) 。
注意:1. 必须使用关键字方式来指定参数;2. args指定的为传给target函数的位置参数,是一个元祖形式,必须有逗号 。
参数介绍:
group:参数未使用,默认值为None 。
target:表示调用对象,即子进程要执行的任务 。
args:表示调用的位置参数元祖 。
kwargs:表示调用对象的字典 。如kwargs = {'name':Jack, 'age':18} 。
name:子进程名称 。
代码:
除了上面这些开启进程的方法之外,还有一种以继承Process的方式开启进程的方式:
通过上面的研究,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理 , 他们之间的运行没有顺序 , 一旦开启也不受我们控制 。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题 。
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题,我们可以考虑加锁,我们以模拟抢票为例,来看看数据安全的重要性 。
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改 。加锁牺牲了速度 , 但是却保证了数据的安全 。
因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题 。
mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道 。队列和管道都是将数据存放于内存中 队列又是基于(管道 锁)实现的 , 可以让我们从复杂的锁问题中解脱出来 , 我们应该尽量避免使用共享数据 , 尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性(后续扩展该内容) 。
线程
Python的threading模块
Python 供了几个用于多线程编程的模块,包括 thread, threading 和 Queue 等 。thread 和 threading 模块允许程序员创建和管理线程 。thread 模块 供了基本的线程和锁的支持 , 而 threading 供了更高级别,功能更强的线程管理的功能 。Queue 模块允许用户创建一个可以用于多个线程之间 共享数据的队列数据结构 。
python创建和执行线程
创建线程代码
1. 创建方法一:
2. 创建方法二:
进程和线程都是实现多任务的一种方式,例如:在同一台计算机上能同时运行多个QQ(进程),一个QQ可以打开多个聊天窗口(线程) 。资源共享:进程不能共享资源,而线程共享所在进程的地址空间和其他资源,同时 , 线程有自己的栈和栈指针 。所以在一个进程内的所有线程共享全局变量,但多线程对全局变量的更改会导致变量值得混乱 。
代码演示:
得到的结果是:
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念 。就好比C是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码 。同样一段代码可以通过CPython,PyPy , Psyco等不同的Python执行环境来执行(其中的JPython就没有GIL) 。
那么CPython实现中的GIL又是什么呢?GIL全称Global Interpreter Lock为了避免误导,我们还是来看一下官方给出的解释:
主要意思为:
因此,解释器实际上被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行 。在多线程环境中,Python 虚拟机按以下方式执行:
由于GIL的存在,Python的多线程不能称之为严格的多线程 。因为多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行 。
由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行 , 既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?
由于以前的电脑基本都是单核CPU,多线程和单线程几乎看不出差别 , 可是由于计算机的迅速发展,现在的电脑几乎都是多核CPU了 , 最少也是两个核心数的,这时差别就出来了:通过之前的案例我们已经知道 , 即使在多核CPU中,多线程同一时刻也只有一个线程在运行 , 这样不仅不能利用多核CPU的优势,反而由于每个线程在多个CPU上是交替执行的 , 导致在不同CPU上切换时造成资源的浪费,反而会更慢 。即原因是一个进程只存在一把gil锁 , 当在执行多个线程时,内部会争抢gil锁 , 这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源 。
但是在使用多线程抓取网页内容时 , 遇到IO阻塞时,正在执行的线程会暂时释放GIL锁,这时其它线程会利用这个空隙时间,执行自己的代码,因此多线程抓取比单线程抓取性能要好 , 所以我们还是要使用多线程的 。
GIL对多线程Python程序的影响
程序的性能受到计算密集型(CPU)的程序限制和I/O密集型的程序限制影响,那什么是计算密集型和I/O密集型程序呢?
计算密集型:要进行大量的数值计算,例如进行上亿的数字计算、计算圆周率、对视频进行高清解码等等 。这种计算密集型任务虽然也可以用多任务完成,但是花费的主要时间在任务切换的时间,此时CPU执行任务的效率比较低 。
IO密集型:涉及到网络请求(time.sleep())、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少 , 任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度) 。对于IO密集型任务 , 任务越多,CPU效率越高 , 但也有一个限度 。
当然为了避免GIL对我们程序产生影响,我们也可以使用 , 线程锁 。
LockRLock
常用的资源共享锁机制:有Lock、RLock、Semphore、Condition等,简单给大家分享下Lock和RLock 。
Lock
特点就是执行速度慢,但是保证了数据的安全性
RLock
使用锁代码操作不当就会产生死锁的情况 。
什么是死锁
死锁:当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下 , 就会发生AB两个线程由于互相持有对方需要的锁 , 而发生的阻塞现象,我们称为死锁 。即死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用 , 这些进程都将无法向前推进 。
所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立 , 如何确定资源的合理分配算法,避免进程永久占据系统资源 。
死锁代码
python线程间通信
如果各个线程之间各干各的,确实不需要通信,这样的代码也十分的简单 。但这一般是不可能的,至少线程要和主线程进行通信,不然计算结果等内容无法取回 。而实际情况中要复杂的多,多个线程间需要交换数据,才能得到正确的执行结果 。
python中Queue是消息队列,提供线程间通信机制,python3中重名为为queue , queue模块块下提供了几个阻塞队列,这些队列主要用于实现线程通信 。
在 queue 模块下主要提供了三个类 , 分别代表三种队列,它们的主要区别就在于进队列、出队列的不同 。
简单代码演示
此时代码会阻塞,因为queue中内容已满,此时可以在第四个queue.put('苹果')后面添加timeout,则成为 queue.put('苹果' , timeout=1)如果等待1秒钟仍然是满的就会抛出异常,可以捕获异常 。
同理如果队列是空的,无法获取到内容默认也会阻塞,如果不阻塞可以使用queue.get_nowait() 。
在掌握了 Queue 阻塞队列的特性之后,在下面程序中就可以利用 Queue 来实现线程通信了 。
下面演示一个生产者和一个消费者,当然都可以多个
使用queue模块,可在线程间进行通信,并保证了线程安全 。
协程
协程,又称微线程 , 纤程 。英文名Coroutine 。
协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源) 。为啥说它是一个执行单元,因为它自带CPU上下文 。这样只要在合适的时机,我们可以把一个协程 切换到另一个协程 。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的 。
通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定 。
在实现多任务时,线程切换从系统层面远不止保存和恢复 CPU上下文这么简单 。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作 。所以线程的切换非常耗性能 。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住 。
greenlet与gevent
为了更好使用协程来完成多任务 , 除了使用原生的yield完成模拟协程的工作,其实python还有的greenlet模块和gevent模块,使实现协程变的更加简单高效 。
greenlet虽说实现了协程,但需要我们手工切换,太麻烦了,gevent是比greenlet更强大的并且能够自动切换任务的模块 。
其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时 , 比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行 。
模拟耗时操作:
如果有耗时操作也可以换成,gevent中自己实现的模块,这时候就需要打补丁了 。
使用协程完成一个简单的二手房信息的爬虫代码吧!
以下文章来源于Python专栏 ,作者宋宋
文章链接:
【python线程函数 python开启多线程】python线程函数的介绍就聊到这里吧,感谢你花时间阅读本站内容 , 更多关于python开启多线程、python线程函数的信息别忘了在本站进行查找喔 。
推荐阅读
- 流量高的直播卖货视频软件,流量高的直播卖货视频软件推荐
- c语言比较字符串是否完全相同,c语言怎么比较字符串
- 个人建论坛放国外服务器,如何搭建论坛网站
- 如何做网络推广网站设计,网站如何进行网络推广
- vb.net除法取商 vba除法取余数
- Android系统网络认证,Android系统登录到网络
- 乐高竞速游戏,乐高竞速冠军下载
- shell里调用Python程序,shell中调用python
- linux下安装7z命令 linux安装7za