热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python多线程编程技巧与实战应用详解

篇首语:本文由编程笔记#小编为大家整理,主要介绍了python多线程创建与使用(转)相关的知识,希望对你有一定的参考价值。 原文:http://codingpy.com/article/python-

篇首语:本文由编程笔记#小编为大家整理,主要介绍了python多线程创建与使用(转)相关的知识,希望对你有一定的参考价值。


原文:http://codingpy.com/article/python-201-a-tutorial-on-threads/


创建多线程

创建多线程主要有2种方式。



  • 使用threading.Thread函数

  • 继承threading类


1. 使用threading.Thread函数

import threading
def tom(number):
print threading.currentThread().getName()
print number
if __name__ == "__main__":
number = ["zero", "one", "two", "three", "four"]
sex = ["man", "woman"]
for i in range(5):
th = threading.Thread(target=tom, args=(number[i],))
# th.setName('mythread')
# print th.getName()
th.start()

说明:Thread()函数有2个参数,一个是target,内容为子线程要执行的函数名称;另一个是args,内容为需要传递的参数。Args 参数看起来有些奇怪,那是因为我们需要传递一个序列给tom函数,但它只接受一个变量,所以我们把逗号放在尾部来创建只有一个参数的序列。创建完子线程,将会返回一个对象,调用对象的start方法,可以启动子线程。

当你运行以上这段代码,会得到以下输出:

Thread-1
zero
Thread-2
one
Thread-3
two
Thread-4
three
Thread-5
four

线程对象的方法:



  • Start() 开始线程的执行


  • Run() 定义线程的功能的函数


  • Join(timeout=None) 程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout秒


  • getName() 返回线程的名字


  • setName() 设置线程的名字


  • isAlive() 布尔标志,表示这个线程是否还在运行


  • isDaemon() 返回线程的daemon标志


  • setDaemon(daemonic) 把线程的daemon标志设为daemonic(一定要在start()函数前调用)


  • t.setDaemon(True) 把父线程设置为守护线程,当父进程结束时,子进程也结束



2. 继承threading类

import threading
class mythread(threading.Thread):
def __init__(self,number):
threading.Thread.__init__(self)
self.number = number
def run(self):
print threading.current_thread().getName()
print self.number
if __name__ == "__main__":
for i in range(5):
th = mythread(i)
th.start()

当你运行以上这段代码,会得到以下输出:

Thread-1
0
Thread-2
1
Thread-3
2
Thread-4
3
Thread-5
4

当然,通常情况下你不会希望输出打印到标准输出。如果不幸真的这么做了,那么最终的显示效果将会非常混乱。你应该使用 Python 的 logging 模块。它是线程安全的,并且表现出色。让我们用 logging 模块修改上面的例子并且给我们的线程命名。代码如下:

import threading
import logging
def get_logger():
#创建一个被设置为调试级别的日志记录器
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)

#设置每行日志的格式。格式包括时间戳、线程名、日志记录级别以及日志信息
fh = logging.FileHandler("threading.log")
fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)

logger.addHandler(fh)
return logger
def tom(number, logger):
logger.debug(number)
if __name__ == "__main__":
logger = get_logger()
number = ["zero", "one", "two", "three", "four"]
sex = ["man", "woman"]
for i in range(5):
th = threading.Thread(target=tom, args=(number[i],logger))
# th.setName('mythread')
# print th.getName()
th.start()

通过继承的方法:

import threading
import logging
class mythread(threading.Thread):
def __init__(self,number,logger):
threading.Thread.__init__(self)
self.number = number
self.logger = logger
def run(self):
self.logger.debug("calling-thread")
tom(self.number, self.logger)
def get_logger():
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler("threading.log")
fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)

logger.addHandler(fh)
return logger
def tom(number, logger):

if __name__ == "__main__":
logger = get_logger()
for i in range(5):
th = mythread(i, logger)
th.start()

在 tom 函数中,我们把 print 语句换成 logging 语句。你会注发现,在创建线程时,我们给 doubler 函数传入了 logger 对象。这样做的原因是,如果在每个线程中实例化 logging 对象,那么将会产生多个 logging 单例(singleton),并且日志中将会有很多重复的内容


线程锁与线程同步

由于物理上得限制,各CPU厂商在核心频率上的比赛已经被多核所取代。为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。锁由 Python 的 threading 模块提供,并且它最多被一个线程所持有。当一个线程试图获取一个已经锁在资源上的锁时,该线程通常会暂停运行,直到这个锁被释放。

有两种方式为线程加锁:



  1. try...finally


  2. with


代码如下:

