Python 多线程

线程和进程

计算机,用于计算的机器。计算机的核心是CPU,在现在多核心的电脑很常见了。为了充分利用cpu核心做计算任务,程序实现了多线程模型。通过多线程实现多任务的并行执行。

现在的操作系统多是多任务操作系统。每个应用程序都有一个自己的进程。操作系统会为这些进程分配一些执行资源,例如内存空间等。在进程中,又可以创建一些线程,他们共享这些内存空间,并由操作系统调用,以便并行计算。

线程状态

创建线程之后,线程并不是始终保持一个状态。其状态大概如下:

  • New 创建。
  • Runnable 就绪。等待调度
  • Running 运行。
  • Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
  • Dead 消亡

这些状态之间是可以相互转换的,一图胜千颜色:

threading_state

threading_state

(图片引用 内心求法博客)

线程中执行到阻塞,可能有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提供两个模块进行多线程的操作,分别是threadthreading,前者是比较低级的模块,用于更底层的操作,一般应有级别的开发不常用。后者则封装了更多高级的接口,类似java的多线程风格,提供run方法和start调用。

12345678910111213141516171819202122 import timeimport 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()

输入如下:(不同的环境不一样)

1234567891011121314 Start main threadingthread Thread1, @number: 0thread Thread2, @number: 0thread Thread3, @number: 0End Main threadingthread Thread1, @number: 1thread Thread3, @number: 1thread Thread2, @number: 1thread Thread3, @number: 2thread Thread1, @number: 2 thread Thread2, @number: 2thread Thread2, @number: 3thread Thread1, @number: 3thread Thread3, @number: 3

每个线程都依次打印 0 – 3 三个数字,可是从输出的结果观察,线程并不是顺序的执行,而是三个线程之间相互交替执行。此外,我们的主线程执行结束,将会打印 End Main threading。从输出结果可以知道,主线程结束后,新建的线程还在运行。

线程合并(join方法)

上述的例子中,主线程结束了,子线程还在运行。如果需要主线程等待子线程执行完毕再退出,可是使用线程的join方法。join方法官网文档大概是

join(timeout)方法将会等待直到线程结束。这将阻塞正在调用的线程,直到被调用join()方法的线程结束。

主线程或者某个函数如果创建了子线程,只要调用了子线程的join方法,那么主线程就会被子线程所阻塞,直到子线程执行完毕再轮到主线程执行。其结果就是所有子线程执行完毕,才打印 End Main threading。只需要修改上面的main函数

12345678910111213 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\”

输入如下:

12345678910 Start main threading过多线程实现多任务的并行执行。

现在的操作系统多是多任务操作系统。每个应用程序都有一个自己的进程。操作系统会为这些进程分配一些执行资源,例如内存空间等。在进程中,又可以创建一些线程,他们共享这些内存空间,并由操作系统调用,以便并行计算。

线程状态

创建线程之后,线程并不是始终保持一个状态。其状态大概如下:

  • New 创建。
  • Runnable 就绪。等待调度
  • Running 运行。
  • Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
  • Dead 消亡

这些状态之间是可以相互转换的,一图胜千颜色:

threading_state

threading_state

(图片引用 内心求法博客)

线程中执行到阻塞,可能有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提供两个模块进行多线程的操作,分别是threadthreading,前者是比较低级的模块,用于更底层的操作,一般应有级别的开发不常用。后者则封装了更多高级的接口,类似java的多线程风格,提供run方法和start调用。

12345678910111213141516171819202122 import timeimport 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()

输入如下:(不同的环境不一样)

1234567891011121314 Start main threadingthread Thread1, @number: 0thread Thread2, @number: 0thread Thread3, @number: 0End Main threadingthread Thread1, @number: 1thread Thread3, @number: 1thread Thread2, @number: 1thread Thread3, @number: 2thread Thread1, @number: 2 thread Thread2, @number: 2thread Thread2, @number: 3thread Thread1, @number: 3thread Thread3, @number: 3

每个线程都依次打印 0 – 3 三个数字,可是从输出的结果观察,线程并不是顺序的执行,而是三个线程之间相互交替执行。此外,我们的主线程执行结束,将会打印 End Main threading。从输出结果可以知道,主线程结束后,新建的线程还在运行。

线程合并(join方法)

上述的例子中,主线程结束了,子线程还在运行。如果需要主线程等待子线程执行完毕再退出,可是使用线程的join方法。join方法官网文档大概是

join(timeout)方法将会等待直到线程结束。这将阻塞正在调用的线程,直到被调用join()方法的线程结束。

主线程或者某个函数如果创建了子线程,只要调用了子线程的join方法,那么主线程就会被子线程所阻塞,直到子线程执行完毕再轮到主线程执行。其结果就是所有子线程执行完毕,才打印 End Main threading。只需要修改上面的main函数

12345678910111213 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\”

输入如下:

123456