Python 3 高级编程 - 多线程编程 python多线程菜鸟教程
bigegpt 2024-12-19 11:31 4 浏览
python中的多线程是一个非常重要的知识点,python 默认是单线程的。
什么是线程:
- 线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。
- 线程是进程中的一个实体,是CPU调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
- 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行
线程与进程的区别
进程(process)和线程(thread)是操作系统的基本概念,“进程是资源分配的最小单位,线程是CPU调度的最小单位”。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。
主线程:当?个程序启动时,就有?个进程被操作系统(OS)创建,与此同时?个线程也?刻运 ?,该线程通常叫做程序的主线程。
主线程的重要性:
- 是产生其他子线程的线程
- 通常它必须最后完成执行各种关闭动作
子线程:可以看做是程序执?的?条分?,当?线程启动后会和主线程?起同时执?。
线程
一个线程有一个开始、一个执行顺序和一个结束。它有一个指令指针,可以跟踪它当前在上下文中的哪个位置运行。
- 它可以被抢占(中断)。
- 它可以在其他线程运行时暂时搁置(也称为休眠)——这称为让步。
有两种不同的线程:
- kernel thread 内核线程
- user thread 用户线程
内核线程是操作系统的一部分,而用户空间线程并未在内核中实现。
Python 提供了多个模块来支持多线程编程,包括 thread、 threading 和 Queue 模块等。程序是可以使用 thread 和 threading 模块来创建与管理线程。 thread 模块提供了基本的线程和锁定支持;而 threading 模块提供了更高级别、功能更全面的线程管理。
Python3 中有两个模块支持使用线程:
- _thread
- threading
在 Python 3 中,“thread”模块不再可用。为了在 Python3 中向后兼容,它已重命名为“_thread”。
创建一个新线程
要创建一个新线程,需要调用线程模块中的方法:
_thread.start_new_thread ( function, args[, kwargs] )
此方法调用支持在 Linux 和 Windows 中快速高效地创建新线程。方法调用立即返回,子线程启动并使用传递的参数列表调用函数。当函数返回时,线程终止。
args 是一个参数元组;使用空元组调用函数将不传递任何参数。 kwargs 是可选字典,是关键字参数。
例如:华罗庚先生的时间统筹法烧水,其实就是一个单线程和多线程的例子。
- 如果按照线性思维,先去洗开水壶、烧水花去15分钟,再去洗茶壶、洗茶杯和拿茶叶要10分钟,那一共需要25分钟
- 按照时间统筹法,先洗开水壶,把水放在炉子上烧,然后同时去洗茶壶、茶杯、拿茶叶,等水烧好了,茶具也准备好了,这样两件事情一共只需要花费15分钟,无形中你就节约了10分钟的时间。
单线程:
#单线程
import time
def boiling():
print('我要烧开水了')
time.sleep(40) #模拟需要一定的时间
print('水烧开了')
def wash_port():
print('我要洗茶壶茶杯')
time.sleep(20)
print('茶壶洗好了')
def wash_cup():
print('我要洗茶杯')
time.sleep(10)
print('茶杯洗好了')
start_time = time.time()
boiling()
wash_port()
wash_cup()
end_time = time.time()
print('总共耗时:{}'.format(end_time-start_time))
运行结果:
我要烧开水了
水烧开了
我要洗茶壶茶杯
茶壶洗好了
我要洗茶杯
茶杯洗好了
总共耗时:70.03596377372742
多线程:
import _thread
import time
def boiling():
print('我要烧开水了')
time.sleep(40)
print('水烧开了')
def wash_port():
print('我要洗茶壶茶杯')
time.sleep(20)
print('茶壶洗好了')
def wash_cup():
print('我要洗茶杯')
time.sleep(10)
print('茶杯洗好了')
start_time = time.time()
try:
_thread.start_new_thread( boiling,())
_thread.start_new_thread( wash_port,())
_thread.start_new_thread( wash_cup,())
except:
print ("Error: unable to start thread")
finally:
end_time = time.time()
print('总共耗时:{}'.format(end_time-start_time))
while 1: #一直等待,等待子线程运行完
pass
运行结果:
总共耗时:0.0 #主线程,运行完了,子线程还在运行
我要烧开水了
我要洗茶杯
我要洗茶壶茶杯
茶杯洗好了
茶壶洗好了
水烧开了
程序进入无限循环。您必须按 ctrl-c 才能停止。
Threading 线程模块
Python 2.4 中包含的较新的线程模块为线程提供了比 _thread 线程模块更强大、更高级的支持。
threading 模块公开了 thread 模块的所有方法并提供了一些额外的方法:
- threading.activeCount() - 返回活动线程对象的数量。
- threading.currentThread() - 返回调用者线程控件中线程对象的数量。
- threading.enumerate() - 返回当前活动的所有线程对象的列表。
threading 模块还有实现线程的Thread 类。 Thread 类提供的方法如下:
- run() - 是线程的入口点。
- start() - 通过调用 run 方法启动线程。
- join([time]) - 等待线程终止。表示主线程等待子线程time时长(s)后子线程若还未结束,就强制结束子线程,不设置则主线程会一直等待子线程结束后再结束。
- isAlive() - 检查线程是否仍在执行。
- getName() - 返回线程的名称。
- setName() - 设置线程的名称。
- daemon 属性- 设置主,子线程运行时的关系。bool为True时主线程结束,子线程立即结束;为false主,子线程运行毫不相关,各自独立运行。
使用 threading 线程模块创建线程
创建线程之前,需要先把交给线程去做的工作写成一个函数,这个函数叫线程函数。
操作过程如下:
- 定义 Thread 类的新子类。
- 覆盖 __init__(self [,args]) 方法以添加额外的参数。
- 然后,重写 run(self [,args]) 方法来实现线程在启动时应该做什么。
一旦创建了新的 Thread 子类,就可以创建它的一个实例,然后通过调用 start() 启动一个新线程,而 start() 又会调用 run() 方法。
import threading
import time
exitFlag = 0
#类必须继承threading.Thread
class myThread (threading.Thread):
#threadID, name, counter 为传入线程的参数,可根据自己的需求进行定义
def __init__(self, threadID, name, counter):
# Thread类的__init__(self)方法
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
# 定义run()方法,主要写线程的执行内容
def run(self):
print ("Starting " + self.name)
print_time(self.name, self.counter, 5)
print ("Exiting " + 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 ("Exiting Main Thread")
运行结果:
Starting Thread-1
Starting Thread-2
Thread-1: Mon Apr 3 11:42:02 2023
Thread-1: Mon Apr 3 11:42:03 2023
Thread-2: Mon Apr 3 11:42:03 2023
Thread-1: Mon Apr 3 11:42:04 2023
Thread-2: Mon Apr 3 11:42:05 2023
Thread-1: Mon Apr 3 11:42:05 2023
Thread-1: Mon Apr 3 11:42:06 2023
Exiting Thread-1
Thread-2: Mon Apr 3 11:42:07 2023
Thread-2: Mon Apr 3 11:42:09 2023
Thread-2: Mon Apr 3 11:42:11 2023
Exiting Thread-2
Exiting Main Thread
注意:创建的线程为子线程,是主线程创建的子线程。
线程同步
Python 提供的线程模块包括一个易于实现的锁定机制,允许同步线程。通过调用 Lock() 方法创建一个新锁,该方法返回新锁。新锁对象的 acquire(blocking) 方法用于强制线程同步运行。可选的阻塞参数使您能够控制线程是否等待获取锁。如果阻塞设置为 0,则如果无法获取锁,线程将立即返回 0 值,如果获取了锁,则返回 1。如果 blocking 设置为 1,则线程阻塞并等待锁被释放。新锁对象的 release() 方法用于在不再需要时释放锁。
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 ("Starting " + 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 ("Exiting Main Thread")
运行结果:
Starting Thread-1
Starting Thread-2
Thread-1: Mon Apr 3 11:51:44 2023
Thread-1: Mon Apr 3 11:51:45 2023
Thread-1: Mon Apr 3 11:51:46 2023
Thread-2: Mon Apr 3 11:51:48 2023
Thread-2: Mon Apr 3 11:51:50 2023
Thread-2: Mon Apr 3 11:51:52 2023
Exiting Main Thread
多线程优先级队列
Queue 模块允许创建一个可以容纳特定数量项目的新队列对象。有以下方法来控制队列 -
- get() - get() 从队列中删除并返回一个项目。
- put() - put 将项目添加到队列中。
- qsize() - qsize() 返回队列中当前的项目数。
- empty() - 如果队列为空,则 empty() 返回 True;否则,假。
- full() ? 如果队列已满,full() 返回 True;否则,假。
import queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, q):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.q = q
def run(self):
print ("Starting " + self.name)
process_data(self.name, self.q)
print ("Exiting " + self.name)
def process_data(threadName, q):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty():
data = q.get()
queueLock.release()
print ("%s processing %s" % (threadName, data))
else:
queueLock.release()
time.sleep(1)
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1
# Create new threads
for tName in threadList:
thread = myThread(threadID, tName, workQueue)
thread.start()
threads.append(thread)
threadID += 1
# Fill the queue
queueLock.acquire()
for word in nameList:
workQueue.put(word)
queueLock.release()
# Wait for queue to empty
while not workQueue.empty():
pass
# Notify threads it's time to exit
exitFlag = 1
# Wait for all threads to complete
for t in threads:
t.join()
print ("Exiting Main Thread")
运行结果:
Starting Thread-1
Starting Thread-2
Starting Thread-3
Thread-1 processing One
Thread-2 processing Two
Thread-3 processing Three
Thread-1 processing Four
Thread-2 processing Five
Exiting Thread-3
Exiting Thread-1
Exiting Thread-2
Exiting Main Thread
守护线程
Daemon属性用于设置主线程的守护线程,在启动线程启动之前使用。当bool为True时,该线程为守护线程,主线程结束,子线程立即结束;为false主,子线程运行毫不相关,独立运行,子线程继续运行到结束。
例如,daemon = False:
import time
import threading
def sub_thread():
print("子线程开始 ...")
time.sleep(3)
print("子线程结束 ...")
print("主线程开始 ...")
t = threading.Thread(target=sub_thread)
t.daemon = False
t.start()
print("主线程已结束 ...")
运行结果:
主线程开始 ...
子线程开始 ...
主线程已结束 ...
子线程结束 ...
例如,daemon = True:
import time
import threading
def sub_thread():
print("子线程开始 ...")
time.sleep(3)
print("子线程结束 ...")
print("主线程开始 ...")
t = threading.Thread(target=sub_thread)
t.daemon = True
t.start()
print("主线程已结束 ...")
运行结果:
主线程开始 ...
子线程开始 ...
主线程已结束 ...
主线程结束时,子线程也立即被结束了。
线程锁
Lock锁
threading模块中Lock锁和_thread模块中的锁是一样的。
import threading
import time
num = 0
# 申请线程锁
lock = threading.Lock()
# 类必须继承threading.Thread
class threadTest(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
# 定义run()方法,主要写线程的执行内容
def run(self):
# 声明全局变量num
global num
# 申请线程锁
lock.acquire()
print('子线程' + self.name + '开始: ' +
time.strftime('%T', time.localtime()))
while num < 5:
time.sleep(2)
print(self.name, 'num: ', num)
num += 1
print('子线程' + self.name + '结束: ' +
time.strftime('%T', time.localtime()))
# 释放线程锁
lock.release()
return super().run()
print('主线程开始: %s' % (time.strftime('%T', time.localtime())))
thread1 = threadTest()
# 设置线程名称
thread1.name = 'Thread-1'
thread2 = threadTest()
# 设置线程名称
thread2.name = 'Thread-2'
# 启动线程
thread1.start()
# 启动线程
thread2.start()
time.sleep(1)
thread1.join()
thread2.join()
print('主线程已结束: %s' % (time.strftime('%T', time.localtime())))
运行结果:
主线程开始: 13:13:30
子线程Thread-1开始: 13:13:30
Thread-1 num: 0
Thread-1 num: 1
Thread-1 num: 2
Thread-1 num: 3
Thread-1 num: 4
子线程Thread-1结束: 13:13:40
子线程Thread-2开始: 13:13:40
子线程Thread-2结束: 13:13:40
主线程已结束: 13:13:40
在使用锁线程时,线程1和线程2对num操作就不会出现混乱。
RLock锁
RLock锁又称递归锁,其与Lock锁的差别在于,Lock锁只允许在同一线程中申请一次,否则线程会进入死锁,但是RLock允许在同一线程多次调用。
使用Lock锁产生死锁示例代码:
import threading
import time
print('主线程开始: %s' %(str(time.strftime('%T', time.localtime()))))
lock = threading.Lock()
# 申请线程锁
lock.acquire()
print(threading.enumerate())
# 再次申请线程锁,产生了死锁
lock.acquire()
print(threading.enumerate())
lock.release()
lock.release()
print('主线程结束: %s' %(str(time.strftime('%T',time.localtime()))))
运行结果:
主线程开始: 13:17:04
[<_MainThread(MainThread, started 31564)>]
线程锁死。
使用RLock锁不会产生死锁示例代码:
import threading
import time
print('主线程开始: %s' %(str(time.strftime('%T', time.localtime()))))
lock = threading.RLock()
# 申请线程锁
lock.acquire()
print(threading.enumerate())
# 再次申请线程锁,不会产生死锁
lock.acquire()
print(threading.enumerate())
lock.release()
lock.release()
print('主线程结束: %s' %(str(time.strftime('%T',time.localtime()))))
运行结果:
主线程开始: 13:19:07
[<_MainThread(MainThread, started 32932)>]
[<_MainThread(MainThread, started 32932)>]
主线程结束: 13:19:07
注意线程锁需要成对出现。
条件变量 Condition
条件变量(Condition)也是一把锁,除了同步锁的作用外,还具有在线程间通信的功能。 有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持。
Condition是python3中一种更高级的锁,除和线程锁类似的 acquire() 和 release() 函数外,还提供以下函数:
- wait() 条件不满足时调用,线程会释放锁并进入等待阻塞;
- notify() 条件创造后调用,通知等待池激活一个线程;
- notifyAll() 条件创造后调用,通知等待池激活所有线程。
注意:线程使用前需要获得锁,否则会抛出RuntimeError异常。
Condition提供了一种多线程通信机制,若线程1需要数据,线程1就会阻塞等待,线程2制造出数据,等待线程2制造好数据并通知线程1后,线程1就可以去获取数据了。
import threading,time
from random import randint
class Producer(threading.Thread):
def run(self):
global L
while True:
val=randint(0,10)
print('生产者',self.name,":Append"+str(val),L)
if lock_con.acquire():
L.append(val)
lock_con.notify() #通知消费者,激活wait。
lock_con.release()
time.sleep(3)
class Consumer(threading.Thread):
def run(self):
global L
while True:
lock_con.acquire() #wait阻塞后,从这里开始这行,重新获得锁。
if len(L)==0: #如果容器中没有数据,则等待。
lock_con.wait() #wait的作用:1、释放锁;2、阻塞,等待notify通知
print('消费者',self.name,":Delete"+str(L[0]),L)
del L[0]
lock_con.release()
time.sleep(1)
if __name__=="__main__":
L=[] #容器
lock_con=threading.Condition() #创建一把条件同步变量的锁。
threads=[]
for i in range(3):
threads.append(Producer())
threads.append(Consumer())
for t in threads:
t.start() #start了4个线程对象。
for t in threads:
t.join()
print('-------程序结束----------')
相关推荐
- 了解Linux目录,那你就了解了一半的Linux系统
-
大到公司或者社群再小到个人要利用Linux来开发产品的人实在是多如牛毛,每个人都用自己的标准来配置文件或者设置目录,那么未来的Linux则就是一团乱麻,也对管理造成许多麻烦。后来,就有所谓的FHS(F...
- Linux命令,这些操作要注意!(linux命令?)
-
刚玩Linux的人总觉得自己在演黑客电影,直到手滑输错命令把公司服务器删库,这才发现命令行根本不是随便乱用的,而是“生死簿”。今天直接上干货,告诉你哪些命令用好了封神!喜欢的一键三连,谢谢观众老爷!!...
- Linux 命令速查手册:这 30 个高频指令,拯救 90% 的运维小白!
-
在Linux系统的世界里,命令行是强大的武器。对于运维小白而言,掌握一些高频使用的Linux命令,能极大提升工作效率,轻松应对各种系统管理任务。今天,就为大家奉上精心整理的30个Linu...
- linux必学的60个命令(linux必学的20个命令)
-
以下是Linux必学的20个基础命令:1.cd:切换目录2.ls:列出文件和目录3.mkdir:创建目录4.rm:删除文件或目录5.cp:复制文件或目录6.mv:移动/重命名文件或目录7....
- 提高工作效率的--Linux常用命令,能够决解95%以上的问题
-
点击上方关注,第一时间接受干货转发,点赞,收藏,不如一次关注评论区第一条注意查看回复:Linux命令获取linux常用命令大全pdf+Linux命令行大全pdf为什么要学习Linux命令?1、因为Li...
- 15 个实用 Linux 命令(linux命令用法及举例)
-
Linux命令行是系统管理员、开发者和技术爱好者的强大工具。掌握实用命令不仅能提高效率,还能解锁Linux系统的无限潜力,本文将深入介绍15个实用Linux命令。ls-列出目录内容l...
- Linux 常用命令集合(linux常用命令全集)
-
系统信息arch显示机器的处理器架构(1)uname-m显示机器的处理器架构(2)uname-r显示正在使用的内核版本dmidecode-q显示硬件系统部件-(SMBIOS/DM...
- Linux的常用命令就是记不住,怎么办?
-
1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...
- Linux常用文件操作命令(linux常用文件操作命令有哪些)
-
ls命令在Linux维护工作中,经常使用ls这个命令,这是最基本的命令,来写几条常用的ls命令。先来查看一下使用的ls版本#ls--versionls(GNUcoreutils)8.4...
- Linux 常用命令(linux常用命令)
-
日志排查类操作命令查看日志cat/var/log/messages、tail-fxxx.log搜索关键词grep"error"xxx.log多条件过滤`grep-E...
- 简单粗暴收藏版:Linux常用命令大汇总
-
号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友在Linux系统中,命令行界面(CLI)是管理员和开发人员最常用的工具之一。通过命令行,用户可...
- 「Linux」linux常用基本命令(linux常用基本命令和用法)
-
Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们。总结送免费学习资料(包含视频、技术学习路线图谱、文档等)1、显示日期的指令:d...
- Linux的常用命令就是记不住,怎么办?于是推出了这套教程
-
1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...
- Linux的30个常用命令汇总,运维大神必掌握技能!
-
以下是Linux系统中最常用的30个命令,精简版覆盖日常操作核心需求,适合快速掌握:一、文件/目录操作1.`ls`-列出目录内容`ls-l`(详细信息)|`ls-a`(显示隐藏文件)...
- Linux/Unix 系统中非常常用的命令
-
Linux/Unix系统中非常常用的命令,它们是进行文件操作、文本处理、权限管理等任务的基础。下面是对这些命令的简要说明:**文件操作类:*****`ls`(list):**列出目录内容,显...
- 一周热门
- 最近发表
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)