神奇的Python多线程编程,有点意思(54)

小朋友们好,大朋友们好!

我是猫妹,一名爱上Python编程的小学生。

和猫妹学Python,一起趣味学编程。

今日主题

并发与并行。

学习下Python多线程知识,用到的库是threading。

并发和并行

并发和并行是两个相关但不同的概念。

它们在计算机科学和编程领域经常被讨论,特别是在处理多个任务或同时执行多个进程时。

并发:在这种情况下,你可以在同一时间段内同时执行多个任务。

并行:在这种情况下,你可以将这些任务分配给多个处理器核心来同时执行它们。

并发是在同一时间只做一件事情,将时间分开,比如0-1秒再执行方法1,1-2秒执行方法2,可以交替执行。

并行是可以在同一时间做多个事件。

threading

Python自带多线程库threading,用它可以轻松创建多线程程序。

方法如下:

  1. 创建函数
  2. 创建线程
  3. 启动线程
  4. 等待线程结束

举例如下:

import threading




#1.write a function
def my_func(a,b):
    print(a,"*",b,'==',a*b)
    pass




#2.create a thread which use the functon
t=threading.Thread(target=my_func,args=(8,9))




#3.start thread
t.start()




#4.wait thread stop
t.join()

单线程爬虫VS多线程爬虫

这里选用一位大佬爬取博客园的程序,比较下单线程和多线程的速度。

import requests
import threading
import time
urls=[
f"https://www.cnblogs.com/#p{page}"
for page in range(1,50+1)
    ]


def craw(url):
    r=requests.get(url)
    print(url,len(r.text))




def single_thread():
    print("single_thread begin")
    for url in urls:
        craw(url)
    print("single_thread end")


def multi_thread():
    print("multi_thread begin")
    threads=[]
    for url in urls:
        threads.append(
            threading.Thread(target=craw,args=(url,))
            )
    for thread in threads:
        thread.start()
        
    for thread in threads:
        thread.join()
    print("multi_thread end")
    
if __name__=='__main__':
    start=time.time()
    single_thread()
    end=time.time()
    print("single thread cost:",end-start)
    
    start=time.time()
    multi_thread()
    end=time.time()
    print("multi thread cost:",end-start)



执行时间对比:

single thread cost: 4.703268766403198

multi thread cost: 0.4390251636505127

从数据上看,提升还是很明显的。

线程安全和线程不安全

任何事物都有两面性,多线程加速了程序的执行,也存在一些隐患,那就是多线程不安全问题,使用时要注意规避。

线程安全和线程不安全是指多线程环境下的程序是否具有正确性和稳定性。

线程安全指的是在多线程环境下,程序可以正确地执行,不会发生竞态条件、死锁等问题。

也就是说,多个线程可以同时访问共享资源,而不会发生冲突或破坏。

线程不安全则指在多线程环境下,程序可能会出现不可预测的行为,如竞态条件、死锁等。

这意味着多个线程可以同时访问共享资源,但由于某些原因导致程序崩溃或产生错误结果。

举个例子,先有个感性的认识。

假设有一个方法 withdraw(amount),如果请求量小于当前余额,则从当前余额中减去请求量,然后返回余额。

方法定义如下:

balance = 500
def withdraw(amount):
    if (amount < balance):
        balance -= amount
     return balance

正常结果余额不能为负,如果此时用两个线程不同参数执行该方法时。

如:线程1 withdraw(400)和线程2 withdraw(200)的执行流程如下图:

对于这种问题,我们可以利用锁来解决,将一个线程的关键执行部分加上锁,执行完再释放。

举个具体例子:

10个多线程执行,分别将数字my_var加1000000,结果是多少呢?

import threading


# 定义一个全局变量
my_var = 0


def increment():
    global my_var
    for i in range(1000000):
        my_var += 1


if __name__ == '__main__':
    threads = []
    for i in range(10):
        t = threading.Thread(target=increment)
        threads.append(t)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(my_var)



神奇的Python多线程编程,有点意思(54)_第1张图片

类似问题要如何规避呢?

这就要用到锁了。

神奇的Python多线程编程,有点意思(54)_第2张图片

改造后的代码:

import threading


# 定义一个全局变量
my_var = 0
lock=threading.Lock()


def increment():
    global my_var
    with lock:
        for i in range(1000000):
            my_var += 1


if __name__ == '__main__':
    threads = []
    for i in range(10):
        t = threading.Thread(target=increment)
        threads.append(t)
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    print(my_var)

神奇的Python多线程编程,有点意思(54)_第3张图片

好了,我们今天就学到这里吧!

如果遇到什么问题,咱们多多交流,共同解决。

我是猫妹,咱们下次见!

你可能感兴趣的:(python,开发语言)