什么是进程

  • 进程是执行中的程序。
  • 拥有独立地址空间,内存,数据栈等。
  • 操作系统统一管理。
  • 派生(fork或spawn)新进程。
  • 进程间通信(IPC)方式共享信息。

什么是线程

  • 同进程下执行,并共享相同的上下文。
  • 线程间的信息共享和通信更加容易。
  • 多线程并发执行。
  • 需要同步原语。

python与线程

  • 解释器主循环
  • 主循环中只有一个控制线程在执行。
  • 使用全局解释器锁(GIL)。

GIL保证一个线程

  • 设置GIL
  • 切换进一个线程去运行
  • 执行下面的操作之一
  • 指定数量的字节码指令
  • 线程主动让出控制权
  • 把线程设置回睡眠状态(切换出线程)
  • 解锁GIL
  • 重复上述步骤

两种线程管理

Python提供了两种线程管理模式。

  • _thread:提供了基本的线程和锁。
  • threading: 提供了更高级别,功能更全的线程管理。threading底层使用了_thread。
  • 支持同步机制
  • 支持守护线程

_thread模块

函数/方法

描述

thread模块的函数

start_new_thread(function,args,kwargs=None)

派生一个新的线程,使用给定的args和可选的kwargs来执行function

allocate_lock()

分配LockType锁对象

LockType锁对象方法

acquire(wait=None)

尝试获取锁对象

locked()

如果获取了锁对象则返回True,否则返回False

release()

释放锁

使用_thread模块案例1

代码:

import _thread
import logging
from time import sleep, ctime

# 配置日志收集器
logging.basicConfig(level=logging.INFO)


def loop0():
    """定义一个子方法1"""
    logging.info("start loop0 at " + ctime())
    sleep(4)
    logging.info("end loop0 at " + ctime())


def loop1():
    """定义一个子方法2"""
    logging.info("start loop1 at " + ctime())
    sleep(2)
    logging.info("end loop1 at " + ctime())


def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 运行子方法1
    _thread.start_new_thread(loop0, ())
    # 运行子方法2
    _thread.start_new_thread(loop1, ())
    sleep(6)
    logging.info("end all at " + ctime())


if __name__ == "__main__":
    main()

运行结果:

python 多线程 sleep Python 多线程管理_python

注意:这里主方法中必须加上time.sleep(6),否则主进程退出后,所有子进程都将强行退出,所有子线程将不被执行。这里也该模块的缺点之一,因为它没有守护线程的概念。

以下结果是不加time.sleep(6)的效果。

python 多线程 sleep Python 多线程管理_python_02

使用_thread模块案例2-利用锁

上述案例中其实存在缺点。在实际应用中,我们并不确定主线程需要等待子线程几秒钟,子线程才能结束。所以本案例加入锁,可以有效的解决这一问题。

代码:

import _thread
import logging
from time import sleep, ctime

# 配置日志收集器
logging.basicConfig(level=logging.INFO)

loops = [2, 4]


def loop(nloop, nsec, lock):
    """
    定义一个子方法
    nloop: 用于标识当前loop处于第几个
    nsec: 时间,loop循环时间
    lock: 锁
    """
    logging.info(f"start loop {nloop} at " + ctime())
    sleep(nsec)
    logging.info(f"end loop {nloop} at " + ctime())
    # loop执行完成后释放锁
    lock.release()


def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 定义一个锁列表,可以包含很多的锁
    locks = []
    # loop的名字
    nloops = range(len(loops))
    for i in nloops:
        # 声明一个锁
        lock = _thread.allocate_lock()
        # 锁上锁
        lock.acquire()
        # 将这个锁追加到锁列表
        locks.append(lock)
        # 获取锁是需要时间的,因此获取锁和开启子线程不能放在同一个for循环中
        # 可以有效避免获取第2个锁的时间,第一个线程已经执行完毕了
        # _thread.start_new_thread(loop, (i, loops[i], locks[i]))

    for i in nloops:
        # 启动子线程
        _thread.start_new_thread(loop, (i, loops[i], locks[i]))

    for i in nloops:
        # 检查锁是否是锁定,如果被锁定就什么都不做;如果锁是释放的则退出循环
        # 也就是等待每个子线程结束后才退出主线程
        while locks[i].locked(): pass
    logging.info("end all at " + ctime())


if __name__ == "__main__":
    main()

运行结果:

python 多线程 sleep Python 多线程管理_子方法_03

