• 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 等待所有线程结束。当第一个线程把所有数据都放到队列中,它也就运行结束了。

  • 相关阅读:
    input文本框输入限制(正则表达式)
    SQL Server通用型分页存储过程
    简单易学的数据图表
    HTML中input文本框只读不可编辑的方法
    SQL添加外键约束的方式
    +1 也要睁着眼
    博客园的自定义皮肤
    网站收集整理
    jQuery extend方法介绍
    HTML5本地存储
  • 原文地址:https://www.cnblogs.com/xhnxhnu/p/9736011.html
Copyright © 2020-2023  润新知