目录
成都创新互联凭借专业的设计团队扎实的技术支持、优质高效的服务意识和丰厚的资源优势,提供专业的网站策划、网站设计制作、成都网站制作、网站优化、软件开发、网站改版等服务,在成都十余年的网站建设设计经验,为成都近1000家中小型企业策划设计了网站。什么是线程?
并发与并行
什么是GIL?
为什么要使用多线程?
多线程实例
守护线程
多线程共享全局变量
互斥锁Lock
RLock递归锁
信号量
python多线程是一个非常重要的知识点,接下来对该部分内容进行梳理。
什么是线程?线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
并发与并行并发是快速交替轮换执行(线程看起来是同时在运行,实际上是一个线程遇到耗时的IO等操作时将此线程挂起释放GIL全局解释器锁转而执行其他线程的过程,这个过程轮换的非常快,所以宏观上看起来就像是多个线程同时运行),并行是同一时间同时运行(进程可以做到并行运行,充分利用多核cpu的优势)
什么是GIL?GIL全称是全局解释器锁,为了解决多线程之间数据完整性和状态同步而产生的一把锁。它保证了在同一时刻只有一个线程执行,当其他线程需要运行时必须先拿到这把锁才能执行,这就保证了数据的完整性和状态的同步。关于GIL的更多知识可以参考这篇博客。
为什么要使用多线程?线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄 和其他进程应有的状态。
因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率。线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能高得要多。
操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率。python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。
多线程实例普通创建方式:
打印结果如下:
通过打印的顺序可以发现,多线程是异步非阻塞的(首先打印task2,再打印task3,最后等待task1执行结束后再结束主线程),主线程的结束并没有强制结束子线程,是等待子线程结束后才结束主线程。 当我们需要某个线程执行完成再往下面执行,可以手动添加join()方法阻塞主线程,这样主线程会等待join的线程执行完成再往下执行,下面守护线程会讲到join()方法。
自定义线程------继承threading.Thread类实现自定义线程,其本质是重构Thread类的run方法:
打印结果:
t.start()方法在执行的时候会自动调用run()方法,所以在自定义线程的时候只需要重写__init__方法和run()方法即可,__init__方法负责接收参数,这些参数最后会传入run()方法中,根据需要实现自定义功能。
守护线程这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主线程结束后,子线程也会随之结束,所以当主线程结束后,整个程序就退出了。
把线程t1设置成守护线程,意味着不管t1线程有没有运行完成,只要主线程运行结束了,t1线程也会被迫结束,看一下打印结果:
t2、t3线程运行结束后主线程也随之运行到结尾,此时t1线程还处于sleep状态,但是由于设置成守护线程了,此时主线程关闭,t1线程也被迫结束,整个程序退出。
但是在这种情况下,可以使用join()方法手动阻塞当前线程,看如下代码:
使用t1.join()阻塞当前进程,当主线程运行到t1.join()的时候会被阻塞在此处,直到t1线程运行结束,所以就算t1线程设置成了守护线程,主线程也得等到t1线程运行完成后才能往下执行,然后结束主线程,退出程序。打印结果如下:
多线程共享全局变量一个进程中的多个线程资源是共享的,看下面这个例子:
打印结果如下:
从打印结果可以看出来,全局的变量在work1中做了加法,在work2中打印num的值时显示的是加法运行之后的值,说明两个work操作的是同一个变量。从而印证了线程之间的资源是共享的。
虽然有了全局解释器锁GIL,但是它不能保证线程数据的安全性,当多个线程操作一个变量的时候,举个例子有个变量a初始值是1,要对其进行a+=1这个操作可以细分成两步,a+1 和a=a+1,比如两个线程对a进行操作,当线程1拿到调度权的时候,对a进行a+1操作,此时线程1交出了调度权,线程2获取到的执行的机会,同样对a进行操作,此时的a仍然是1,因为线程1只是做了加法运算还没有最终把a+1的值赋值给变量a的时候线程2便拿到了调度权,所以线程2做了一次完整的a+=1操作,a的值变成了2,这个时候线程2交出调度权,线程1拿到调度权继续运行最后的赋值操作,a的值为2。此时发现a做了两次加法操作,但是最终的值是2,这便产生了脏数据。为了解决这个问题引入线程锁。
互斥锁Lock为了解决线程数据不安全性,引入互斥锁概念,看如下例子:
在修改变量n的时候,先lock.acquire()给线程上锁,这样其他的线程便拿不到这个资源,当完成对数据的操作后,lock.release()解锁,其他线程便可以操作变量n,这样便解决了原子性问题。
打印结果:使用10个线程对数字10000000进行减法操作,如期得到最后的结果1。使用互斥锁解决了线程数据不安全问题,避免了脏数据的产生。
RLock递归锁还有一种锁RLock递归锁,用法与lock锁一样,不同的是它支持嵌套,在多个锁没有释放的时候,一般会使用RLock类。
信号量用一句话说,信号量就是为了控制线程的并发数。
例如餐桌有三个位置,但是有10个人同时就餐,这时候就需要信号量参与保证餐桌上只有三个人,也就是线程的并发数始终是3。
观察打印结果发现,只有三个线程在并发运行,其他线程需要等待线程释放之后才参与进来。线程池也是为了控制线程的并发数。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