[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 Thread


def 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 Thread


class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def 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 Process


def 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 Process

n = 100
def task():
    global n
    n = 0


if __name__ == '__main__':
    # 在主进程下开启子进程
    print('子进程',n)
    p1 = Process(target=task,)
    p1.start()
    p1.join()

    print('主线程',n)

代码输出如下: 

线程的效果

from threading import Thread
from multiprocessing import Process

n = 100
def task():
    global n
    n = 0


if __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 os


def task():
    # print(current_process().pid)
    print('子进程PID:%s  父进程的PID:%s' % (os.getpid(),os.getppid()))

if __name__ == '__main__':
    # 开多个进程,每个进程都有不同的 PID
    p1 = Process(target=task,)
    p2 = Process(target=task, )
    p1.start()
    p2.start()

    # print('主线程',current_process().pid)
    print('主线程',os.getpid())

代码输出如下:

线程的 PID

from threading import Thread
import os

def 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:continue
        msg_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 time

def 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)

代码输出如下:

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