import threading
import logging
lock = threading.Lock()
class mythread(threading.Thread):
def __init__(self,number,logger):
threading.Thread.__init__(self)
self.number = number
self.logger = logger

def run(self):
lock.acquire()
try:
self.logger.debug("calling-thread")
tom(self.number, self.logger)
finally:
lock.release()
def get_logger():
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler("threading.log")
fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)

logger.addHandler(fh)
return logger
def tom(number, logger):
with lock:
logger.debug(number)
if __name__ == "__main__":
logger = get_logger()
for i in range(5):
with lock:
th = mythread(i, logger)
th.start()

当你真正运行这段代码时,你会发现它只是挂起了。究其原因,是因为我们只告诉 threading 模块获取锁。所以当我们调用第一个函数时,它发现锁已经被获取,随后便把自己挂起了,直到锁被释放,然而这将永远不会发生。

真正的解决办法是使用重入锁(Re-Entrant Lock)。threading 模块提供的解决办法是使用 RLock 函数。即把 lock = threading.lock() 替换为 lock = threading.RLock(),然后重新运行代码,现在代码就可以正常运行了。


线程通信

某些情况下,你会希望线程之间互相通信。就像先前提到的,你可以通过创建 Event 对象达到这个目的。但更常用的方法是使用队列(Queue)。在我们的例子中,这两种方式都会有所涉及。下面让我们看看到底是什么样子的:

import threading
import Queue
def creator(data, q):
"""
生成用于消费的数据,等待消费者完成处理
"""
print('Creating data and putting it on the queue')
for item in data:
evt = threading.Event()
q.put((item, evt))

print('Waiting for data to be doubled')
evt.wait()
def my_consumer(q):
"""
消费部分数据,并做处理
这里所做的只是将输入翻一倍
"""
while True:
data, evt = q.get()
print('data found to be processed: {}'.format(data))
processed = data * 2
print(processed)
evt.set()
q.task_done()
if __name__ == '__main__':
q = Queue()
data = [5, 10, 13, -1]
thread_One= threading.Thread(target=creator, args=(data, q))
thread_two = threading.Thread(target=my_consumer, args=(q,))
thread_one.start()
thread_two.start()

q.join()

让我们掰开揉碎分析一下。首先,我们有一个创建者(creator)函数(亦称作生产者(producer)),我们用它来创建想要操作(或者消费)的数据。然后用另外一个函数 my_consumer 来处理刚才创建出来的数据。Creator 函数使用 Queue 的 put 方法向队列中插入数据,消费者将会持续不断的检测有没有更多的数据,当发现有数据时就会处理数据。Queue 对象处理所有的获取锁和释放锁的过程,这些不用我们太关心。

在这个例子中,先创建一个列表,然后创建两个线程,一个用作生产者,一个作为消费者。你会发现,我们给两个线程都传递了 Queue 对象,这两个线程隐藏了关于锁处理的细节。队列实现了数据从第一个线程到第二个线程的传递。当第一个线程把数据放入队列时,同时也传递一个 Event 事件,紧接着挂起自己,等待该事件结束。在消费者侧,也就是第二个线程,则做数据处理工作。当完成数据处理后就会调用 Event 事件的 set 方法,通知第一个线程已经把数据处理完毕了,可以继续生产了。

最后一行代码调用了 Queue 对象的 join 方法,它会告知 Queue 等待所有线程结束。当第一个线程把所有数据都放到队列中,它也就运行结束了。


推荐阅读
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文探讨了在Java多线程环境下,如何确保具有相同key值的线程能够互斥执行并按顺序输出结果。通过优化代码结构和使用线程安全的数据结构,我们解决了线程同步问题,并实现了预期的并发行为。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 在多线程编程环境中,线程之间共享全局变量可能导致数据竞争和不一致性。为了解决这一问题,Linux提供了线程局部存储(TLS),使每个线程可以拥有独立的变量副本,确保线程间的数据隔离与安全。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • FinOps 与 Serverless 的结合:破解云成本难题
    本文探讨了如何通过 FinOps 实践优化 Serverless 应用的成本管理,提出了首个 Serverless 函数总成本估计模型,并分享了多种有效的成本优化策略。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本文介绍了如何在C#中启动一个应用程序,并通过枚举窗口来获取其主窗口句柄。当使用Process类启动程序时,我们通常只能获得进程的句柄,而主窗口句柄可能为0。因此,我们需要使用API函数和回调机制来准确获取主窗口句柄。 ... [详细]
  • 本文详细解析了Python中的os和sys模块,介绍了它们的功能、常用方法及其在实际编程中的应用。 ... [详细]
author-avatar
书友16941424_529
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有