AI全文总结:

这篇文章主要介绍了Python的多线程学习,包括以下几个方面:

  1. 最简单的多线程:通过一个简单的Python脚本展示了如何使用多线程下载电影,说明了多线程程序开始是顺序执行,但完成顺序可能会有所不同。
  2. threading类:详细解释了threading类的各个参数和功能,包括线程组、执行方法、线程名字、参数设置、守护线程等。
  3. 守护线程:介绍了如何设置守护线程,并解释了守护线程的特点,以及在什么情况下应该使用守护线程。同时,也提到了守护线程被粗鲁结束的问题,建议使用合理的信号方法如事件Event来优雅地结束守护线程。
  4. 主线程:介绍了Python中的主线程对象,以及如何获取主线程对象。
  5. 线程间的等待(join()):通过代码示例展示了如何使用join()方法等待线程完成。join()方法会将选中的线程放入线程队列的尾部,等待上一个线程结束才能开始。
  6. lock:通过两段代码对比,解释了多线程环境下进行资源竞争可能导致的问题(如计数器数值错误),并介绍了如何使用lock来解决这个问题。lock可以保证同一时刻只有一个线程在执行某些关键代码,避免资源竞争。

总的来说,这篇文章对Python的多线程进行了详细的介绍和示例,帮助读者理解多线程的基本概念、使用方法和注意事项。

python多线程学习

前言

从今天开始,进行python的爬虫和多线程学习。没有为什么,就是突然感兴趣~

1、最简单的多线程

直接上个最简单的多线程py

import threading
import time

tasks = [
    'movie1','movie2','movie3','movie4','movie5',
    'movie6','movie7','movie8','movie9','movie10'
]

def download(movie):
    print(f'start downloading {movie}')
    time.sleep(2)
    print(f'finished download {movie}')

for task in tasks:
    t = threading.Thread(target=download, args = (task,))
    t.start()

运行后得到结果

image-20210606234620593

可以看到,程序是多线程进行的,开始当然是按着顺序的,可是完成是不同顺序的。这就是最简单的多线程。

2、threading类

t = threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

  • group 线程组
  • target 执行什么方法
  • name 线程名字
  • args,kwargs 参数设置
  • daemon 守护线程
  • t 一个线程对象
  • t.name 线程编号
  • t.ident 操作系统给线程的id
  • t.is_alive() 查询线程是否active
  • t.start() 启动线程

更多的方法,请查询官方文档

3、守护线程

开启守护线程

参数设置中daeman=True

守护线程,当主线程执行完成后,马上就终止当前的任务,不管是否完成。

类似与游戏的bgm。当你在打Boss,bgm刚刚到高潮时,Boss被击败,Boss处刑曲就结束了。这里的bgm就是守护线程。

用官方文档的话解释

Daemon线程会被粗鲁的直接结束,它所使用的资源(已打开文件、数据库事务等)无法被合理的释放。因此如果需要线程被优雅的结束,请设置为非Daemon线程,并使用合理的信号方法,如事件Event。

4、主线程

main_thread()是Python中线程模块的内置方法。 它用于返回主线程对象。 在正常情况下,这是Python解释程序从其启动的线程。

5、线程间的等待join()

thread_num = 100
threads = []

for i in range(thread_num):
    t = threading.Thread(target=download)
    t.start()
    threads.append(t)

for t in threads:
    t.join()

join()方法将被选中的线程,放入线程队列的尾部,等到上一个线程结束,才能开始

6、lock

我们看一段代码

import threading
import time

def download():
    global count
    for i in range(threading_tasks):
        count += 1

count = 0
threading_tasks = 100000
thread_num = 100
threads = []
lock = threading.Lock()
startTime = time.time()

for i in range(thread_num):
    t = threading.Thread(target=download)
    t.start()
    threads.append(t)

for t in threads:
    t.join()

endTime = time.time()

print(f'应该下载的数量:{thread_num*threading_tasks}')
print(f'实际下载的数量:{count}')
print(f'下载的时间:{endTime-startTime}')

得到结果

image-20210607003921667

造成这个的原因就是因为同时有很多线程在执行 count += 1 这条命令

我们需要给download()方法上锁,避免count计数错误

import threading
import time

def download():
    global count
    for i in range(threading_tasks):
        lock.acquire()
        count += 1
        lock.release()

count = 0
threading_tasks = 100000
thread_num = 100
threads = []
lock = threading.Lock()
startTime = time.time()

for i in range(thread_num):
    t = threading.Thread(target=download)
    t.start()
    threads.append(t)

for t in threads:
    t.join()

endTime = time.time()

print(f'应该下载的数量:{thread_num*threading_tasks}')
print(f'实际下载的数量:{count}')
print(f'下载的时间:{endTime-startTime}')

得到结果

image-20210607004710924

虽然时间增加了非常多,但是每个任务都确保完成了。这就是lock的好处。