多线程实践—Python多线程编程

多线程实践

前面的一些文章和脚本都是只能做学习多线程的原理使用,实际上什么有用的事情也没有做。接下来进行多线程的实践,看一看在实际项目中是怎么使用多线程的。

图书排名示例

Bookrank.py:

该脚本通过单线程进行下载图书排名信息的调用

?
 1 from atexit import register
 2 from re import compile
 3 from threading import Thread
 4 from time import sleep, ctime
 5 import requests
 6 ?
 7 REGEX = compile(‘#([\d,]+) in Books‘)
 8 AMZN = ‘https://www.amazon.com/dp/‘
 9 ISBNS = {
10     ‘0132269937‘: ‘Core Python Programming‘,
11     ‘0132356139‘: ‘Python Web Development with Django‘,
12     ‘0137143419‘: ‘Python Fundamentals‘,
13 }
14 ?
15 def getRanking(isbn):
16     url = ‘%s%s‘ % (AMZN, isbn)
17     page = requests.get(url)
18     data = page.text
19     return REGEX.findall(data)[0]
20 ?
21 def _showRanking(isbn):
22     print ‘- %r ranked %s‘ % (
23         ISBNS[isbn], getRanking(isbn))
24 ?
25 def _main():
26     print ‘At‘, ctime(), ‘on Amazon‘
27     for isbn in ISBNS:
28         _showRanking(isbn)
29 ?
30 @register
31 def _atexit():
32     print ‘all DONE at:‘, ctime()
33 ?
34 if __name__ == ‘__main__‘:
35     _main()
36 ?
 

输出结果为:

1 /usr/bin/python ~/Test_Temporary/bookrank.py
2 At Sat Jul 28 17:16:51 2018 on Amazon
3 - ‘Core Python Programming‘ ranked 322,656
4 - ‘Python Fundamentals‘ ranked 4,739,537
5 - ‘Python Web Development with Django‘ ranked 1,430,855
6 all DONE at: Sat Jul 28 17:17:08 2018
 

引入线程

上面的例子只是一个单线程程序,下面引入线程,并使用多线程再执行程序对比各自所需的时间。

? 将上面脚本中 _main() 函数的 _showRanking(isbn) 修改以下代码:

?

Thread(target=_showRanking, args=(isbn,)).start()

再次执行查看返回结果:

1 /usr/bin/python ~/Test_Temporary/bookrank.py
2 At Sat Jul 28 17:39:16 2018 on Amazon
3 - ‘Python Fundamentals‘ ranked 4,739,537
4 - ‘Python Web Development with Django‘ ranked 1,430,855
5 - ‘Core Python Programming‘ ranked 322,656
6 all DONE at: Sat Jul 28 17:39:19 2018

从两个的输出结果中可以看出,使用单线程时总体完成的时间为 7s ,而使用多线程时,总体完成时间为 3s 。另外一个需要注意的是,单线程版本是按照变量的顺序输出,而多线程版本按照完成的顺序输出。

同步原语

一般在多线程代码中,总会有一些特定的函数或代码块不希望(或不应该)被多个线程同时执行,通常包括修改数据库、更新文件或其它会产生竟态条件的类似情况。这就是需要使用同步的情况。

  • 当任意数量的线程可以访问临界区的代码,但给定的时刻只有一个线程可以通过时,就是使用同步的时候了;
  • 程序员选择适合的同步原语,或者线程控制机制来执行同步;
  • 进程同步有不同的类型【参见:https://en.wikipedia.org/wiki/Synchronization_(computer_science) 】
  • 同步原语有:锁/互斥、信号量。锁是最简单、最低级的机制,而信号量用于多线程竞争有限资源的情况。

锁示例

锁有两种状态:锁定和未锁定。而且它也只支持两个函数:获得锁和释放锁。

  • 当多线程争夺锁时,允许第一个获得锁的线程进入临界区,并执行代码;
  • 所有之后到达的线程将被阻塞,直到第一个线程结束退出临界区并释放锁;
  • 锁被释放后,其它等待的线程可以继续争夺锁,并进入临界区;
  • 被阻塞的线程没有顺序,不会先到先得,胜出的线程是不确定的。

代码示例(mtsleepF.py):

*注:该脚本派生了随机数量的线程,每个线程执行结束时会进行输出

 1 # -*- coding=utf-8 -*-
 2 from atexit import register
 3 from random import randrange
 4 from threading import Thread, currentThread
 5 from time import sleep, ctime
 6 ?
 7 class CleanOutputSet(set):
 8     def __str__(self):
 9         return ‘, ‘.join(x for x in self)
10 ?
11 loops = (randrange(2, 5) for x in range(randrange(3, 7)))
12 remaining = CleanOutputSet()
13 ?
14 def loop(nsec):
15     myname = currentThread().name
16     remaining.add(myname)
17     print(‘这个是目前线程池中的线程:‘, remaining)
18     print(‘[%s] Started %s‘ % (ctime(), myname))
19     sleep(nsec)
20     remaining.remove(myname)
21     print(‘[%s] Completed %s (%d secs)‘ % (ctime(), myname, nsec))
22     print(‘ (remaining: %s)‘ % (remaining or ‘None‘))
23 ?
24 def _main():
25     for pause in loops:
26         Thread(target=loop, args=(pause,)).start()
27 ?
28 @register
29 def _atexit():
30     print(‘all DONE at:%s‘ % ctime())
31 ?
32 if __name__ == ‘__main__‘:
33     _main()
 

执行后的输出结果:

 1 /usr/local/bin/python3.6 /Users/zhenggougou/Project/Test_Temporary/mtsleepF.py
 2 这个是目前线程池中的线程: Thread-1
 3 [Sat Jul 28 21:09:44 2018] Started Thread-1
 4 这个是目前线程池中的线程: Thread-2, Thread-1
 5 [Sat Jul 28 21:09:44 2018] Started Thread-2
 6 这个是目前线程池中的线程: Thread-3, Thread-2, Thread-1
 7 [Sat Jul 28 21:09:44 2018] Started Thread-3
 8 这个是目前线程池中的线程: Thread-3, Thread-2, Thread-4, Thread-1
 9 [Sat Jul 28 21:09:44 2018] Started Thread-4
10 这个是目前线程池中的线程: Thread-5, Thread-4, Thread-3, Thread-2, Thread-1
11 [Sat Jul 28 21:09:44 2018] Started Thread-5
12 这个是目前线程池中的线程: Thread-5, Thread-6, Thread-4, Thread-3, Thread-2, Thread-1
13 [Sat Jul 28 21:09:44 2018] Started Thread-6
14 [Sat Jul 28 21:09:46 2018] Completed Thread-2 (2 secs)
15 [Sat Jul 28 21:09:46 2018] Completed Thread-1 (2 secs)
16 [Sat Jul 28 21:09:46 2018] Completed Thread-3 (2 secs)
17  (remaining: Thread-5, Thread-6, Thread-4)
18 [Sat Jul 28 21:09:46 2018] Completed Thread-6 (2 secs)
19  (remaining: Thread-5, Thread-4)
20 [Sat Jul 28 21:09:46 2018] Completed Thread-4 (2 secs)
21  (remaining: Thread-5)
22  (remaining: Thread-5)
23 [Sat Jul 28 21:09:46 2018] Completed Thread-5 (2 secs)
24  (remaining: None)
25  (remaining: None)
26 all DONE at:Sat Jul 28 21:09:46 2018

从执行结果中可以看出,有的时候可能会存在多个线程并行执行操作删除 remaining 集合中数据的情况。比如上面结果中,线程1、2、3 就是同时执行去删除集合中数据的。所以为了避免这种情况需要加锁,通过引入 Lock (或 RLock),然后创建一个锁对象来保证数据的修改每次只有一个线程能操作。

  1. 首先先导入锁类,然后创建锁对象

    from threading import Thread, Lock, currentThread

    lock = Lock()

  2. 然后使用创建的锁,将上面 mtsleepF.py 脚本中 loop() 函数做以下改变:
     1 def loop(nsec):
     2     myname = currentThread().name
     3     lock.acquire() # 获取锁
     4     remaining.add(myname)
     5     print(‘这个是目前线程池中的线程:‘, remaining)
     6     print(‘[%s] Started %s‘ % (ctime(), myname))
     7     lock.release() # 释放锁
     8     sleep(nsec)
     9     lock.acquire() # 获取锁
    10     remaining.remove(myname)
    11     print(‘[%s] Completed %s (%d secs)‘ % (ctime(), myname, nsec))
    12     print(‘ (remaining: %s)‘ % (remaining or ‘None‘))
    13     lock.release() # 释放锁

在操作变量的前后需要进行获取锁和释放锁的操作,以保证在修改变量时只有一个线程进行。上面的代码有两处修改变量,一是:remaining.add(myname) ,二是:remaining.remove(myname)。 所以上面代码中有两次获取锁和释放锁的操作。其实还有一种方案可以不再调用锁的 acquire()release() 方法,二是使用上下文管理,进一步简化代码。代码如下:

 1 def loop(nesc):
 2     myname = currentThread().name
 3     with lock:
 4         remaining.add(myname)
 5         print(‘[{0}] Started {1}‘.format(ctime(), myname))
 6     sleep(nesc)
 7     with lock:
 8         remaining.remove(myname)
 9         print(‘[{0}] Completed {1} ({2} secs)‘.format(ctime(), myname, nesc))
10         print(‘ (remaining: {0})‘.format(remaining or ‘None‘))
 

信号量示例

锁非常易于理解和实现,也很容易决定何时需要它们,然而,如果情况更加复杂,可能需要一个更强大的同步原语来代替锁。

信号量是最古老的同步原语之一。它是一个计数器,当资源消耗时递减,当资源释放时递增。可以认为信号量代表它们的资源可用或不可用。信号量比锁更加灵活,因为可以有多个线程,每个线程都拥有有限资源的一个实例。

  • 消耗资源使计数器递减的操作习惯上称为 P() —— acquire ;
  • 当一个线程对一个资源完成操作时,该资源需要返回资源池中,这个操作一般称为 V() —— release 。

示例,糖果机和信号量(candy.py):

*注:该脚本使用了锁和信号量来模拟一个糖果机

 1 # -*- coding=utf-8 -*-
 2 from atexit import register
 3 from random import randrange
 4 from threading import BoundedSemaphore, Lock, Thread
 5 from time import sleep, ctime
 6 ?
 7 lock = Lock()
 8 MAX = 5
 9 candytray = BoundedSemaphore(MAX)
10 ?
11 def refill():
12     lock.acquire()
13     print(‘Refilling candy‘)
14     try:
15         candytray.release() # 释放资源
16     except ValueError:
17         print(‘full, skipping‘)
18     else:
19         print(‘OK‘)
20     lock.release()
21 ?
22 def buy():
23     lock.acquire()
24     print(‘Buying candy...‘)
25     if candytray.acquire(False): # 消耗资源
26         print(‘OK‘)
27     else:
28         print(‘empty, skipping‘)
29     lock.release()
30 ?
31 def producer(loops):
32     for i in range(loops):
33         refill()
34         sleep(randrange(3))
35 ?
36 def consumer(loops):
37     for i in range(loops):
38         buy()
39         sleep(randrange(3))
40 ?
41 def _main():
42     print(‘starting at:{0}‘.format(ctime()))
43     nloops = randrange(2, 6)
44     print(‘THE CANDY MACHINE (full with %d bars)!‘ % MAX)
45     Thread(target=consumer, args=(randrange(nloops, nloops+MAX+2),)).start()
46     Thread(target=producer, args=(nloops,)).start()
47 ?
48 @register
49 def _atexit():
50     print(‘all DONE at:{0}‘.format(ctime()))
51 ?
52 if __name__ == ‘__main__‘:
53     _main()

执行结果为:

 1 /usr/local/bin/python3.6 ~/Test_Temporary/candy.py
 2 starting at:Sun Jul 29 21:12:50 2018
 3 THE CANDY MACHINE (full with 5 bars)!
 4 Buying candy...
 5 OK
 6 Refilling candy
 7 OK
 8 Refilling candy
 9 full, skipping
10 Buying candy...
11 OK
12 Buying candy...
13 OK
14 all DONE at:Sun Jul 29 21:12:52 2018

原文地址:https://www.cnblogs.com/tester-xt/p/9387971.html

时间: 2024-11-12 03:41:30

多线程实践—Python多线程编程的相关文章

Linux多线程实践(7) --多线程排序对比

屏障 int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count); int pthread_barrier_destroy(pthread_barrier_t *barrier); int pthread_barrier_wait(pthread_barrier_t *barrier); 屏障允许任意数量的线程等待

