线程和进程
计算机,用于计算的机器。计算机的核心是CPU,在现在多核心的电脑很常见了。为了充分利用cpu核心做计算任务,程序实现了多线程模型。通过多线程实现多任务的并行执行。
现在的操作系统多是多任务操作系统。每个应用程序都有一个自己的进程。操作系统会为这些进程分配一些执行资源,例如内存空间等。在进程中,又可以创建一些线程,他们共享这些内存空间,并由操作系统调用,以便并行计算。
线程状态
创建线程之后,线程并不是始终保持一个状态。其状态大概如下:
New
创建。Runnable
就绪。等待调度Running
运行。Blocked
阻塞。阻塞可能在Wait
Locked
Sleeping
Dead
消亡
这些状态之间是可以相互转换的,一图胜千颜色:
(图片引用 内心求法博客)
线程中执行到阻塞,可能有3种情况:
- 同步:线程中获取同步锁,但是资源已经被其他线程锁定时,进入Locked状态,直到该资源可获取(获取的顺序由Lock队列控制)
- 睡眠:线程运行sleep()或join()方法后,线程进入Sleeping状态。区别在于sleep等待固定的时间,而join是等待子线程执行完。当然join也可以指定一个“超时时间”。从语义上来说,如果两个线程a,b, 在a中调用b.join(),相当于合并(join)成一个线程。最常见的情况是在主线程中join所有的子线程。
- 等待:线程中执行wait()方法后,线程进入Waiting状态,等待其他线程的通知(notify)。
线程类型
线程有着不同的状态,也有不同的类型。大致可分为:
- 主线程
- 子线程
- 守护线程(后台线程)
- 前台线程
Python线程与GIL
相比进程,线程更加轻量,可以实现并发。可是在python的世界里,对于线程,就不得不说一句GIL(全局解释器锁)。GIL的存在让python的多线程多少有点鸡肋了。Cpython的线程是操作系统原生的线程在解释器解释执行任何Python代码时,都需要先获得这把锁才行,在遇到 I/O 操作时会释放这把锁。因为python的进程做为一个整体,解释器进程内只有一个线程在执行,其它的线程都处于等待状态等着GIL的释放。
关于GIL可以有更多的趣事,一时半会都说不完。总之python想用多线程并发,效果可能还不如单线程(线程切换耗时间)。想要利用多核,可以考虑使用多进程。
线程的创建
虽然python线程比较鸡肋,可是也并发一无是处。多了解还是有理由对并发模型的理解。
Python提供两个模块进行多线程的操作,分别是thread
和threading
,前者是比较低级的模块,用于更底层的操作,一般应有级别的开发不常用。后者则封装了更多高级的接口,类似java的多线程风格,提供run
方法和start
调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import time import threading class MyThread(threading.Thread): def run(self): for i in range(5): print 'thread {}, @number: {}'.format(self.name, i) time.sleep(1) def main(): print "Start main threading" # 创建三个线程 threads = [MyThread() for i in range(3)] # 启动三个线程 for t in threads: t.start() print "End Main threading" if __name__ == '__main__': main() |
输入如下:(不同的环境不一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Start main threading thread Thread-1, @number: 0 thread Thread-2, @number: 0 thread Thread-3, @number: 0 End Main threading thread Thread-1, @number: 1 thread Thread-3, @number: 1 thread Thread-2, @number: 1 thread Thread-3, @number: 2 thread Thread-1, @number: 2 thread Thread-2, @number: 2 thread Thread-2, @number: 3 thread Thread-1, @number: 3 thread Thread-3, @number: 3 |
每个线程都依次打印 0 – 3 三个数字,可是从输出的结果观察,线程并不是顺序的执行,而是三个线程之间相互交替执行。此外,我们的主线程执行结束,将会打印 End Main threading
。从输出结果可以知道,主线程结束后,新建的线程还在运行。
线程合并(join方法)
上述的例子中,主线程结束了,子线程还在运行。如果需要主线程等待子线程执行完毕再退出,可是使用线程的join
方法。join方法官网文档大概是
join(timeout)
方法将会等待直到线程结束。这将阻塞正在调用的线程,直到被调用join()方法的线程结束。
主线程或者某个函数如果创建了子线程,只要调用了子线程的join方法,那么主线程就会被子线程所阻塞,直到子线程执行完毕再轮到主线程执行。其结果就是所有子线程执行完毕,才打印 End Main threading
。只需要修改上面的main
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def main(): print "Start main threading" threads = [MyThread() for i in range(3)] for t in threads: t.start() # 一次让新创建的线程执行 join for t in threads: t.join() print "End Main threading" |
输入如下:
1 2 3 4 5 6 7 8 9 10 |
Start main threading 过多线程实现多任务的并行执行。
现在的操作系统多是多任务操作系统。每个应用程序都有一个自己的进程。操作系统会为这些进程分配一些执行资源,例如内存空间等。在进程中,又可以创建一些线程,他们共享这些内存空间,并由操作系统调用,以便并行计算。 线程状态创建线程之后,线程并不是始终保持一个状态。其状态大概如下:
这些状态之间是可以相互转换的,一图胜千颜色: threading_state
(图片引用 内心求法博客) 线程中执行到阻塞,可能有3种情况:
线程类型线程有着不同的状态,也有不同的类型。大致可分为:
Python线程与GIL相比进程,线程更加轻量,可以实现并发。可是在python的世界里,对于线程,就不得不说一句GIL(全局解释器锁)。GIL的存在让python的多线程多少有点鸡肋了。Cpython的线程是操作系统原生的线程在解释器解释执行任何Python代码时,都需要先获得这把锁才行,在遇到 I/O 操作时会释放这把锁。因为python的进程做为一个整体,解释器进程内只有一个线程在执行,其它的线程都处于等待状态等着GIL的释放。 关于GIL可以有更多的趣事,一时半会都说不完。总之python想用多线程并发,效果可能还不如单线程(线程切换耗时间)。想要利用多核,可以考虑使用多进程。 线程的创建虽然python线程比较鸡肋,可是也并发一无是处。多了解还是有理由对并发模型的理解。 Python提供两个模块进行多线程的操作,分别是
输入如下:(不同的环境不一样)
每个线程都依次打印 0 – 3 三个数字,可是从输出的结果观察,线程并不是顺序的执行,而是三个线程之间相互交替执行。此外,我们的主线程执行结束,将会打印 线程合并(join方法)上述的例子中,主线程结束了,子线程还在运行。如果需要主线程等待子线程执行完毕再退出,可是使用线程的
主线程或者某个函数如果创建了子线程,只要调用了子线程的join方法,那么主线程就会被子线程所阻塞,直到子线程执行完毕再轮到主线程执行。其结果就是所有子线程执行完毕,才打印
输入如下:
|