前言
此文,是我初学python3 多线程的学习记录,在下初学不久,理解比较浅薄,望各位大佬海涵。
一、多线程简介
我从python3开始接触多线程,并且还并没有接触其他语言的多线程操作,个人觉得应该不会有过多的差距,但没学习过之前不敢下判断。
在开发的实际工作中,时常遇到的问题之一便是,某一个事件需要一定时间的处理,但是又有其他的操作实际上不需要等待这个事件的处理结果。这样的单线操作,就导致的运行效率的低下。而解决这种问题的方法,便是多线程。
每个独立的线程都有一个程序运行的入口、顺序执行序列、以及程序的出口。并且线程无法独立执行,线程必须依存在应用程序中,由该程序提供多个线程执行控制。
每个线程都有其自己的一组CPU寄存器,称为线程的上下文,该上下文反应了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
线程可以被分为内核线程和用户线程。
Python3 提供了两个模块来对线程进行操作:
_thread(原Python2 中的thread模块)
threading
其中threading模块包含了_thread的全部内容,并且还有之增加,所以在学习时,推荐使用此模块。
theading模块包含以下的类。
Thread: 基本线程类
Lock: 互斥锁
RLock:可重入锁,使单一进程再次获得已持有的锁(递归锁)
Condition:条件锁,使得一个线程等待另一个线程满足特定条件,比如改变状态或某个值。
Semaphore:信号锁,为线程间共享的有限资源提供一个”计数器”,如果没有可用资源则会被阻塞。
Event:事件锁,任意数量的线程等待某个事件的发生,在该事件发生后所有线程被激活。
Timer:一种计时器
Barrier:Python3.2新增的“阻碍”类,必须达到指定数量的线程后才可以继续执行。
本文不会细致的介绍各个类中的各个方法如何使用,但是会介绍多线程操作的基本编写思路,具体的函数细节操作,可以通过查询官方文档获得。
二、使用线程的两种方法
1.使用函数来创建线程
此方法是将任务函数当作参数传入线程
示例如下:
threading.Thread(target=show, args=(i,))
注意:args这个参数一定是一个元组
2.通过继承thread类
此类方式是通过继承thread类并重写run()方法,达到创建新线程的方法
下段代码是编程狮上的示例代码:
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("开始线程:" + self.name)
print_time(self.name, self.counter, 5)
print ("退出线程:" + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")
这段程序对应的输出是
开始线程:Thread-1
开始线程:Thread-2
Thread-1: Wed Apr 6 11:46:46 2016
Thread-1: Wed Apr 6 11:46:47 2016
Thread-2: Wed Apr 6 11:46:47 2016
Thread-1: Wed Apr 6 11:46:48 2016
Thread-1: Wed Apr 6 11:46:49 2016
Thread-2: Wed Apr 6 11:46:49 2016
Thread-1: Wed Apr 6 11:46:50 2016
退出线程:Thread-1
Thread-2: Wed Apr 6 11:46:51 2016
Thread-2: Wed Apr 6 11:46:53 2016
Thread-2: Wed Apr 6 11:46:55 2016
退出线程:Thread-2
退出主线程
我们来分析一下这个输出结果。
按照输入的参数,我们认为每当线程1输出两次结果,线程2就应当与线程1第二次输出时同步输出,并且线程2的总的执行时间应当是线程1的两倍。
结果符合我们的预期,这证明了线程的确正在按照我们所认为的进行运作。
三、线程锁解决数据同步
在介绍完上述流程后相信大家已经意识到了多线程编程的强大性,但是还有一个问题是我目前没有提及的,并且这个问题也是多线程编程最常遇到的问题——如何进行线程之间的同步。
我们都知道,cpu调用线程是随机调用的,可能之前还在这一个线程的这一条语句,下一个瞬间就跳转到了另一个线程的另一条语句。因此出现脏数据而导致线程间协同出错的可能性就非常的大,于是便有了线程锁这一概念。
线程锁是为了防止数据的不同步而引入的概念,线程锁有两种状态——锁定/未锁定。每当一个线程需要访问共享数据时,必须先获得锁定,但如果这个数据已经被另一个线程锁定了,这个线程就会先暂停,也就是同步阻塞,等到这个线程访问完毕,将锁释放后,再让其他线程继续。
一下是编程狮上的代码示例
import threading
import time
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("开启线程: " + self.name)
# 获取锁,用于线程同步
threadLock.acquire()
print_time(self.name, self.counter, 3)
# 释放锁,开启下一个线程
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)
# 等待所有线程完成
for t in threads:
t.join()
print ("退出主线程")
此段代码的输出是
开启线程: Thread-1
开启线程: Thread-2
Thread-1: Wed Apr 6 11:52:57 2016
Thread-1: Wed Apr 6 11:52:58 2016
Thread-1: Wed Apr 6 11:52:59 2016
Thread-2: Wed Apr 6 11:53:01 2016
Thread-2: Wed Apr 6 11:53:03 2016
Thread-2: Wed Apr 6 11:53:05 2016
退出主线程
可以看到在线程锁的作用下,两个线程按我们所预想的依次对数据进行了操作,接下来我们实验一下,如果没有线程锁,不进行线程同步,会发生什么结果。
#!/usr/bin/python3
import threading
import time
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("开启线程: " + self.name)
# 获取锁,用于线程同步
#threadLock.acquire()
print_time(self.name, self.counter, 3)
# 释放锁,开启下一个线程
#threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)
# 等待所有线程完成
#for t in threads:
# t.join()
print ("退出主线程")
在这段代码中,我将线程锁进行了注释操作,我们来看看输出会是什么结果。
开启线程: Thread-1
开启线程: Thread-2
退出主线程
Thread-1: Mon Aug 2 17:10:46 2021
Thread-2: Mon Aug 2 17:10:47 2021
Thread-1: Mon Aug 2 17:10:47 2021
Thread-1: Mon Aug 2 17:10:48 2021
Thread-2: Mon Aug 2 17:10:49 2021
Thread-2: Mon Aug 2 17:10:51 2021
可以很明显的看出,当注释掉了线程锁之后,这两个线程便互不干扰的进行运行,这样就达不到数据同步的作用。
总结
本文简单的说明了,python3多线程的使用方法,其实简化的说,无非是一下几步
1)继承threading.Thread创建新的子类
2)重写run()方法
3)根据需要,在需要进行数据同步的地方使用进程锁,实现数据同步。
当然,多线程的编写有非常多的写法,比较好且广泛的设计有利用优先队列实现数据同步的,这个也许会放在下一篇文章写。总之,多线程的使用不论是任何语言都是想当重要的内容,需要用心学好。