什么是线程?
要弄清楚线程的定义,往往就要和进程相互比较,从比较中才能更准确地明白一个东西的定义。首先有个数量关系是这样的,进程是由多线程组成的,每个进程起码都有一个线程。
然后重点是有人说:
进程和线程简单而基本靠谱的定义如下:
1. 进程:程序的一次执行
2. 线程:CPU的基本调度单位
给一句话的定义,其实没有什么意思。但是,做为最根本的落脚点,放着也是极好的。
阮大神的这篇blog写的很通俗,但是不清楚,不深入,但是文章加评论却是相当有意思。
在我眼中的定义的理解
- 进程:程序的一次执行?
所谓程序的一次执行,就是指有自己的内存空间。就程序代码而言,就是当有全局变量,局部变量的时候,两者是不会互相影响的,每个进程都有自己独立的内存空间。其实,这些都是操作系统在影响着,你看,每个进程都有自己的内存空间,那么每个进程自己使用自己的东西的时候,就不会拿错东西,这样一来,我的程序员师叔们编程序的时候就不会弄错了,不用考虑这个变量是属于哪个进程这种问题了。(也不知道当时他们是不是这么想的:( )(那么问题来了,操作系统层是这样的,那么硬件层呢?CPU层到底是怎么实现的呢?) - 线程:CPU的基本调度单位?
这个问题还没有考虑清楚,下次看看书,再来补充。有一个肤浅的一点就是在实际编程的过程中,全局变量是可以被所有线程所访问到的。(太肤浅了。。)
Python的多线程实现(Python3实现)
Python的标准库提供了两个模块:_thread
和threading
,_thread
是低级模块,threading
是高级模块,对_thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块。
下面是廖大官网的例子,感觉非常不错,搬过来了哈。
import time, threading
# 假定这是你的银行存款:
balance = 0
def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
如果是初次在学校中开始接触到线程的话,其实有一点是十分困惑的。(反正我是非常困惑的)就是有一种欲望是想看看threading这个模块的Thread函数的实现是什么?或者说就想弄清楚,弄明白run_thread,这个函数命传递进去了之后,程序代码到底做了什么?为什么要t1.start(),t1.join()?就很奇怪,一个好好的函数,为什么要target传一个函数名,args参数传函数的参数,一起传不好吗?
然后当你真的点击去看实现的时候,就会一脸懵逼。那么如果控制这种想法呢?反正我就明白一点就是,学习的过程,并不是你前面的都明白的,下一个点也能明白的,有时候明白这种明白,知道这个点不是现阶段的问题这个事实是非常好的。
API中的几个注意点:
- target传递的是一个函数名,而不是函数调用,如果你看过Python的线程的API的话,也是要注意这一点的。
- balance这种全局变量是可以被所有线程访问到的。然后就有了锁的概念,那不是另一个世界了。
- 线程的start(),join()函数的作用,线程的调度是由操作系统决定的,没有先后顺序,高级语言的有些一条语句其实可以拆分之类的概念,这里是不会提的,不然显得没重点,可以看一下廖大的教程,很详细。
- 下面是其中一种运行结果。
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
结果 balance = -8
- 线程中锁。
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
还真是,这个API比JAVA中的要简单很多。
- 还有一种是一个python的类继承的thread的API还没有写,下次补充。显示这个继承的API不好,类中参杂了很多不是这个类的东西。
GIL
GIL是全局解释器锁,可以看这个东西的英文比较能懂。因为这个东西的存在,Python的多线程,不论怎么样都只能充分使用CPU的一个核心。有下面几点要注意:
- Python是一种语言,语言也可以理解为一种语法标准,在这个标准里面是没有GIL的,但是这个语言的实现,官方的解释去CPython中是用了GIL来实现的,但是比如JPython是没有这个概念的。(那么问题来了,为什么还是有那么多人使用CPython?)
- Python3中的GIL也是一样存在的,只是Python3中对GIL的一个多线程比单线程还要慢的这一个问题进行了修复,没有从根本上解决这个问题。
- 想充分利用多核CPU的话,可以使用多进程。(那么为什么多进程可以避开GIL而充分使用CPU呢?)
最后之前
为什么多线程或者说多进程会比单进程快呢?对于IO密集型,计算密集型?可以从CPU和操作系统的角度来谈谈这个问题吗?计算机有几个IO方式呢,比如DMA是什么,有什么不同?计算机IO的时候,是需要CPU的运行吗?需要吗?不需要吗?不需要的话,又是谁把磁盘中的数据读到内存中的呢?除了CPU还有谁有这个能力呢?线程和进程在CPU这个层面到底是什么瓜葛呢?待我好好看书,来回答这个问题。
最后
写给未来的自己,也写给读到最后的你,能力有限,如有错误,请交流谈论及指正,如果有帮助,请评论或者点击喜欢,谢谢。
参考:
进程与线程的一个简单解释
廖雪峰的官方教程之多线程
Python 之父谈 Python
Python 3.2与更好的GIL