python 多线程编程

一)线程基础 1.创建线程: thread模块提供了start_new_thread函数,用以创建线程.start_new_thread函数成功创建后还能够对其进行操作. 其函数原型: start_new_thread(function,atgs[,kwargs]) 其參数含义例如以下: function: 在线程中运行的函数名 args:元组形式的參数列表. kwargs: 可选參数,以字典的形式指定參数 方法一:通过使用thread模块中的函数创建新线程. >>> import th

python-学习-python并发编程之多进程与多线程

一 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程.Python提供了multiprocessing.    multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似.  multiprocessing模块的功能众多:支持子进程.通信和共享数据.执行不同形式的同步,

深入 HTML5 Web Worker 应用实践:多线程编程

深入 HTML5 Web Worker 应用实践:多线程编程 HTML5 中工作线程(Web Worker)简介 至 2008 年 W3C 制定出第一个 HTML5 草案开始,HTML5 承载了越来越多崭新的特性和功能.它不但强化了 Web 系统或网页的表现性能,而且还增加了对本地数据库等 Web 应用功能的支持.其中,最重要的一个便是对多线程的支持.在 HTML5 中提出了工作线程(Web Worker)的概念,并且规范出 Web Worker 的三大主要特征:能够长时间运行(响应),理想的启

Python多线程编程

