python threading2种调用方式实例

1.认识GIL:

说到GIL一直是代码专家们一直以来想要解决的问题,也是被许多程序员诟病的,下面带领大家看下官方threading模块document中如何去描述对于GIL这个全局解释器锁的:https://docs.python.org/3/library/threading.html

全局解释器锁

所使用的机制的CPython解释器来确保只有一个线程执行的Python 字节码在一个时间。通过使对象模型(包括关键的内置类型,例如dict)隐式安全地防止并发访问,从而简化了CPython的实现锁定整个解释器可以使解释器更容易进行多线程处理,但会牺牲多处理器机器提供的许多并行性。

但是,某些扩展模块(标准的或第三方的)被设计为在执行诸如压缩或散列之类的计算密集型任务时释放GIL。另外,在执行I / O时,始终释放GIL。

过去创建“自由线程”解释器(一种以更精细的粒度锁定共享数据的解释器)的努力并未成功,因为在常见的单处理器情况下性能会受到影响。相信克服该性能问题将使实施更加复杂,因此维护成本更高。

在这里专门对全局解释器锁的概念引入时说了这么一段话,第一指明了GIL造成了python解释器一次只有一个线程可以获取解释器锁,也就是解释器本身是自带锁的,谁先拿到谁就可以抢占到cpu的资源,

从而导致了python无论一个线程怎么去跑,都只能最多跑满一个cpu核心,就造成了多核cpu资源的无法利用的带来的资源浪费,但是这是否就意味着python一无是处呢,答案肯定No!,虽然GIL对于cpu密集的支持

不是那么的友好,但是,对于IO密集的支持恰恰是其他语言所无法比拟的,就拿asyncio这这个协程库来讲:https://docs.python.org/3/library/asyncio.html

1.第一部分认识多线程创建以及调用,上一节我讲解了源码关于多线程实现方式有两种:第一种通过Thread类传入可调用target对象,第二种继承Thread类并重写run方法:

下面就以target进行举例:

 

 可以看到线程的资源抢占效果;证明GIL锁的的确真实存在的;

先在我们如果更换t.join()到t1.start()的后面会发现,线程每次不管怎么运行都是先执行完threading1线程,再去执行threading2线程,而这完全得益于joIn()函数:

我们来看源码是怎么解释join()的:

等待直到线程终止。这将阻塞调用线程,直到join()被调用方法的线程终止(正常或通过未处理的异常终止),或者直到发生可选的超时。

 讲完join():

我们再来认识一个有趣的参数daemon->守护线程:

先看源码:

 daemon用'来指示线程是否是守护线程,必须在线程start()方法调用之前设置,否则会引发RunTimeError,当不存在活动的非守护线程时将退出python程序

演示daemon:

正常默认daemon为False:

 可以看到主线程运行结束后退出后子线程继续运行并输出了内容,

但是现在如果有个需求要求我们实现主线程退出后必须kill掉子线程那么,如何实现这个需求,这就用到了daemon,我们只需要设置为True:

 主线程运行结束后并没有等待子线程

运行完毕就kill掉了子线程没有输出finsh打印就已经结束了子线程了

# 多线程实现方式二,继承Thread 类,重写run方法:

 这是一种很基本的写法。实际开发过程我们会涉及到类的调用以及类的方法引用写法会发生改变:

这里实现了在线程外部定义一个类,其中类的方法test作为一个可调用对象传给了继承了Thread并重写run方法的MyCallable类,这里留一个思考题,对于以上重写run你会发现我们的run是无法实现结果回调的

这就意味着,我们需要根据实际场景使用不同的模块处理不同的需求,可以给大家一个小提示可以使用concurrent.futures或者使用  queue.Queue()来实现结果回调,因为在queue源码中,它是基于deque来实现的而deque是线程安全的也就意味queue也是线程安全的,有兴趣的可以自己去看下

j结语:

 

原文地址:https://www.cnblogs.com/SunshineKimi/p/12310495.html