Skip to main content

多线程

简介

什么是线程?

线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。

线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。

一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行

一个进程中只有一个主线程,多个子线程

为什么要使用多线程?

线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄 和其他进程应有的状态。

因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率。

线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。

操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程 来实现并发比使用多进程的性能高得要多。

进程与线程的区别

特性进程线程
共享的内存空间✔️
创建开销

03a268d2d8cd40de8ba169f5a1daa214_tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0

threading模块

模块解析

打印所有子模块:

[print(i) for i in dir(threading) if not i.startswith("_")]
子模块名称说明备注
Barrier
BoundedSemaphore
BrokenBarrierError
Condition
Event
ExceptHookArgs
Lock
RLock
Semaphore
TIMEOUT_MAX
Thread
ThreadError
Timer
WeakSet
activeCount
active_count()返回当前还活着的线程数目等价于len(threading.enumerate())
currentThread返回当前正在执行的线程对象,如果当前的线程并不是通过 threading 调用的,那么就返回一个dummy thread
current_thread
enumerate
excepthook
functools
get_ident
get_native_id
getprofile
gettrace
local
main_thread
setprofile
settrace为每个线程定义一个 settrace/profile 方法,该方法会传递 sys.settrace/profile, 在 run 线程之前被调用。
stack_size返回创建一个新线程时所会占用的堆栈大小。指定 size 参数可去修改这个值。

Thread 核心模块

线程控制说明备注
start()start()方法开始线程活动。
对每一个线程对象来说它只能被调用一次,它安排对象在一个另外的单独线程中调- 用 run()方法(而非当前所处线程)。
当该方法在同一个线程对象中被调用超过一次时,会引入RuntimeError(运行时错误)。
run()run()方法代表了线程活动的方法,以在子类中重写此方法。
标准 run()方法调用了传递给对象的构造函数的可调对象作为目标参数,如果有这样的参数的话,顺序和关键字参数分别从argskargs取得。
join([timeout])
守护进程
daemon
isDaemon()
setDaemon()
线程状态
is_alive()判断线程还活着没。
sAlive()所谓或者就是线程 start 和结束的中间态。
ident如果线程没 start,则返回 None,如果线程启动了,则返回一个代表当前线程的魔法数字,常用于从一个字典中获得线程相关的数据。

star() 和 run() :

  • start() 方法的作用是启动一个新线程,新线程会执行相应的 run()方法,start()不能被重复调用。

  • run() 方法则只是普通的方法调用,在调用线程中顺序运行而已。如果将 start 用 run 代替,你会发现只有一个线程 Main 在一直调用这个 run 方法而已。

join([timeout])

在 python 中,默认情况下(其实就是 setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。

当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。那么这时如果你还想让主线程等待子线程结束后才终结,这就要调用到 join 方法啦,当线程调用 join 方法时,它会阻塞住调用它的线程,直到自己任务结束,毋论是正常执行完毕还是因为异常或超时而终结。

timeout 参数:

当设置守护线程时,主线程会等待子线程timeout的时间后杀死该子线程,最后退出程序。

所以说,如果有 10 个子线程,全部的等待时间就是每个 timeout 的累加和。简单的来说,就是给每个子线程一个 timeout 的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。

没有设置守护线程时,主线程将会等待 timeout 的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。

守护线程 daemon & isDaemon() & setDaemon()

守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。

当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

Night gathers, and now my watch begins. It shall not end until my death.

”我的存在就是为了将你守护, 白夜行的既视感。 我们可以通过daemon或者isDaemon()去获取线程是否为守护线程,也可通过 setDaemon(True/False)去改变线程的这个属性。

线程名字 name & getname &setName()

错误处理

e. threading.ThreadError
threading的线程异常。

创建线程

方案 1:函数式

以函数形式执行

import threading

def run(n):
print(n)

t1 = theading.Thread(target=run, args=("n",))
t2 = theading.Thread(target=run, args=("n",))

以类形式执行

import threading

方案 2:Thread 子对象(线程对象)

用 Thread 类创建一个线程实例:

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})

参数解析:

  • group:线程组,但因为 python 的多线程不支持 group,这里必须为 None,属于一个扩展保留字段
  • target:传入一个可被 run 调用的对象,通常我们会传入一个行数名;
  • name:定义线程的名字,否则系统会给该线程定义一个类似 Thread-N 的名字;
  • args:tuple 变量集合,传给 target 里的函数对象;
  • kwargs:关键字变量字典,依然传给 target 里的函数对象。
  • daemon:设置线程是否为守护线程,即是前台执行还是后台执行,默认是非守护线程,当 daemon=True 时,子线程为守护线程,此时主线程不会等待子线程,如果主线程完成会强制杀死所有的子线程然后退出。

可以直接用这个类创建线程实例,传入函数及参数,并调用 start 方法开启进程。

方案 3:创建线程类

import threading

class ThreadLearn(threading.Thread):
def __init__(self, args):
super(ThreadLearn, self).__init__()
self.args = args

def run(self):
print('run....')
pass


# 创建线程
c1 = ThreadLearn(args)
c1.start()

加入暂停, 恢复, 退出功能

class YYS(threading.Thread):
def __init__(self, config: Config):
super(YYS, self).__init__()

# flag 初始化
self.__pause = threading.Event()
self.__running = threading.Event()
self.__pause.set()
self.__running.set()

self.config = config
self.thread_id = threading.current_thread().native_id

def run(self):
if not self.config.thread_id:
self.config.thread_id = self.thread_id

while self.__running.is_set():
# 为True时立即返回,
# 为False时阻塞直到内部的标识位为True后返回
self.__pause.wait()
time.sleep(1)
print(f"【{self.config.thread_id}】yys_client: ", self.config.msg)
else:
print("退出线程: ", self.thread_id)

def pause(self):
# 将__pause标识设置为 False
self.__pause.clear()

def resume(self):
# 将__pause标识设置为 True
self.__pause.set()

def stop(self):
# 将 __所有标识设置为 False
self.__pause.clear()
self.__running.clear()

py2.x

  • 函数式使用,通过调用thread模块中的start_new_thread()来产生线程任务
  • 类包装多线程对象,

py2.x 函数式多线程

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import thread
import time

# 为线程定义一个函数
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print "%s: %s" % ( threadName, time.ctime(time.time()) )

# 创建两个线程
try:
thread.start_new_thread( print_time, ("Thread-1", 2, ) )
thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print "Error: unable to start thread"

while 1:
pass