1 默认启动主线程
一般的,程序默认执行只在一个线程,这个线程称为主线程,例子演示如下:
导入线程相关的模块 :
threading的类方法 返回当前线程:
所以,验证了程序默认是在中执行。
获得这个线程的名字,其他常用方法,获得线程,判断线程是否存活等。
以上这些仅是介绍多线程的,因为到目前为止,我们有且仅有一个"干活"的主线程
2 创建线程
创建一个线程:
创建一个名称为的线程:
创建线程的目的是告诉它帮助我们做些什么,做些什么通过参数传入,参数类型为,函数就是可调用的:
线程已经全副武装,但是我们得按下发射按钮,启动start(),它才开始真正起飞。
打印结果如下,其中指定函数需要的参数i,类型为元祖。
至此,多线程相关的核心知识点,已经总结完毕。但是,仅仅知道这些,还不够!光纸上谈兵,当然远远不够。
接下来,聊聊应用多线程编程,最本质的一些东西。
3 交替获得CPU时间片
为了更好解释,假定计算机是单核的,尽管对于,这个假定有些多余。
开辟3个线程,装到中:
启动3个线程:
打印结果如下,,,三个线程,根据操作系统的调度算法,轮询获得CPU时间片,注意观察,线程可能被连续调度,从而获得时间片。
4 多线程抢夺同一个变量
多线程编程,存在抢夺同一个变量的问题。
比如下面例子,创建的10个线程同时竞争全局变量:
执行结果:
结果一切正常,每个线程执行一次,把的值加1,最后 变为10,一切正常。
运行上面代码十几遍,一切也都正常。
所以,我们能下结论:这段代码是线程安全的吗?
NO!
多线程中,只要存在同时读取和修改一个全局变量的情况,如果不采取其他措施,就一定不是线程安全的。
尽管,有时,某些情况的资源竞争,暴露出问题的概率:
本例中,如果线程0 在修改a后,其他某些线程还是get到的是没有修改前的值,就会暴露问题。
但是在本例中,这种修改操作,花费的时间太短了,短到我们无法想象。所以,线程间轮询执行时,都能get到最新的a值。所以,暴露问题的概率就变得微乎其微。
5 代码稍作改动,叫问题暴露出来
只要弄明白问题暴露的原因,叫问题出现还是不困难的。
想象数据库的写入操作,一般需要耗费我们可以感知的时间。
为了模拟这个写入动作,简化期间,我们只需要延长修改变量的时间,问题很容易就会还原出来。
重新运行代码,只需一次,问题立马完全暴露,结果如下:
看到,10个线程全部运行后,的值只相当于一个线程执行的结果。
下面分析,为什么会出现上面的结果:
这是一个很有说服力的例子,因为在修改a前,有0.2秒的休眠时间,某个线程延时后,CPU立即分配计算资源给其他线程。直到分配给所有线程后,根据结果反映出,0.2秒的休眠时长还没耗尽,这样每个线程get到的a值都是0,所以才出现上面的结果。
以上最核心的三行代码:
6 加上一把锁,避免以上情况出现
知道问题出现的原因后,要想修复问题,也没那么复杂。
通过python中提供的锁机制,某段代码只能单线程执行时,上锁,其他线程等待,直到释放锁后,其他线程再争锁,执行代码,释放锁,重复以上。
创建一把锁:
通过 获得锁,通过释放锁,它们之间的这些代码,只能单线程执行。
执行结果如下:
一起正常,其实这已经是单线程顺序执行了,就本例子而言,已经失去多线程的价值,并且还带来了因为线程创建开销,浪费时间的副作用。
程序中只有一把锁,通过 还能确保不发生死锁。但是,当程序中启用多把锁,还是很容易发生死锁。
注意使用场合,避免死锁,是我们在使用多线程开发时需要注意的一些问题。
7 总结
Python的多线程模型还有一些更深入的问题,在此不再展开,后续再讨论。
希望透过这篇文章,帮助你对多线程模型编程本质有些更清晰的认识。
领取专属 10元无门槛券
私享最新 技术干货