多线程实践—Python多线程编程
多线程实践
前面的一些文章和脚本都是只能做学习多线程的原理使用,实际上什么有用的事情也没有做。接下来进行多线程的实践,看一看在实际项目中是怎么使用多线程的。
图书排名示例
Bookrank.py:
该脚本通过单线程进行下载图书排名信息的调用
from atexit import register
from re import compile
from threading import Thread
from time import sleep, ctime
import requests
REGEX = compile('#([\d,]+) in Books')
AMZN = 'https://www.amazon.com/dp/'
ISBNS = {
'': 'Core Python Programming',
'': 'Python Web Development with Django',
'': 'Python Fundamentals',
}
def getRanking(isbn):
url = '%s%s' % (AMZN, isbn)
page = requests.get(url)
data = page.text
return REGEX.findall(data)[0]
def _showRanking(isbn):
print '- %r ranked %s' % (
ISBNS[isbn], getRanking(isbn))
def _main():
print 'At', ctime(), 'on Amazon'
for isbn in ISBNS:
_showRanking(isbn)
@register
def _atexit():
print 'all DONE at:', ctime()
if __name__ == '__main__':
_main()
输出结果为:
/usr/bin/python ~/Test_Temporary/bookrank.py
At Sat Jul 28 17:16:51 2018 on Amazon
- 'Core Python Programming' ranked 322,656
- 'Python Fundamentals' ranked 4,739,537
- 'Python Web Development with Django' ranked 1,430,855
all DONE at: Sat Jul 28 17:17:08 2018
引入线程
上面的例子只是一个单线程程序,下面引入线程,并使用多线程再执行程序对比各自所需的时间。
将上面脚本中 _main() 函数的 _showRanking(isbn) 修改以下代码:
Thread(target=_showRanking, args=(isbn,)).start()
再次执行查看返回结果:
/usr/bin/python ~/Test_Temporary/bookrank.py
At Sat Jul 28 17:39:16 2018 on Amazon
- 'Python Fundamentals' ranked 4,739,537
- 'Python Web Development with Django' ranked 1,430,855
- 'Core Python Programming' ranked 322,656
all DONE at: Sat Jul 28 17:39:19 2018
从两个的输出结果中可以看出,使用单线程时总体完成的时间为 7s ,而使用多线程时,总体完成时间为 3s 。另外一个需要注意的是,单线程版本是按照变量的顺序输出,而多线程版本按照完成的顺序输出。
同步原语
一般在多线程代码中,总会有一些特定的函数或代码块不希望(或不应该)被多个线程同时执行,通常包括修改数据库、更新文件或其它会产生竟态条件的类似情况。这就是需要使用同步的情况。
当任意数量的线程可以访问临界区的代码,但给定的时刻只有一个线程可以通过时,就是使用同步的时候了;
程序员选择适合的同步原语,或者线程控制机制来执行同步;
进程同步有不同的类型【参见:https://en.wikipedia.org/wiki/Synchronization_(computer_science) 】
同步原语有:锁/互斥、信号量。锁是最简单、最低级的机制,而信号量用于多线程竞争有限资源的情况。
锁示例
锁有两种状态:锁定和未锁定。而且它也只支持两个函数:获得锁和释放锁。
当多线程争夺锁时,允许第一个获得锁的线程进入临界区,并执行代码;
所有之后到达的线程将被阻塞,直到第一个线程结束退出临界区并释放锁;
锁被释放后,其它等待的线程可以继续争夺锁,并进入临界区;
被阻塞的线程没有顺序,不会先到先得,胜出的线程是不确定的。
代码示例(mtsleepF.py):
*注:该脚本派生了随机数量的线程,每个线程执行结束时会进行输出
# -*- coding=utf-8 -*-
from atexit import register
from random import randrange
from threading import Thread, currentThread
from time import sleep, ctime
class CleanOutputSet(set):
def __str__(self):
return ', '.join(x for x in self)
loops = (randrange(2, 5) for x in range(randrange(3, 7)))
remaining = CleanOutputSet()
def loop(nsec):
myname = currentThread().name
remaining.add(myname)
print('这个是目前线程池中的线程:', remaining)
print('[%s] Started %s' % (ctime(), myname))
sleep(nsec)
remaining.remove(myname)
print('[%s] Completed %s (%d secs)' % (ctime(), myname, nsec))
print(' (remaining: %s)' % (remaining or 'None'))
def _main():
for pause in loops:
Thread(target=loop, args=(pause,)).start()
@register
def _atexit():
print('all DONE at:%s' % ctime())
if __name__ == '__main__':
_main()
执行后的输出结果:
/usr/local/bin/python3.6 /Users/zhenggougou/Project/Test_Temporary/mtsleepF.py
这个是目前线程池中的线程: Thread-1
[Sat Jul 28 21:09:44 2018] Started Thread-1
这个是目前线程池中的线程: Thread-2, Thread-1
[Sat Jul 28 21:09:44 2018] Started Thread-2
这个是目前线程池中的线程: Thread-3, Thread-2, Thread-1
[Sat Jul 28 21:09:44 2018] Started Thread-3
这个是目前线程池中的线程: Thread-3, Thread-2, Thread-4, Thread-1
[Sat Jul 28 21:09:44 2018] Started Thread-4
这个是目前线程池中的线程: Thread-5, Thread-4, Thread-3, Thread-2, Thread-1
[Sat Jul 28 21:09:44 2018] Started Thread-5
这个是目前线程池中的线程: Thread-5, Thread-6, Thread-4, Thread-3, Thread-2, Thread-1
[Sat Jul 28 21:09:44 2018] Started Thread-6
[Sat Jul 28 21:09:46 2018] Completed Thread-2 (2 secs)
[Sat Jul 28 21:09:46 2018] Completed Thread-1 (2 secs)
[Sat Jul 28 21:09:46 2018] Completed Thread-3 (2 secs)
(remaining: Thread-5, Thread-6, Thread-4)
[Sat Jul 28 21:09:46 2018] Completed Thread-6 (2 secs)
(remaining: Thread-5, Thread-4)
[Sat Jul 28 21:09:46 2018] Completed Thread-4 (2 secs)
(remaining: Thread-5)
(remaining: Thread-5)
[Sat Jul 28 21:09:46 2018] Completed Thread-5 (2 secs)
(remaining: None)
(remaining: None)
all DONE at:Sat Jul 28 21:09:46 2018
从执行结果中可以看出,有的时候可能会存在多个线程并行执行操作删除 remaining 集合中数据的情况。比如上面结果中,线程1、2、3 就是同时执行去删除集合中数据的。所以为了避免这种情况需要加锁,通过引入 Lock (或 RLock),然后创建一个锁对象来保证数据的修改每次只有一个线程能操作。
首先先导入锁类,然后创建锁对象
from threading import Thread, Lock, currentThreadlock = Lock()然后使用创建的锁,将上面 mtsleepF.py 脚本中 loop() 函数做以下改变:
def loop(nsec):
myname = currentThread().name
lock.acquire() # 获取锁
remaining.add(myname)
print('这个是目前线程池中的线程:', remaining)
print('[%s] Started %s' % (ctime(), myname))
lock.release() # 释放锁
sleep(nsec)
lock.acquire() # 获取锁
remaining.remove(myname)
print('[%s] Completed %s (%d secs)' % (ctime(), myname, nsec))
print(' (remaining: %s)' % (remaining or 'None'))
lock.release() # 释放锁
在操作变量的前后需要进行获取锁和释放锁的操作,以保证在修改变量时只有一个线程进行。上面的代码有两处修改变量,一是:remaining.add(myname) ,二是:remaining.remove(myname)。 所以上面代码中有两次获取锁和释放锁的操作。其实还有一种方案可以不再调用锁的 acquire() 和 release() 方法,二是使用上下文管理,进一步简化代码。代码如下:
def loop(nesc):
myname = currentThread().name
with lock:
remaining.add(myname)
print('[{0}] Started {1}'.format(ctime(), myname))
sleep(nesc)
with lock:
remaining.remove(myname)
print('[{0}] Completed {1} ({2} secs)'.format(ctime(), myname, nesc))
print(' (remaining: {0})'.format(remaining or 'None'))
信号量示例
锁非常易于理解和实现,也很容易决定何时需要它们,然而,如果情况更加复杂,可能需要一个更强大的同步原语来代替锁。
信号量是最古老的同步原语之一。它是一个计数器,当资源消耗时递减,当资源释放时递增。可以认为信号量代表它们的资源可用或不可用。信号量比锁更加灵活,因为可以有多个线程,每个线程都拥有有限资源的一个实例。
消耗资源使计数器递减的操作习惯上称为 P() —— acquire ;
当一个线程对一个资源完成操作时,该资源需要返回资源池中,这个操作一般称为 V() —— release 。
示例,糖果机和信号量(candy.py):
*注:该脚本使用了锁和信号量来模拟一个糖果机
# -*- coding=utf-8 -*-
from atexit import register
from random import randrange
from threading import BoundedSemaphore, Lock, Thread
from time import sleep, ctime
lock = Lock()
MAX = 5
candytray = BoundedSemaphore(MAX)
def refill():
lock.acquire()
print('Refilling candy')
try:
candytray.release() # 释放资源
except ValueError:
print('full, skipping')
else:
print('OK')
lock.release()
def buy():
lock.acquire()
print('Buying candy...')
if candytray.acquire(False): # 消耗资源
print('OK')
else:
print('empty, skipping')
lock.release()
def producer(loops):
for i in range(loops):
refill()
sleep(randrange(3))
def consumer(loops):
for i in range(loops):
buy()
sleep(randrange(3))
def _main():
print('starting at:{0}'.format(ctime()))
nloops = randrange(2, 6)
print('THE CANDY MACHINE (full with %d bars)!' % MAX)
Thread(target=consumer, args=(randrange(nloops, nloops+MAX+2),)).start()
Thread(target=producer, args=(nloops,)).start()
@register
def _atexit():
print('all DONE at:{0}'.format(ctime()))
if __name__ == '__main__':
_main()
执行结果为:
/usr/local/bin/python3.6 ~/Test_Temporary/candy.py
starting at:Sun Jul 29 21:12:50 2018
THE CANDY MACHINE (full with 5 bars)!
Buying candy...
OK
Refilling candy
OK
Refilling candy
full, skipping
Buying candy...
OK
Buying candy...
OK
all DONE at:Sun Jul 29 21:12:52 2018
多线程实践—Python多线程编程的更多相关文章
- Linux多线程实践(7) --多线程排序对比
屏障 int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restri ...
- python 并发编程 多线程 目录
线程理论 python 并发编程 多线程 开启线程的两种方式 python 并发编程 多线程与多进程的区别 python 并发编程 多线程 Thread对象的其他属性或方法 python 并发编程 多 ...
- 初识python多线程
目录 GIL锁 Thread类构造方法 Lock类.Rlock类 参考: python3多线程--官方教程中文版 python多线程-1 python多线程-2.1 python多线程-2.2 pyt ...
- Python网络编程—socket(二)
http://www.cnblogs.com/phennry/p/5645369.html 接着上篇博客我们继续介绍socket网络编程,今天主要介绍的内容:IO多路复用.多线程.补充知识点. 一.I ...
- Python - 并发编程,多进程,多线程
传送门 https://blog.csdn.net/jackfrued/article/details/79717727 在此基础上实践和改编某些点 1. 并发编程 实现让程序同时执行多个任务也就是常 ...
- python多线程编程
Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join( ...
- 关于python多线程编程中join()和setDaemon()的一点儿探究
关于python多线程编程中join()和setDaemon()的用法,这两天我看网上的资料看得头晕脑涨也没看懂,干脆就做一个实验来看看吧. 首先是编写实验的基础代码,创建一个名为MyThread的 ...
- 深入 HTML5 Web Worker 应用实践:多线程编程
深入 HTML5 Web Worker 应用实践:多线程编程 HTML5 中工作线程(Web Worker)简介 至 2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越 ...
- day-3 python多线程编程知识点汇总
python语言以容易入门,适合应用开发,编程简洁,第三方库多等等诸多优点,并吸引广大编程爱好者.但是也存在一个被熟知的性能瓶颈:python解释器引入GIL锁以后,多CPU场景下,也不再是并行方式运 ...
随机推荐
- 关于JS垃圾回收机制
一.垃圾回收机制的必要性 由于字符串.对象和数组没有固定大小,所以当它们的大小已知时,才能对它们进行动态的存储分配.JavaScript程序每次创建字符串.数组或对象时,解释器都必须分配内存来存储那个 ...
- Buu刷题
前言 希望自己能够更加的努力,希望通过多刷大赛题来提高自己的知识面.(ง •_•)ง easy_tornado 进入题目 看到render就感觉可能是模板注入的东西 hints.txt给出提示,可以看 ...
- SpringBoot与单元测试JUnit的结合
有些人认为,写单元测试就是在浪费时间 ,写完代码,依然还是能够进行测试的.但是,还是建议写单元测试的,可以让你的条理更加清晰,而且当某个功能出现问题时,可能通过单元测试很容易的定位和解决问题.本文主要 ...
- Java 多线程实现方式一:继承Thread类
java 通过继承Thread类实现多线程很多简单: 只需要重写run方法即可. 比如我们分三个线程去京东下载三张图片: 1.先写个下载类: 注意导入CommonsIO 包 public class ...
- 尤雨溪在直播中讲到的Vue3.0 Beta的那些特性,快记笔记了
前言 在那天风雨交加的夜晚,Vue的创作者尤雨溪尤大大在b站直播分享了Vue.js 3.0 Beta最新进展.我对直播的内容进行了一下整理.整整用了三天的空余时间赶上了 1. 全新文档RFCs Vue ...
- 算法笔记刷题1(codeup 1934)
准备6月份的拼题甲级中(本来现在这两天就考试了,但是因为疫情的原因延期了) 刚刚开始按算法笔记刷题,今天是探索codeup的第一天. 一开始并没有把多点测试当回事,直到一错再错,心态爆炸... 附上我 ...
- python操作ftp文件
from ftplib import FTP ftp = FTP('ftp.abc.com') ftp.login(user='username', passwd='********') ftp.cw ...
- 中国AI觉醒 阿里王坚:云智能将成为大趋势
2019独角兽企业重金招聘Python工程师标准>>> <麻省理工科技评论>新兴科技峰会EmTech China于北京召开.大会中,其中一项热门的讨论便是:中国和美国的科 ...
- FileZilla更新服务器文件后浏览器没有刷新的一种常见情况
一.问题描述 在使用FileZilla更新服务器文件时,常出现的一种情况是: 刷新浏览器,发现该网页并未更新.但是检查网页源代码可以发现文件已经更新了,这就奇怪了,是服务器出了问题吗?还是FileZi ...
- 数学--数论--HDU 2582 F(N) 暴力打表找规律
This time I need you to calculate the f(n) . (3<=n<=1000000) f(n)= Gcd(3)+Gcd(4)+-+Gcd(i)+-+Gc ...