文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Python系列:多线程(threading)的学习和使用

2024-11-30 02:15

关注

引言

在Python中,threading库提供了一种简单且方便的方式来实现多线程编程。通过使用线程,可以在程序中并行执行多个任务,提高程序的性能和响应性。

了解线程

线程是程序执行的最小单元,是操作系统能够进行运算调度的基本单位。与进程不同,线程在同一进程下共享相同的内存空间,因此线程之间的通信更加方便。在Python中,threading库提供了对线程的支持。

创建线程

threading库是Python中的标准库,无需下载,我们只需在文件中导入threading库就可以用了。

创建线程的时候主要有两种方式,第一种是通过继承threading.Thread类,第二种则是通过传递可调用对象给threading.Thread的构造函数,接下来先讲解第一种方式。

1.通过继承threading.Thread类创建线程

import threading

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

    def run(self):
        print(f"Thread {self.name} is running.")

# 创建线程的实例
thread1 = MyThread(name="Thread 1")
thread2 = MyThread(name="Thread 2")

# 启动线程
thread1.start()
thread2.start()

# 等待线程执行完毕
thread1.join()
thread2.join()

print("Main thread is done.")

第一种方式是最常见的方式,创建线程的时候需要先创建一个类,然后继承threading.Thread,然后再我们创建的类中自定义一个方法,这里我构造的是run方法,在这个方法中我们可以去实现线程需要执行的主要逻辑。

然后通过thread1和thread2创建对应的构造实例,使用线程中的start()方法去启动线程,最后在使用join()等到线程执行完毕,这样我们创建了一个基本的多线程,执行后结果如下:

然后我们再来了解第二种创建线程的方式。

2.通过传递可调用对象创建线程

import threading

def my_function(name):
    print(f"Thread {name} is running.")

# 创建线程的实例,传递一个可调用对象和参数
thread1 = threading.Thread(target=my_function, args=("Thread 1",))
thread2 = threading.Thread(target=my_function, args=("Thread 2",))

# 启动线程
thread1.start()
thread2.start()

# 等待线程执行完毕
thread1.join()
thread2.join()

print("Main thread is done.")

这种方式我们是直接通过传递给一个可调用对象给threading.Thread的构造函数,我们所传递的这个可执行对象可以是函数、方法、或者是__call__等方法类的实例,

其中在threading.Thread实例中,通过使用target参数指定我们需要调用的对象,注意这里指定调用对象是不需要加括号,直接传需要调用的可执行对象名就行,后面就和上面一样,通过使用start()方法和join()方法,执行结果也是跟第一种方式一样。

以上两种方式都可以创建线程,选择那种一般取决于个人在项目中的代码风格和偏好,但是最终都是需要确保的是,无论使用哪种方式我们都需要保证在调用的方法中包含有线程的主要逻辑。

线程同步

Python中的线程和其他语言中的线程逻辑也是一样,如果创建了多个线程,那么这几个线程就是共享内存,可能会导致数据竞争和不确定的结果,所以我们需要在线程中加锁(lock)。

1.锁的基本用法

在python中,如果需要对线程加锁我们就需要用到threading.lock()这个方法:

import threading

# 共享资源
counter = 0

# 创建锁对象
my_lock = threading.Lock()

def increment_counter():
    global counter
    for _ in range(1000000):
        with my_lock:
            counter += 1

# 创建两个线程,分别增加计数器的值
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=increment_counter)

# 启动线程
thread1.start()
thread2.start()

# 等待两个线程执行完毕
thread1.join()
thread2.join()

print(f"Final counter value: {counter}")

在上述代码中,我们通过创建了一个全局锁对象,然后在调用的可执行对象中,使用with语句来获取锁和释放锁,以此来确保线程共享的资源是原子的。这样可以避免多个线程对counter的参数结果进行数据竞争。

从这个简单的代码上我们可能看不出执行后实际有什么不同,接下来我举一个例子来说明没有加锁和加了锁后的执行结果。

2.不加锁执行

import threading

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        current_balance = self.balance
        new_balance = current_balance - amount
        # 模拟取款操作的延迟
        threading.Event().wait(0.1)
        self.balance = new_balance
        return new_balance

# 创建一个共享的银行账户
account = BankAccount(balance=1000)

def withdraw_from_account(account, amount):
    for _ in range(3):
        new_balance = account.withdraw(amount)
        print(f"Withdraw {amount}, New Balance: {new_balance}")

# 创建两个线程进行取款操作
thread1 = threading.Thread(target=withdraw_from_account, args=(account, 100))
thread2 = threading.Thread(target=withdraw_from_account, args=(account, 150))

# 启动两个线程
thread1.start()
thread2.start()

# 等待两个线程执行完毕
thread1.join()
thread2.join()

print(f"Final Balance: {account.balance}")

执行结果:

在上面这个不加锁的实例中,我们用withdraw方法来模拟取款操作,然后通过两个线程来对同时对账户进行取款操作,但是由于这个实例中没有加锁,就会出现下面的情况:

就这样,本来是同一个账户,但是两个线程都是各管各的,最后导致两个线程都取了3次钱后,最后得出的结果是账户里面还剩了550元。

接下来我们再看看加锁后的执行结果:

import threading

class BankAccount:
    def __init__(self, balance):
        self.balance = balance
        self.lock = threading.Lock()

    def withdraw(self, amount):
        with self.lock:
            current_balance = self.balance
            new_balance = current_balance - amount
            # 模拟取款操作的延迟
            threading.Event().wait(0.1)
            self.balance = new_balance
            return new_balance

# 创建一个共享的银行账户
account = BankAccount(balance=1000)

def withdraw_from_account(account, amount):
    for _ in range(3):
        new_balance = account.withdraw(amount)
        print(f"Withdraw {amount}, New Balance: {new_balance}")

# 创建两个线程进行取款操作
thread1 = threading.Thread(target=withdraw_from_account, args=(account, 100))
thread2 = threading.Thread(target=withdraw_from_account, args=(account, 150))

# 启动两个线程
thread1.start()
thread2.start()

# 等待两个线程执行完毕
thread1.join()
thread2.join()

print(f"Final Balance: {account.balance}")

同样的实例,我们通过在实例中加锁后再去执行,结果如下:

通过在实例中添加with self.lock后,我们保证了两个线程访问余额blance的原子性,不管是有多少个线程,每个线程访问的余额始终是其他线程取钱后的最新结果,这样就保证了代码程序执行后的结果是正确的。

以上是今天分享的关于Python中一些基本的线程使用,有兴趣的小伙伴想要深入学习threading这个模块的话可以在留言区打出threading,人多的话我下期就继续更新这个模块。

来源:Python都知道内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