[Python学习日记-91] 并发编程之多线程 —— threading 模块、开启线程的方式、线程相关的其他方法
[Python学习日记-91] 并发编程之多线程 —— threading 模块、开启线程的方式、线程相关的其他方法
简介
threading 模块
开启线程的方式
一、方式一:把可调用对象(函数)传递给 Thread 类
二、方式二:继承 Thread 类,并重写其 run 方法
三、同一线程下开启多个线程与多个子进程的区别
1、开进程的开销远大于开线程,所以开线程的速度会比开进程快
2、同一进程内的多个线程共享该进程的地址空间
3、查看 PID
四、练习
1、多线程并发的 Socket 通信
2、多线程并行大写转换
线程相关的其他方法
简介
在前面的线程理论的介绍当中我们已经介绍了线程的概念、线程与进程的区别、经典的线程模型、POSIX线程和线程的实现理论,这些都是比较偏向于理论的。本篇我们将进入实际敲代码的阶段,看看在 Python 当中应该如何创建线程等,以及使用进程时有什么需要注意的地方。
threading 模块
threading 模块能够让你创建和管控线程,进而实现多任务并行处理。其与 multiprocessing 模块的接口基本一致,这是因为 multiprocessing 模块的完全模仿了 threading 模块的接口,二者在使用层面有着很大的相似性,我们可以回顾一下对 multiprocessing 模块的介绍。threading 模块也提供了 Process、Pool、Queue、Pipe、Lock 等组件。
- Thread 类:用于创建和管理线程,可以通过创建该类的实例来启动一个新的线程;
- Lock 类(互斥锁):提供了一个简单的锁对象,用于在多个线程之间同步访问共享资源,解决数据不一致的问题;
- RLock 类(可重入锁):允许同一线程多次获取锁(避免死锁),与 Lock 不同的是 RLock 可以被同一线程重复获取;
- Semaphore 类:指定同一时间的信号量,用于控制同时访问资源的线程数量;
- Event 类:线程间的简单通信,通过标志位(set()/clear())控制阻塞;
- Condition 类:高级同步原语,允许线程在特定条件下等待或唤醒其他线程;
- Timer 类:定时器,用于延迟执行指定函数;
- Barrier 类:让多个线程在某个点同步(全部到达后继续执行);
- Local 类:线程局部存储,为每个线程创建独立的变量副本;
官方链接:https://docs.python.org/3/library/threading.html
开启线程的方式
一、方式一:把可调用对象(函数)传递给 Thread 类
import time
import random
from threading import Threaddef piao(name):print('%s doing' % name)time.sleep(random.randrange(1, 5))print('%s do end' % name)if __name__ == '__main__':t1 = Thread(target=piao, args=('jove',))t1.start()print('主线程')
代码输出如下:
二、方式二:继承 Thread 类,并重写其 run 方法
import time
import random
from threading import Threadclass MyThread(Thread):def __init__(self, name):super().__init__()self.name = namedef run(self):print('%s doing' % self.name)time.sleep(random.randrange(1, 5))print('%s do end' % self.name)if __name__ == '__main__':t1 = MyThread('jove')t1.start()print('主线程')
代码输出如下:
三、同一线程下开启多个线程与多个子进程的区别
1、开进程的开销远大于开线程,所以开线程的速度会比开进程快
import time
from threading import Thread
from multiprocessing import Processdef piao(name):print('%s doing' % name)time.sleep(2)print('%s do end' % name)if __name__ == '__main__':# 开启进程p1 = Process(target=piao, args=('jove',))p1.start()# 开启线程t1 = Thread(target=piao, args=('kerry',))t1.start()print('主线程')
代码输出如下:
2、同一进程内的多个线程共享该进程的地址空间
进程的效果:
from threading import Thread
from multiprocessing import Processn = 100
def task():global nn = 0if __name__ == '__main__':# 在主进程下开启子进程print('子进程',n)p1 = Process(target=task,)p1.start()p1.join()print('主线程',n)
代码输出如下:
线程的效果:
from threading import Thread
from multiprocessing import Processn = 100
def task():global nn = 0if __name__ == '__main__':# 在主进程下开启线程print('线程', n)t1 = Thread(target=task,)t1.start()t1.join()print('主线程',n)
代码输出如下:
3、查看 PID
进程的 PID:
from threading import Thread
from multiprocessing import Process,current_process
import osdef task():# print(current_process().pid)print('子进程PID:%s 父进程的PID:%s' % (os.getpid(),os.getppid()))if __name__ == '__main__':# 开多个进程,每个进程都有不同的 PIDp1 = Process(target=task,)p2 = Process(target=task, )p1.start()p2.start()# print('主线程',current_process().pid)print('主线程',os.getpid())
代码输出如下:
线程的 PID:
from threading import Thread
import osdef task():# print(current_process().pid)print('子线程PID:%s' % os.getpid())if __name__ == '__main__':# 多个子线程的 PID 都和所在主进程的 PID 一样t1 = Thread(target=task, )t2 = Thread(target=task, )t1.start()t2.start()print('主进程', os.getpid())
代码输出如下:
四、练习
1、多线程并发的 Socket 通信
这个之前在网络编程基础实战 —— 多用户 FTP 项目的多线程版就写过了,这里就不再重复了,有兴趣的可以去翻看一下。
2、多线程并行大写转换
三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件。
from threading import Thread
msg_l=[]
format_l=[]
def talk():while True:msg=input('>>: ').strip()if not msg:continuemsg_l.append(msg)def format_msg():while True:if msg_l:res=msg_l.pop()format_l.append(res.upper())def save():while True:if format_l:with open('threading_db.txt','a',encoding='utf-8') as f:res=format_l.pop()f.write('%s\n' %res)if __name__ == '__main__':t1=Thread(target=talk)t2=Thread(target=format_msg)t3=Thread(target=save)t1.start()t2.start()t3.start()
代码输出如下:
线程相关的其他方法
Thread 类实例对象的方法:
isAlive():返回线程是否活动的,当前(Python 3.12.4)已变为 is_alive()
getName():返回线程名,当前(Python 3.12.4)该方法已被弃用改用为 name 属性
setName():设置线程名,当前(Python 3.12.4)该方法已被弃用,只需要直接对 name 属性赋值即可
threading 模块提供的一些方法:
threading.current_thread():返回当前的线程变量
threading.enumerate():返回一个包含正在运行的线程的 list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程
threading.active_count():返回正在运行的线程数量,与 len(threading.enumerate()) 有相同的结果
演示代码如下:
from threading import Thread,current_thread,active_count,enumerate
import timedef task():print('%s is running\n' % current_thread().name) # 原 getName()time.sleep(2)print('%s is done\n' % current_thread().name) # 获取当前线程名 current_thread()获取当前线程if __name__ == '__main__':t = Thread(target=task,name='子线程1')t.start()t.name = '儿子线程1' # 该线程名,原 setName()print(current_thread()) # 主线程print(enumerate()) # 把当前活跃的线程以列表的形式列出来,连同主线程一共有两个运行线程print(active_count()) # 查看目前存活的线程数t.join() # 主线程等待子线程结束print(t.name)current_thread().name = '主线程/主进程' # 修改主线程名print(t.is_alive()) # 原 isAlive()print('主线程/主进程',current_thread().name)
代码输出如下: