人人都能学会的 Python 多线程指南( 三 )


-> 线程1 启动 , 睡眠 1 秒
-> 线程2 启动 , 睡眠 2 秒
-> 线程3 启动 , 睡眠 3 秒
-> 线程4 启动 , 睡眠 4 秒
-> 线程5 启动 , 睡眠 5 秒
-> 线程6 启动 , 睡眠 6 秒
-> 线程7 启动 , 睡眠 7 秒
-> 线程8 启动 , 睡眠 8 秒
-> 线程9 启动 , 睡眠 9 秒
-> 线程10 启动 , 睡眠 10 秒
-> 线程1 结束
-> 线程2 结束
-> 线程3 结束
-> 线程4 结束
-> 线程5 结束
-> 线程6 结束
-> 线程7 结束
-> 线程8 结束
-> 线程9 结束
-> 线程10 结束
全部任务执行完成 , 耗时 10.01 秒
共享变量锁的问题
现在 , 你应该已经了解 threading 最基本的用法 , 只需要将 do_somthing 函数进行修改即可 , 但是如果你深入使用 , 还会有其他的问题出现 , 例如共享变量的问题 , 让我们继续探讨 。
多线程很常见的一个应用就是爬虫 , 回到开头的爬虫问题 , 如果我们希望爬取10个网页的评论 , 可能会先定一个空dataframe , 然后使用多线程都往这个dataframe中写入数据 , 但由于多个线程同时操作这一个变量 , 可能会导致评论并不是按照顺序写入的 。
例如第一个页面有10条评论 , 第一个线程写入了2条后 , 第二个线程将第二个页面的前两条写入 , 最终导致十个页面的评论是乱序存储!
让我们把这个问题抽象出来 , 还是之前的代码 , 稍微修改一下
人人都能学会的 Python 多线程指南
文章图片

文章图片
我们先定义了一个空list , 线程函数会将传入的数字添加到该list中 , 在未加锁的情况下 , 由于线程竞争 , 虽然我们线程是按照顺序开启 , 但是最终数字并不是按照顺序写入 。
有没有办法解决呢?当然有 , 很自然的想法就是当第一个线程操作该变量时 , 其他线程等着 , 写完了再释放 , 这就是锁!
先看代码
人人都能学会的 Python 多线程指南
文章图片

文章图片
在上面的代码中 , 我们使用 threding.Lock 创建了一个线程锁 , 之后在线程函数操作 result 前 , 首先使用 lock.acquire() 加上锁 , 之后操作 results, 在修改完后使用 lock.relese() 释放 , 此时其他线程若想操作 results 则会阻塞 , 等该线程释放后才能拿走操作中 , 这样我们就保证了线程是“安全的”!
最基本的线程锁用法就如上面代码所示 , 定义锁 --> 上锁 --> 解锁 , 但是一定要注意 , lock.acquire() 和 lock.relese() , 如果加了锁但是没有释放 , 后面的线程将会全部阻塞!
限制线程数量
最后还有一个常见的问题 , 上面我们需要执行几次线程函数就开了几个线程 , 但是如果需要爬成千上万个网页 , 开这么多线程cpu一定不同意 , 代码也会在开启的线程达到一定数量后报错 。
所以如何让程序只启动我们指定的线程数量 , 例如一次开五个线程 , 结束一个再添加一个 , 直到全部任务完成?
还是锁!在 threading 模块中有一个 BoundedSemaphore(信号量)类 , 我们可以给他一个初始的信号量(最大线程数) , 之后每次有线程获得信号量的时候(即 acquire() )计数器-1 , 释放信号量时候(release())计数器+1 , 计数器为0的时候其它线程就被阻塞无法获得信号量 。当计数器为设定好的上限的时候 BoundedSemaphore 就无法进行 release() 操作了 。
体现到代码上则比较简单 , 还是基于上面的例子修改
人人都能学会的 Python 多线程指南