网站开发成本报表,推广平台,网页访问禁止怎么恢复,网站建设内容方向Python学习之路-多任务:线程
简介
什么叫“多任务”呢#xff1f;简单地说#xff0c;就是操作系统可以同时运行多个任务。操作系统轮流让各个任务交替执行#xff0c;表面上看#xff0c;每个任务都是交替执行的#xff0c;但是#xff0c;由于CPU的执行速度实在是太快…Python学习之路-多任务:线程
简介
什么叫“多任务”呢简单地说就是操作系统可以同时运行多个任务。操作系统轮流让各个任务交替执行表面上看每个任务都是交替执行的但是由于CPU的执行速度实在是太快了我们感觉就像所有任务都在同时执行一样。真正的并行执行多任务只能在多核CPU上实现但是由于任务数量远远多于CPU的核心数量所以操作系统也会自动把很多任务轮流调度到每个核心上执行。
{{ admonition note “拓展” true }}
并发指的是任务数多余cpu核数通过操作系统的各种任务调度算法实现用多个任务“一起”执行实际上总有一些任务不在执行因为切换任务的速度相当快看上去一起执行而已。 并行指的是任务数小于等于cpu核数即任务真的是一起执行的。
{{ /admonition }}
线程
简介
线程是程序的最小执行流单元是程序中一个单一的顺序控制流程
threading模块
python的thread模块是比较底层的模块python的threading模块是对thread做了一些包装的可以更加方便的被使用
语法
import threading
t threading.Thread(target函数名)
t.start()当调用start()时才会真正的创建线程并且开始执行。主线程会等待所有的子线程结束后才结束
查看线程数量
可以通过len(threading.enumerate())查看当前线程数量
注意点
线程执行代码的封装
通过使用threading模块能完成多任务的程序开发为了让每个线程的封装性更完美所以使用threading模块时往往会定义一个新的子类class只要继承threading.Thread就可以了然后重写run方法。
{{ admonition note “拓展” true }}
python的threading.Thread类有一个run方法用于定义线程的功能函数可以在自己的线程类中覆盖该方法。而创建自己的线程实例后通过Thread类的start方法可以启动该线程交给python虚拟机进行调度当该线程获得执行的机会时就会调用run方法执行线程。
{{ /admonition }}
线程的执行顺序
多线程程序的执行顺序是不确定的。当执行到sleep语句时线程将被阻塞Blocked到sleep结束后线程进入就绪Runnable状态等待调度。而线程调度将自行选择一个线程执行。上面的代码中只能保证每个线程都运行完整个run函数但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
{{ admonition note “拓展” true }}
每个线程默认有一个名字尽管上面的例子中没有指定线程对象的name但是python会自动为线程指定一个名字。当线程的run()方法结束时该线程完成。无法控制线程调度程序但可以通过别的方式来影响线程调度的方式。
{{ /admonition }}
共享全局变量
在一个进程内的所有线程共享全局变量很方便在多个线程间共享数据缺点就是线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱即线程非安全。
多线程开发可能遇到的问题
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算t1和t2都各对g_num加10次g_num的最终的结果应该为20。
但是由于是多线程同时操作有可能出现下面情况
在g_num0时t1取得g_num0。此时系统把t1调度为”sleeping”状态把t2转换为”running”状态t2也获得g_num0然后t2对得到的值进行加1并赋给g_num使得g_num1然后系统又把t2调度为”sleeping”把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。这样导致虽然t1和t2都对g_num加1但结果仍然是g_num1
如果多个线程同时对同一个全局变量操作会出现资源竞争问题从而数据结果会不正确
互斥锁
简介
当多个线程几乎同时修改某一个共享数据的时候需要进行同步控制线程同步能够保证多个线程安全访问竞争资源最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态锁定/非锁定
某个线程要更改共享数据时先将其锁定此时资源的状态为“锁定”其他线程不能更改直到该线程释放资源将资源的状态变成“非锁定”其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类可以方便的处理锁定
# 创建锁
mutex threading.Lock()# 锁定
mutex.acquire()# 释放
mutex.release(){{ admonition warning “注意” true }}
如果这个锁之前是没有上锁的那么acquire不会堵塞如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁那么此时acquire会堵塞直到这个锁被解锁为止
{{ /admonition }}
上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁该线程就会变为“blocked”状态称为“阻塞”直到拥有锁的线程调用锁的release()方法释放锁之后锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁并使得该线程进入运行running状态。
优缺点
优点确保了某段关键代码只能由一个线程从头到尾完整地执行
缺点阻止了多线程并发执行包含锁的某段代码实际上只能以单线程模式执行效率就大大地下降了由于可以存在多个锁不同的线程持有不同的锁并试图获取对方持有的锁时可能会造成死锁
死锁
在线程间共享多个资源的时候如果两个线程分别占有一部分资源并且同时等待对方的资源就会造成死锁。尽管死锁很少发生但一旦发生就会造成应用的停止响应。
如何避免死锁
程序设计时要尽量避免添加超时时间等
GIL
GIL为全局解释器锁。每个线程在执行的过程都需要先获取GIL保证同一时刻只有一个线程可以执行代码。
Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器)难以移除GIL。在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python 3.x使用计时器执行时间达到阈值后当前线程释放GIL或Python 2.xtickets计数达到100。Python使用多进程是可以利用多核的CPU资源的。多线程爬取比单线程性能有提升因为遇到IO阻塞会自动释放GIL锁