threading模块

对象

描述

Thread

表示一个执行线程的对象

Lock

锁原语对象(和thread模块中的锁一样)

RLock

可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)

Condition

条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或某个数据值

Event

条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将被激活

Semaphore

为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞

BoundedSemaphore

与Semaphore相似,不过它不允许超过初始值

Timer

与Thread相似,不过它要在运行前等待一段时间

Barrier

创建一个“障碍”,必须达到指定数量的线程后才可以继续

使用threading模块案例

threading自带锁,可以更有效解决上述案例【使用_thread模块案例2-利用锁】的问题。

代码:

import threading
import logging
from time import sleep, ctime

# 配置日志收集器
logging.basicConfig(level=logging.INFO)

loops = [2, 4]


def loop(nloop, nsec):
    """
    定义一个子方法
    nloop: 用于标识当前loop处于第几个
    nsec: 时间,loop循环时间
    """
    logging.info(f"start loop {nloop} at " + ctime())
    sleep(nsec)
    logging.info(f"end loop {nloop} at " + ctime())


def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 声明一个线程组
    threads = []
    # loop的名字
    nloops = range(len(loops))

    for i in nloops:
        # 设置子线程
        t = threading.Thread(target=loop, args=(i, loops[i]))
        threads.append(t)

    for i in nloops:
        # 启动子线程
        threads[i].start()

    for i in nloops:
        # 将子线程加入到主线程中
        # 如果子进程执行完毕,主线程就会结束。
        # 如果子线程没有执行完成, 主线程就会阻塞,一定会等子线程执行完成
        threads[i].join()

    logging.info("end all at " + ctime())


if __name__ == "__main__":
    main()

运行结果:

注意:loop 0 和loop 1的执行先后顺序不是一定的。

python 多线程 sleep Python 多线程管理_主线程_04

Thread类

属性

描述

Thread对象数据属性

name

线程名

ident

线程的标识符

daemon

布尔标志,表示这个线程是否是守护线程

Thread对象方法

init(group=None, target=None, name=None, args=(), kwargs={}, verbose=None, daemon=None)

实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs。还可以传递name或group参数,不过后者还未实现。此外,verbose标志也是可接受的。而daemon的值将会设定thread.daemon属性/标志

start()

开始执行该线程

run()

定义线程功能的方法(通常在子类中被应用开发者重写)

join(timeout=None)

直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞

getName()

返回线程名

setName(name)

设定线程名

isAlivel/is_alive()

布尔标志,表示这个线程是否还存活

isDaemon()

如果是守护线程,则返回True,否则返回False

setDaemon(daemonic)

把线程的守护标志设定为布尔值daemonic(必须在线程start()之前调用)

使用threading模块案例-重写threading.Thread

我们可以继续改进上述案例【使用threading模块案例】,使得代码更加符合面向对象编程的思想。

代码:

import threading
import logging
from time import sleep, ctime

# 配置日志收集器
logging.basicConfig(level=logging.INFO)

loops = [2, 4]


class MyThread(threading.Thread):
    def __init__(self, func, args, name=""):
        """ 主动调用threadding.Thread的初始构造方法 """
        threading.Thread.__init__(self)
        self.func = func
        self.args = args
        self.name = name

    def run(self) -> None:
        """重写父类的run方法"""
        self.func(*self.args)


def loop(nloop, nsec):
    """
    定义一个子方法
    nloop: 用于标识当前loop处于第几个
    nsec: 时间,loop循环时间
    """
    logging.info(f"start loop {nloop} at " + ctime())
    sleep(nsec)
    logging.info(f"end loop {nloop} at " + ctime())


def main():
    """主方法"""
    logging.info("start all at " + ctime())
    # 声明一个线程组
    threads = []
    # loop的名字
    nloops = range(len(loops))

    for i in nloops:
        # 设置子线程
        t = MyThread(loop, (i, loops[i]), loop.__name__)
        threads.append(t)

    for i in nloops:
        # 启动子线程
        threads[i].start()

    for i in nloops:
        # 将子线程加入到主线程中
        # 如果子进程执行完毕,主线程就会结束。
        # 如果子线程没有执行完成, 主线程就会阻塞,一定会等子线程执行完成
        threads[i].join()

    logging.info("end all at " + ctime())


if __name__ == "__main__":
    main()

运行结果:

python 多线程 sleep Python 多线程管理_子方法_05