excel如何制作月度项目进度表格
668
2022-05-30
如果你有想要交流的想法、技术,欢迎在评论区留言。
本篇文章将给大家介绍 Python 多线程与多进程相关知识,学习完该知识点之后,你的 Python 程序将进入另一个高峰。
十五、Python 多线程与多进程
先尝试理解线程与进程的概念,进程范围大,一个进程可能会包含多个线程,OK,了解到这一步就可以了,知道谁包含谁已经很不错了,细节的地方慢慢研究。
打开你电脑上的任务管理器,注意这里面以前说的叫做杀掉进程。
15.1 Python 多线程
让我们把视角转换一下,先从进程中抽离出来,看一下线程,在学习这部分内容的时候,这两个概念一定不要弄错,弄错就翻车了。
15.1.1 简单的多线程
如果一个线程只完成一个事情,那程序会变得特别呆板,例如现在你正在给编写一段代码,那你在编写代码的过程中,你使用的 IDE(代码编辑器)就完全不能做其它事情了,必须等到编写完所有代码之后才可以执行其它操作,所有的事情只能一件挨着一件的做。而且在这个线程会将资源霸占住,例如让其操作一个文件,必须等到它完成操作其它程序才可以使用,这叫做单线程。
如何实现多线程呢,通过导入 Python 内置的 threading 模块可以解决该问题。
import threading # 定义一个函数,在线程中运行 def thread_work(): pass # 在 Python 中运行线程 # 建立线程对象 my_thread = threading.Thread(target=thread_work) # 启动线程 my_thread.start()
建立一个线程使用的是 threading 模块中的 Thread 方法,该方法会创建一个 Thread 对象(线程对象),使用该方法的时候需要注意方法的参数值是一个函数名称,该参数为 target,后面是线程要调用的函数名称,没有小括号。返回的线程对象在上述代码中叫做 my_thread,自己定义的任意名称都是可以的,遵循变量命名规则即可。
线程的启动需要调用线程对象的 start 方法。
import threading import time # 定义一个函数,在线程中运行 def thread_work(): # 函数内部方法 print(" my_thread 线程开始工作") time.sleep(10) # 暂停十秒,为了方便模拟操作 print("时间到了,线程继续工作") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread = threading.Thread(target=thread_work) # 启动线程 my_thread.start() time.sleep(1) # 主线程停止 1 秒 print("主线程结束")
代码运行之后重点注意输出的顺序。
主线程开始运行 my_thread 线程开始工作 主线程结束 时间到了,线程继续工作
主线程结束 输出之后,需要等待几秒钟的时间,我们定义的子线程才会开始运行,即输出 时间到了,线程继续工作。
15.1.2 子线程传递参数
在创建线程的时候,除了直接调用某函数,也可以向子线程中的函数里传递参数,具体语法格式如下:
my_thread = threading.Thread(target=函数名称,args=['参数1','参数2',....])
具体案例如下,像 thread_work 函数中传递一个 橡皮擦。
import threading import time # 定义一个函数,在线程中运行 def thread_work(name): # 函数内部方法 print(" my_thread 线程开始工作") print("我是从主线程传递进来的参数:", name) time.sleep(10) # 暂停十秒,为了方便模拟操作 print("时间到了,线程继续工作") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread = threading.Thread(target=thread_work, args=["橡皮擦"]) # 启动线程 my_thread.start() time.sleep(1) # 主线程停止 1 秒 print("主线程结束")
参数在传递的时候,需要与函数定义时参数匹配。多线程中不建议使用相同的变量,很容易出现问题,建议每个线程使用自己的局部变量,互相之间不要产生干扰。
15.1.3 线程命名
每个线程在启动之后,如果没有手动命名,系统会自动给其命名为 Thread-n,在程序中可以使用 currentThread().getName() 获取线程的名称。随着 Python 版本的迭代,currentThread 方法已经逐步被 current_thread 替代。
import threading import time # 定义一个函数,在线程中运行 def thread_work1(name): # 函数内部方法 print(threading.currentThread().getName()," 线程启动") time.sleep(2) print(threading.currentThread().getName()," 线程启动") # 定义一个函数,在线程中运行 def thread_work2(name): # 函数内部方法 print(threading.currentThread().getName(), " 线程启动") time.sleep(2) print(threading.currentThread().getName(), " 线程启动") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread1 = threading.Thread(target=thread_work1, args=["橡皮擦"]) my_thread2 = threading.Thread(target=thread_work2, args=["橡皮擦"]) # 启动线程 my_thread1.start() # 启动线程 my_thread2.start() time.sleep(1) # 主线程停止 1 秒 print("主线程结束")
代码运行结果如下,可以重点看一下线程默认的名称。
主线程开始运行 Thread-1 线程启动 Thread-2 线程启动 主线程结束 Thread-2 线程启动 Thread-1 线程启动
如果想要给线程起一个独特的名字,可以在通过 Thread 方法建立线程时,使用参数 name = "线程名称",该名称就是为线程单独命名。
import threading import time # 定义一个函数,在线程中运行 def thread_work1(name): # 函数内部方法 print(threading.currentThread().getName()," 线程启动") time.sleep(2) print(threading.currentThread().getName()," 线程启动") # 定义一个函数,在线程中运行 def thread_work2(name): # 函数内部方法 print(threading.currentThread().getName(), " 线程启动") time.sleep(2) print(threading.currentThread().getName(), " 线程启动") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread1 = threading.Thread(name="我是线程1(不建议用中文)",target=thread_work1, args=["橡皮擦"]) my_thread2 = threading.Thread(name="work thread",target=thread_work2, args=["橡皮擦"]) # 启动线程 my_thread1.start() # 启动线程 my_thread2.start() time.sleep(1) # 主线程停止 1 秒 print("主线程结束")
除了上述办法以外,还可以使用 currentThread().setName() 给函数命名,自己可以尝试下哦~
15.1.4 Daemon 守护线程
默认创建的线程都不是 Daemon 线程,正常情况下,一个程序建立了主线程和子线程,那程序结束需要等待所有的线程工作结束,因为如果主线程先结束了,那子线程会因为没有可用资源而导致程序崩溃。
如果我们希望主线程结束了,子线程自行终止,那这时就要设置一下 Daemon 线程的属性了,设置之后,主线程若是想要结束运行,需要检查一下 Daemon 线程的属性。
如果 Daemon 线程的属性是 True,其它非 Daemon 线程执行结束,不会等待 Daemon 线程,主线程会自动结束。
如果 Daemon 线程属性是 False,那主线程必须等待 Daemon 线程结束才会将程序结束运行。
以上内容翻译成大白话就是可以把一个线程设置为 Daemon 线程,而且还可以设置一个属性,如果属性设置为 True,那该线程就不受重视了,其它线程结束,它就被结束了,如果设置为 False,那它就是最重要的了,主线程需要等着它结束运行,才可以进行下一步操作。
import threading import time # 定义一个函数,在线程中运行 def thread_work1(): # 函数内部方法 print(threading.currentThread().getName()," 线程启动") # 等待 5 秒,如果被重视,那主线程将等待,如果不被重视,很快就会执行完毕 time.sleep(5) print(threading.currentThread().getName()," 线程启动") # 定义一个函数,在线程中运行 def thread_work2(): # 函数内部方法 print(threading.currentThread().getName(), " 线程启动") print(threading.currentThread().getName(), " 线程启动") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread1 = threading.Thread(name="我是守护线程 Daemon",target=thread_work1) my_thread1.setDaemon(True) # 先设置为 True,该线程将不被重视 my_thread2 = threading.Thread(name="work thread",target=thread_work2) # 启动线程 my_thread1.start() # 启动线程 my_thread2.start() print("主线程结束")
以上代码运行之后发现瞬间执行完毕了,并没有等待 5 秒钟,充分证明了不被重视的线程的处境。
接下来修改一个属性,可以再看一下效果。
my_thread1.setDaemon(False)
运行之后发现程序等待 5 秒之后才结束运行,你是否发现了其中的差异呢?
15.1.5 堵塞主线程
主线程在工作的时候,如果希望子线程先运行,直到该子线程运行结束,主线程才继续工作。
import threading import time # 定义一个函数,在线程中运行 def thread_work1(): # 函数内部方法 print(threading.currentThread().getName()," 线程启动") time.sleep(5) print(threading.currentThread().getName()," 线程启动") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread1 = threading.Thread(name="work thread",target=thread_work1) # 启动线程 my_thread1.start() print("join 开始......") my_thread1.join() # 等待 work thead 线程运行结束 print("join 结束....") print("主线程结束")
join 方法可以增加一个参数,该参数表示等待的秒数,当秒数到了,主线程恢复工作。
my_thread.join(3) # 子线程运行 3 秒。
15.1.6 is_alive 检验子线程是否在工作
使用 join 方法之后,一般在后面需要加上一个 is_alive 方法,该方法会简称子线程是否工作结束了,如果子线程结束则返回 False,仍在工作则会返回 True。
import threading import time # 定义一个函数,在线程中运行 def thread_work1(): # 函数内部方法 print(threading.currentThread().getName()," 线程启动") time.sleep(5) print(threading.currentThread().getName()," 线程启动") print("主线程开始运行") # 在 Python 中运行线程 # 建立线程对象 my_thread1 = threading.Thread(name="work thread",target=thread_work1) # 启动线程 my_thread1.start() print("join 开始......") my_thread1.join(2) # 等待 work thead 线程运行结束 print("join 结束....") print("子线程是否仍在工作?",my_thread1.is_alive()) time.sleep(3) print("子线程是否仍在工作?",my_thread1.is_alive()) print("主线程结束")
有的教程或者书籍中还会使用 isAlive 方法来进行判断,这是因为 Python 版本的问题,后续建议使用 is_alive 方法。
15.1.7 自定义线程类
threading.Thread 是 threading 模块内的一个类,我们可以继承这个类,定义自己的线程类,定义的时候有两个需要注意的地方,第一个需要在构造函数中调用 threading.Thread.__init()__ 方法,第二个是需要在类内容定义好 run 方法。
之前的内容中,通过 threading.Thread 声明一个线程对象时,执行 start 方法可以建立一个线程,start 方法就是在调用类中的 run 方法。
import threading class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): print(threading.Thread.getName(self)) print("橡皮擦定义好的线程") my_thread = MyThread() my_thread.run() you_thread = MyThread() you_thread.run()
15.1.8 资源锁定与解锁
在多线程程序中经常碰到多个线程使用一个共享资源的情况,为了确保共享资源在多线程共享时不出现问题,需要使用 theading.Lock 对象的两个方法 acquire 与 release 。
import threading my_num = 0 lock = threading.Lock() class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): print(threading.Thread.getName(self)) # 调用全局变量 global my_num my_num += 10 print("现在的数字是:", my_num, "\n") # 线程列表 ts = [] # 批量创建 10 个线程 for i in range(0, 10): my_thread = MyThread() ts.append(my_thread) # 启动 10 个线程 for t in ts: t.start() # 等待所有线程结束 for t in ts: t.join()
以上代码没有使用 acquire 与 release 方法,出现的结果无规律可循,是因为各线程无法预期谁会优先取得资源,专业描述叫做 线程以不可预知的速度向前推进,当然有的地方叫做线程竞速,一个意思。
稍微修改一下就可以让线程按照规矩执行了,在使用全局变量的时候,先锁定资源,使用之后在释放资源。
# 调用全局变量 global my_num lock.acquire() my_num += 10 lock.release() print("现在的数字是:", my_num, "\n")
以上内容如果使用 acquire 连续使用两次就会导致死锁。
关于死锁问题与资源锁定 Threading.RLock,还有高级锁定相关的知识,在以后的滚雪球中继续学习,先阶段掌握基本的锁定就可以啦。
15.1.9 未来要学习的知识
进展到现在你已经可以实现简单的多线程开发了,但是对于线程类的学习只揭示了最简单的一部分,后续我们将学习到如下内容,都在第二遍滚雪球时学习。
queue 模块,也叫做队列模块
Semaphore 信号量,高级锁机制
Barrier 栅栏
Event 线程通讯机制
15.2 subprocess 模块
subprocess 是 Python 中用于建立子进程的模块,注意是子进程。导入该模块使用 import subprocess。
15.2.1 Popen 方法
该方法可以打开计算机内部的应用程序,也可以打开自己写好的程序,文件路径写对即可。
import subprocess # 打开计算机 calc_pro = subprocess.Popen('calc.exe') # 打开画板 mspaint_pro = subprocess.Popen('mspaint.exe')
打开的子进程,主程序已经结束了。
15.2.2 Popen 方法携带参数
可以在 Popen 方法打开程序的时候,传递一个参数进去,该参数为列表类型,第一个元素是要打开的应用程序,第二个则是传递进去的文件。
例如打开画图程序。
import subprocess # 打开计算机 # calc_pro = subprocess.Popen('calc.exe') # 打开画板 mspaint_pro = subprocess.Popen(['mspaint.exe','./pic.jpg'])
文件的路径不要写错,以上代码会打开画板程序并且在画板打开一个图片。
15.2.3 通过 start 打开程序
在电脑上通过双击就可以打开某种文件,这是因为 Windows 系统已经给我们做好了关联,那能不能在 Python 中也模拟出该方式呢,很简单,通过 subprocess.Popen 方法的参数即可实现。
import subprocess # 打开图片 mspaint_pro = subprocess.Popen(['start','./pic.jpg'],shell = True)
使用该代码打开图片是使用你默认的图片预览程序,满足了刚才所说的场景。该方法核心使用的有两个地方一个是原程序位置使用的是 start 关键字(仅在 Windows 上有效),第一个是 shell = True 参数。
15.2.4 通过 run 方法调用子进程
该方法属于新增方法,通过 subprocess.run 方法即可调用子进程。具体内容可以自行尝试即可。
15.3 这篇博客的总结
本篇博客主要内容是 Python 的多线程应用,顺带着说了一点点关于进程的相关知识,对于多线程,很多学习 Python 很久的同学都不一定可以搞清楚,在这里希望大家第一次学习先有概念支撑即可,能掌握多少在本阶段不重要,学习是需要时间积累的,一遍就会那是天才或者是吹牛的,有很多工作 2~3 年的还不一定能把多线程多进程说清楚呢,所以不要着急哦,继续往后面看,往后面学就好了。
第一遍滚雪球学 Python 收官。下期见。
博主 ID:梦想橡皮擦,希望大家
、
评论
、
。
Python 任务调度
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。