原文 运行几个线程和同时运行几个不同的程序类似,它有以下好处: 一个进程内的多个线程和主线程分享相同的数据空间,比分开不同的过程更容易分享信息或者彼此通信. 线程有时叫做轻量化过程,而且他们不要求更多的内存开支:它们比过程便宜. 一个线程的顺序是:启动,执行和停止.有一个指令指针跟踪线程正在运行的上下文在哪里. 它可以被抢占(中断) 它能暂时被挂起(也叫做休眠),而别的线程在运行--这也叫做yielding(让步). 开始一个新线程: 要生成一个线程,需要调用在thread模块中方法如下: th

python并发编程&多线程(一)

本篇理论居多,实际操作见:  python并发编程&多线程(二) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线 流水线的工作需要电源,电源就相当于cpu 所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位. 多线程(即多个控制线程)的概念

python并发编程&多线程(二)

前导理论知识见:python并发编程&多线程(一) 一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 官网链接:https://docs.python.org/3/library/threading.html?highlight=threading#(装B模式加载中…………) 二 开启线程的两种方式  方式一  方式二 三 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别  1 谁的开启速度快  2 瞅

python并发编程之多线程理论部分

阅读目录 一 什么是线程 二 线程的创建开销小 三 线程与进程的区别 四 为何要用多线程 五 多线程的应用举例 六 经典的线程模型(了解) 七 POSIX线程(了解) 八 在用户空间实现的线程(了解) 九 在内核空间实现的线程(了解) 十 用户级与内核级线程的对比(了解) 十一 混合实现(了解) 一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合到

day-3 聊聊python多线程编程那些事

python一开始给我的印象是容易入门,适合应用开发,编程简洁,第三方库多等等诸多优点,并吸引我去深入学习.直到学习完多线程编程,在自己环境上验证完这句话:python解释器引入GIL锁以后,多CPU场景下,也不再是并行方式运行,甚至比串行性能更差.不免有些落差,一开始就注定了这门语言迟早是有天花板的,对于一些并行要求高的系统,python可能不再成为首选,甚至是完全不考虑.但是事情也并不是绝对悲观的,我们已经看到有一大批人正在致力优化这个特性,新版本较老版本也有了一定改进,一些核心模块我们也可