目 录
3.2.2 创建 Thread 的实例,传递给它一个可调用的类实例
Python 多线程编程目录
Python 多线程编程-01-threading 模块初识
Python 多线程编程-02-threading 模块-锁的使用
Python 多线程编程-03-threading 模块 - Condition
Python 多线程编程-04-threading 模块 - Event
Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore
Python 多线程编程-06-threading 模块 - Timer
Python 多线程编程-07-threading 模块 - Barrie
1. 多线程基础知识
1.1 进程和线程的定义与区别?
进程是一个执行中程序,每一个进程都有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。操作系统管理其上面的所有进程,并为这些进程合理的安排时间。一个进程也可以通过 fork 或者 spawn 新的进程来执行其他任务。进程之间通过 IPC 进行通信。
进程的相关知识,可以看我的这个专栏:操作系统_一分耕耘一分收获-CSDN博客
线程和进程类似,但是它是在进程下工作的,一个进程可能拥有多个线程,彼此之间享有共同的上下文或者说数据空间。可以把它看成是迷你进程。线程之间的信息通信和数据共享更加容易,但是因为两个或者多个线程访问同一片数据空间,不同的访问顺序会导致不同的结果,这种情况称之为竞态条件(race condition)。绝大多数线程库都有同步原语来解决这个问题,一般是通过线程管理器来控制执行和访问。
一个线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。它可以被抢占资源(中断),可以临时挂起(睡眠),这种做饭也叫做让步。在多线程之间,不类似多进程,OS 会尽可能公平地给与时间片,线程如果没有专门进行为多线程的情况进行修改,那么线程在执行一些程序时候会保持阻塞状态,CPU的时间分配会被这些贪婪的程序多用。
1.2 Python 的全局解释器锁
Python 代码的执行是由 Python 虚拟机(又叫解释器主循环)进行控制的。Python 的设计思路是,在主循环中同时只能有一个控制线程在执行,就像单核 CPU 系统中的多进程一样,只是在宏观上看起来多进程,在微观上其实还得单个单个的运行。同理,尽管 Python 解释器中可以有多个线程在运行,但是微观时间上,只有一个线程在被真正执行。
而对 Python 虚拟机的访问,就是由全程解释器锁(Global Interpreter Lock)来控制的。这个锁就是用来保证同时只能由一个线程运行的。它的方式:
- 设置GIL
- 切换一个线程去运行
- 执行下面的操作
- 把线程设置回睡眠状态(切换出线程)
a) 指定数量的字节码指令
b) 线程主动让出控制权(可以用 tims.sleep())实现
- 解锁GIL
- 重复 1~5
请注意,从上面的描述,我们能够得到一个关键信息:I/O 密集型的 Python 程序比计算密集型的代码能够更好地利用多线程!如果我们的程序要处理的是计算密集型的情况,请慎用多线程编程,否则频繁的切换线程可能反而会带来性能上的下降。
1.3 在不同的 OS 上,在 Python 中使用线程
Python 支持多线程编程,但是还取决于它所处的 OS 是否支持。绝大多数操作系统是支持多线程的:绝大多数 Unix 平台(Linux、Solaris、Mac OS X、*BSD)等,以及 Windows 平台。此外,Python 使用兼容 POSIX 的线程,也就是众所周知的 pthread。
1.4 thread 模块和 threading 模块比较
在 Python 2.X 时代,使用最基础的 thread 模块只需要 import thread 语句就可以了,但是进入了 Python 3.X 时代,thread 模块越来越式微,名字甚至被改为了 _thread 。
推荐大家使用 threading 模块而不是 thread 模块的原因如下:
- threading 模块更先进,由更好的线程支持;
- thread 模块只拥有一个同步原语,而 threading 模块拥有很多
- thread 模块没有控制线程如何退出,当主线程结束时候,其他的线程也强制结束,不会发出警告或者进行适当的清理。这就造成 thread 模块不支持守护线程这个概念。而这在多线程使用中是非常普遍的。
- thread 模块中的一些属性和 threading 模块冲突;
2. threading 模块使用详解
2.1 threading 模块的对象
2.1.1 Thread :执行线程的对象
首先,创建一个 Thread 类对象。
import threading
t1=threading.Thread()
可以看到它有很多方法和属性。
序号 | 属性和方法 | 描述 |
1 | 属性:daemon | 布尔标识。表示这个线程是否是守护线程。 |
2 | 属性:ident | 线程标识符 |
3 | 属性:name | 线程名 |
4 | 属性:native_id | 线程 ID,该非负数可用于在系统范围内唯一标识此特定线程。 |
5 | 方法:getName() | 返回线程名 |
6 | 方法:is_alive() | 返回布尔标识。表示这个线程是否是否存活。 |
7 | 方法:isAlive() | 驼峰式命名已经被弃用。#DeprecationWarning: isAlive() is deprecated, use is_alive() instead |
8 | 方法:isDaemon() | 返回布尔标识。表示这个线程是否是守护线程,是则 True,否则是 False。 |
9 | 方法:start() | 开始执行该线程,请注意,一个线程只能开始执行依次,反复调用这个方法会报错:threads can only be started once; |
10 | 方法:join() | 直至启动的线程终止之前一直挂起,除非给出了 timeout(秒),否则就会一直阻塞。请注意,得先 start 才能 join。cannot join thread before it is started |
11 | 方法:run() | 定义线程功能的方法,通常在子类中被应用开发者重现 |
12 | 方法:setDaemon(daemonic) | 已经弃用,应该直接设置属性 daemon,也可以在实例化中设置 |
13 | 方法:setName("abc") | 设置线程名 |
2.1.2 Lock :锁原语对象
首先,创建一个 Lock 类对象。
lock_example=threading.Lock()
序号 | 属性和方法 | 描述 |
1 | 方法:acquire(blocking=True, timeout=-1) | 如果没有参数,且这个锁已经被锁定了,哪怕是被同一个线程锁定,这个线程将会阻塞,需要等待另外一个线程释放获取锁后,返回 True。 如果有参数,只有当参数是 True时候,才会阻塞。 方法的返回值反应释放获取了锁。阻塞操作是可以中断的。 |
2 | 方法:acquire_lock() | 已经废弃 |
3 | 方法:locked() | 返回一个布尔值,指示锁的状态 |
4 | 方法:locked_lock() | 已经废弃 |
5 | 方法:release() | 释放锁,允许在等待该锁的阻塞队列中的某个线程获得这个锁。这个锁当前应该是被锁定的状态,但是不能是锁定这个锁的线程再次锁定它。 |
6 | 方法:release_lock() | 已经废弃 |
详细使用请见:Python 多线程编程-02-threading 模块-锁的使用
2.1.3 RLock:可重入锁对象
首先,创建一个 RLock 类对象。RLock 类对象使单一线程可以(再次)获得已持有的锁(锁递归)。
序号 | 属性和方法 | 描述 |
1 | 方法:acquire (blocking=True) | 锁定锁,返回一个布尔值指示是否锁定。blocking 指示我们是否等待这个锁可用。blocking = False 且另外一个线程占据了这个锁,那么立刻返回 False。blocking = True, 且另外一个线程占据了这个锁,那么则等待这个锁释放,拿到这个锁,然后返回 True。 请注意,阻塞操作是可中断的。在所有其他情况下,该方法将立即返回True。 确切地说,如果当前线程已经持有锁,则其内部计数器简单地递增。如果没有人拿着锁,获取锁,其内部计数器初始化为1。 |
2 | 方法:release() | 释放锁,允许在等待该锁的阻塞队列中的某个线程获得这个锁。这个锁当前应该是被锁定的状态,但是必须是锁定这个锁的线程再次锁定它。 |
详细使用请见:Python 多线程编程-02-threading 模块-锁的使用
2.1.4 Condition:条件变量对象
高级用法,详细见:Python 多线程编程-03-threading 模块 - Condition
2.1.5 Event:条件变量的通用版本
高级用法,详细见:Python 多线程编程-04-threading 模块 - Event
2.1.6 Semaphore:有限资源计数器
为线程间共享的有限资源提供了一个计数器,如果没有资源时候会被阻塞。
高级用法,详细见:Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore
2.1.7 BoundedSemaphore:
和 Semaphore 类似,但是不允许超过初始值。
高级用法,详细见:Python 多线程编程-05-threading 模块 - Semaphore 和 BoundedSemaphore
2.1.8 Timer:定时器
高级用法,详细见:Python 多线程编程-06-threading 模块 - Timer
2.1.9 Barrier:障碍
创造一个障碍,必须达到指定数量的线程后才可以继续。
高级用法,详细见:Python 多线程编程-07-threading 模块 - Barrier
2.2 threading 模块的其他函数
2.2.1 active_count()
返回当前活动的Thread 对象个数。返回值和通过enumerate()返回的列表长度是相等的。
2.2.2 current_thread
返回当前活动的Thread 对象,对应调用者的控制线程。如果调用者的控制线程不是通过threading 模块创建,一个功能受限的虚拟线程被返回。
2.2.3 enumerate()
返回当前活动的Thread 对象列表。该列表包括精灵线程、被current_thread()创建的虚拟线程对象、和主线程。它不包括终止的线程和还没有启动的线程。
2.2.4 main_thread()
返回主线程对象。在正常情况下,主线程就是Python解释器启动的线程。
2.2.5 get_ident()
返回当前线程的“线程标识符”。这是一个非0整数,没有特定含义,通常用于索引线程特定数据的字典。线程标识符可以被循环使用。
2.2.6 setrace(func)
为所有线程设置一个 trace 函数。为所有从 threading 模块启动的线程设置一个 trace 函数。在每个线程的 run() 方法被调用前,函数将为每个线程被传递到 sys.settrace()。
2.2.7 setprofile(func)
为所有线程设置一个 profile 函数。
2.2.8 stack_size(size=0)
返回新创建线程的栈大小;或者为后续创建的线程设定栈的大小为 size。
3. threading.Thread 调用
3.1 threading.Thread 构造函数
3.1.1 构造方法
t=threading.Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)
3.1.2 参数列表
- group:默认为 None,是为后续实现线程组时候使用的。
- target:默认为 None,不为None 时候是一个可调用的对象,指定了启动线程时候进行的操作。
- name:默认为 None,不为None 时候指定线程的名字,否则由系统指定以 "Thread-N" 开头的线程名。
- args:和 target 连用,作为 target 的参数列表
- kwargs:和 target 连用,作为 target 的参数字典
- daemon:默认为 None,是否是守护线程
3.2 threading.Thread 使用示范
使用 threading.Thread 类,可以有很多方法创建线程。主要有三种:
- 创建 Thread 的实例,传递给它一个函数
- 创建 Thread 的实例,传递给它一个可调用的类实例
- 派生 Thread 的子类,并创建子类的实例
3.2.1 创建 Thread 的实例,传递给它一个函数
请注意此处在创建 Thread 类时候就指定了其调用的目标函数和参数。
import threading
import time
def say_time(nSeconds,name):
print("{0} Starting In saytime:{1}".format(name,time.ctime()))
print(" I will sleep for {0} seconds!".format(nSeconds))
time.sleep(nSeconds)
print("{0} Ending In saytime:{1}".format(name,time.ctime()))
n_loops=list(range(5))
def main():
threads=[]
# 创建线程,并且指定函数
for i in n_loops:
thread=threading.Thread(target=say_time,args=(i+1,i))
threads.append(thread)
# 依次启动线程
for i in n_loops:
threads[i].start()
for i in n_loops:
threads[i].join()
print("All done! {0}".format(time.ctime()))
main()
.join() 方法等待线程结束,或者在提供了超时时间的情况,达到超时时间。使用 .join() 方法要比等待锁释放的无限循环更加清晰。此处的 .join() 可有可无,因为主程序之后没有必须等待 thread 完成的工作,但是如果不使用 .join(),对于某些对顺序有严格要求的程序,可能不合适。 如果我把此处的 .join() 注释掉,程序各条语句完成的顺序就不一样了。
import threading
import time
def say_time(nSeconds,name):
print("{0} Starting In saytime:{1}".format(name,time.ctime()))
print(" I will sleep for {0} seconds!".format(nSeconds))
time.sleep(nSeconds)
print("{0} Ending In saytime:{1}".format(name,time.ctime()))
n_loops=list(range(5))
def main():
threads=[]
for i in n_loops:
thread=threading.Thread(target=say_time,args=(i+1,i))
threads.append(thread)
for i in n_loops:
threads[i].start()
#for i in n_loops:
#threads[i].join()
print("All done! {0}".format(time.ctime()))
main()
3.2.2 创建 Thread 的实例,传递给它一个可调用的类实例
这种写法很累赘,目前我还没有看出来有什么好处。不过,这是一种实现方式。
import threading
import time
class Say_Time(object):
def __init__(self,func,args,name=" "):
self.name=name
self.func=func
self.args=args
def __call__(self):
self.func(*self.args)
def say_time(nSeconds,name):
print("{0} Starting In saytime:{1}".format(name,time.ctime()))
print(" I will sleep for {0} seconds!".format(nSeconds))
time.sleep(nSeconds)
print("{0} Ending In saytime:{1}".format(name,time.ctime()))
n_loops=list(range(3))
def main():
threads=[]
print("All Starting! {0}".format(time.ctime()))
for i in n_loops:
print(say_time,18+i,"name"+str(i))
thread=threading.Thread(target=Say_Time(say_time,(18+i,"name"+str(i))))
threads.append(thread)
for i in n_loops:
threads[i].start()
for i in n_loops:
threads[i].join()
print("All done! {0}".format(time.ctime()))
main()
3.2.3 派生 Thread 的子类,并创建子类的实例
这种方法和第一种方法用得较多。
import threading
import time
class MyThread(threading.Thread):
def __init__(self,func,args,name=" "):
threading.Thread.__init__(self)
self.name=name
self.func=func
self.args=args
# run() 定义线程功能的方法,通常在子类中被应用开发者重现
def run(self):
self.func(*self.args)
def say_time(nSeconds,name):
print("{0} Starting In saytime:{1}".format(name,time.ctime()))
print(" I will sleep for {0} seconds!".format(nSeconds))
time.sleep(nSeconds)
print("{0} Ending In saytime:{1}".format(name,time.ctime()))
n_loops=list(range(3))
def main():
threads=[]
print("All Starting! {0}".format(time.ctime()))
for i in n_loops:
print(say_time,18+i,"name"+str(i))
thread=MyThread(say_time,(18+i,"name"+str(i)),say_time.__name__)
threads.append(thread)
for i in n_loops:
threads[i].start()
for i in n_loops:
threads[i].join()
print("All done! {0}".format(time.ctime()))
main()
'''
要是大家觉得写得还行,麻烦点个赞或者收藏吧,想个博客涨涨人气,非常感谢!
'''