当前位置:首页 > 科技  > 软件

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

来源: 责编: 时间:2024-01-15 17:11:10 132观看
导读哈喽大家好,我是了不起,今天来给大家介绍关于Python中的线程,threading库。引言在Python中,threading库提供了一种简单且方便的方式来实现多线程编程。通过使用线程,可以在程序中并行执行多个任务,提高程序的性能和响应性。

哈喽大家好,我是了不起,今天来给大家介绍关于Python中的线程,threading库。7ak28资讯网——每日最新资讯28at.com

引言

在Python中,threading库提供了一种简单且方便的方式来实现多线程编程。通过使用线程,可以在程序中并行执行多个任务,提高程序的性能和响应性。7ak28资讯网——每日最新资讯28at.com

7ak28资讯网——每日最新资讯28at.com

了解线程

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

创建线程

threading库是Python中的标准库,无需下载,我们只需在文件中导入threading库就可以用了。7ak28资讯网——每日最新资讯28at.com

创建线程的时候主要有两种方式,第一种是通过继承threading.Thread类,第二种则是通过传递可调用对象给threading.Thread的构造函数,接下来先讲解第一种方式。7ak28资讯网——每日最新资讯28at.com

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

import threadingclass 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方法,在这个方法中我们可以去实现线程需要执行的主要逻辑。7ak28资讯网——每日最新资讯28at.com

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

7ak28资讯网——每日最新资讯28at.com

然后我们再来了解第二种创建线程的方式。7ak28资讯网——每日最新资讯28at.com

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

import threadingdef 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__等方法类的实例,7ak28资讯网——每日最新资讯28at.com

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

7ak28资讯网——每日最新资讯28at.com

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

线程同步

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

1.锁的基本用法

在python中,如果需要对线程加锁我们就需要用到threading.lock()这个方法:7ak28资讯网——每日最新资讯28at.com

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的参数结果进行数据竞争。7ak28资讯网——每日最新资讯28at.com

从这个简单的代码上我们可能看不出执行后实际有什么不同,接下来我举一个例子来说明没有加锁和加了锁后的执行结果。7ak28资讯网——每日最新资讯28at.com

2.不加锁执行

import threadingclass 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}")

执行结果:7ak28资讯网——每日最新资讯28at.com

7ak28资讯网——每日最新资讯28at.com

在上面这个不加锁的实例中,我们用withdraw方法来模拟取款操作,然后通过两个线程来对同时对账户进行取款操作,但是由于这个实例中没有加锁,就会出现下面的情况:7ak28资讯网——每日最新资讯28at.com

  • thread1读取了账户余额(假设为1000)。
  • thread2也读取了相同的账户余额(仍然是1000)。
  • thread1执行取款操作,更新了账户余额为900。
  • thread2执行取款操作,更新了账户余额为850。

就这样,本来是同一个账户,但是两个线程都是各管各的,最后导致两个线程都取了3次钱后,最后得出的结果是账户里面还剩了550元。7ak28资讯网——每日最新资讯28at.com

接下来我们再看看加锁后的执行结果:7ak28资讯网——每日最新资讯28at.com

import threadingclass 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}")

同样的实例,我们通过在实例中加锁后再去执行,结果如下:7ak28资讯网——每日最新资讯28at.com

7ak28资讯网——每日最新资讯28at.com

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

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

本文链接:http://www.28at.com/showinfo-26-61905-0.htmlPython系列:多线程(threading)的学习和使用

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 高可靠的跨系统转账如何设计

下一篇: REST API的艺术:初学者穿越API空间的旅程与速查表!

标签:
  • 热门焦点
Top