这是第一篇Python多线程的学习笔记,看看什么是多线程,以及如何创建他。


之前Python写的脚本程序里面,我们用到的都是单进程单线程的操作。

例如

传统的单进程,单线程的程序

import time
def f1(arg,):
    time.sleep(5)
    print(arg)
for i in range(10):
    f1(i)


然而很多时候,我们需要并发的同时处理多个任务,举个例子,一个播放软件在放电影的时候,他需要同时播放图形,声音,字幕,这个时候他就创建了一个播放软件的进程,然后其内部又运行了多个线程(第一个线程叫做主线程),其中一个线程放图像,一个放声音,一个字幕等等。从这个例子可以知道,一个进程又可以包括多个线程。


因此呢,如果我们需要实现多个任务处理,我们可以使用:

  1. 多进程

  2. 多线程

  3. 多进程+多线程


在Python里面,他有个特殊的规定,每个进程里面同一时间只能有一个线程让CPU执行;而C#,JAVA里面,任意的线程,即使是同一个进程中的多个线程也是可以同时执行的。因此,Python在处理运算密集型的操作(大量CPU执行)时候,适合使用多进程,因为再多的线程在同一个进程里面也不会同时执行;而IO密集型的操作的时候(比如访问网络,文件操作),因为他不占用CPU的资源,大多时候时间都消耗在IO操作等待上了,因此更适合多线程的处理。


下面来对比看看几个简单的例子。


例1:

几个要点

  1. threading.current_thread()是获取当前线程的实例,主线程的名字就叫做MainThread

  2. t.start()表示启动线程,但是他不一定会立刻执行,因此我们可以看见主线程MainThread完成了之后才执行的这个子线程Thread-1

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
import time
def f1(arg, t=None):
    time.sleep(5)
    print(threading.current_thread().name,arg)

import threading
t = threading.Thread(target=f1, args=(1,))
t.start()# 不代表当前线程会被立即执行
print(threading.current_thread().name)
---------------
MainThread
Thread-1 1


例2:

如果我们在上面的基础上加上一个setDaemon(True)的语句,那么主线程结束之后整个程序就直接结束了,并不会等待子线程的执行

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
import time
def f1(arg, t=None):
    time.sleep(5)
    print(threading.current_thread().name,arg)

import threading
t = threading.Thread(target=f1, args=(1,))
t.setDaemon(True)
t.start()# 不代表当前线程会被立即执行
print(threading.current_thread().name)
---------------
MainThread


例3, 如果我们在例1的基础上使用了t.join(),那么可以指定主线程到了这个地方,就给我等着,可以设置等待的时间,比如t.join(2)就是等待2秒,如果什么都不设置的话,主线程会一直等着直到子线程结束之后才执行


比如

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
import time
def f1(arg, t=None):
    time.sleep(5)
    print(threading.current_thread().name,arg)

import threading
t = threading.Thread(target=f1, args=(1,))
t.start()# 不代表当前线程会被立即执行
t.join() #主进程在这给我挂起
print(threading.current_thread().name)
---------------
Thread-1 1
MainThread



看了这3个例子,可以发现创建多线程非常的简单,直接导入模块,然后调用模块的Thread()类实例一个对象就是了。如果看看源代码,会发现我们传入的函数和元祖,会在构造函数中封装起来;当我们调用start()方法之后,他会自动调用bootstrap()->bootstrap_inner()->run(),最后在run()里面执行我们自己的函数,并传入参数


    def run(self):
        """Method representing the thread's activity.
        You may override this method in a subclass. The standard run() method
        invokes the callable object passed to the object's constructor as the
        target argument, if any, with sequential and keyword arguments taken
        from the args and kwargs arguments, respectively.
        """
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self._target, self._args, self._kwargs


知道他的工作逻辑之后,下面来看看另外一种创建的方式。我们可以自定义个类来继承Threading.Thread,自己写个构造函数,这样实例化的时候直接从自己封装参数,然后执行super,这样Thread也会执行上面分析的过程,当他执行到run()的时候,因为最下面我们自己定义了一个run()了,那他就不会执行上面的那个而是执行我们自己的。


可以看见,多线程的效果是一样的,但是我们可以添加更多的新功能进去~


Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32
>>> import threading
class MyThread(threading.Thread):
   def __init__(self, func,args):
      self.func = func
      self.args = args
      super(MyThread, self).__init__()
   def run(self):
      self.func(self.args)
def f2(arg):
   print(arg)
obj = MyThread(f2,123)
obj.start()
123