也谈Python多线程(实例讲解)

结合最近一段时间,对python多线程的了解和使用,现把对python多线程的体会用实例的形式展现出来,python的GIL(全局解释锁)一直倍受诟病,它使得python的多线程不能像c++/java 那样百分百的利用处理器多核,因此用python来实现多线程并发,效果并不一定比单线程处理效果好,尤其是计算密集型任务(CPU密集),这里引用知乎关于此问题的回复:[python多线程就像是一个游泳池的多个放水口,但是同时只能有一个放水口打开,虽然开起来是多个放水口同时放水,实际上仍然是不同线程之间来回切换运行,其实际效果可能还不如只打开一个放水口工作,因为存在context switch 的问题],因此对于CPU密集型任务,若采用并发完成,可以使用多进程实现,对于IO密集型任务,python多线程并发效果比单线程要好得多,因为context switch 的花销比IO等待花销小得多,而且多个线程可以防止IO阻塞的问题。

1 python多线程的实现

python标准库提供了两个模块实现多线程:_thread 和 threading ,threading 对 _thread 进行了封装,更容易实现,所以推荐使用threading 模块,通过一个简单实例,说明threading模块实现多线程的一些常用用法,代码如下:

#coding:utf-8

import threading
import time

class Thread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self._num = num
        self.setDaemon(True)
    def run(self):
        global total,multex

        print threading.currentThread().getName()

        for x in xrange(0,int(self._num)):
            mutex.acquire()
            total += 1
            if total == 3000:
                print 'now:',total
                raw_input('> 暂停一会,按任意键继续: ')

            mutex.release()
if __name__ == '__main__':
    global total,mutex
    total = 0
    mutex = threading.Lock()

    threads = []
    for x in xrange(0,40):
        threads.append(Thread(100))
    for t in threads:
        t.start()
        t.join()

    print total

此段代码实现了创建40个线程,每个线程对全局变量增加固定的值,最后全部子线程执行完毕,打印出全局变量的计数值,线程运行间通过input()函数可以实现对执行任务的暂停和回复。

使用之前先导入模块,为了实现多线程代码的可复用性,一般将其封装成一个类, class Thread(threading.Thread):这个类继承自threading模块的Thread类,接下来,对类初始化,调用父类的初始化函数threading.Thread.init(self),以及一些参数的初始化工作,self.setDaemon(True)这里是设置子线程为守护线程,必须在.start()启动线程之前运行,所以我们把它在类初始化阶段完成,效果就是设置成了守护线程即使子线程没有结束,只要主线程运行完,子线程也被kill掉,设置守护线程防止程序 被永久挂起(如果某个子线程由于某种原因永久执行下去),def run(self):这里是定义子线程执行的函数,需要通过重写run函数的方式 实现,更为通用一点的做法是在初始化的时候穿进来一个函数,然后每个在 run方法里运行这个函数,下边例子会介绍;mutex = threading.Lock() 这条语句是设置线程锁,因为并发运算各个线程之间通信是需要非常小心的,尤其写入文件、操作全局变量等,同时只能有一个线程获得写权限,要实现这样就得对线程 加锁,在写操作之前mutex.acquire()获得锁,操作之后mutex.release()释放锁,允许其他线程继续操作,锁机制是能保证线程间正常运转的方法,但是会损失掉多线程的效率,因为锁机制下实际上是串行写入文件的。接下来,就是在程序中实例化Thread,一般我么创建一个线程池,来批量管理子线程,threads = []创建线程池,threads.append(Thread(100))初始化Thread类并添加进线程池,最后在批量启动线程.start() 和 .join() .join()的作用是在子线程完成之前父线程处于阻塞状态等待全部子线程完成再运行。

2 python多线程的实际应用场景

实际需求是这样的:现在我有大量的代理ip地址和端口,需要向某个网站发送请求来判断该ip地址是否可用,由于ip数量众多,而且IO等待时间很长,再加上单线程很容易出现IO阻塞的问题,综上,解决这个需求采用单线程是相当抓狂的。。。,因此,必然会想到开多线程来同时验证,但是问题来了,多个线程同时操作这些IP,就会涉及到线程间如何通信的问题,一个简单清晰的做法就是使用队列Queue,即:通过给不同的子线程传递一个全局队列,这里当然就是ip地址了,子线程每取走一个ip就会从队列中删除这个ip,这样给子线程分配任务的时候就不会重复了,接下来,如果队列不为空则子线程继续执行,知道队列为空,子线程结束,表明所有的ip已经验证完毕。思路是这样,把这个功能封装进一个detect()类,方便其他程序调用和修改,具体得代码实现如下:

#coding:utf-8

import socket
import urllib2
from spider.parser import Parser
import threading

#ip_queue = Parser.ip_queue
class Detector(object):
    def __init__(self,ip_queue):
        self.detect_url = 'http://ip.chinaz.com/getip.aspx'
        socket.setdefaulttimeout(2)
        self.ip_queue = ip_queue

    def detect(self):
        try:
            value = self.ip_queue.get_nowait()
            proxy_host ="http://ha:ha@"+value['ip']+':'+value['port']
            response = urllib2.urlopen(self.detect_url, proxies={"http": proxy_host})
            if response.getcode() == 200:
                #goodNum += 1
                print value['ip'],'good'
                return True
            else:
                #badNum += 1
                print value['ip'],'bad'
                return False
        except Exception,e:
            #print ip,'req error',response.getcode()
            #badNum += 1
            return False
    def run_detect(self,ip_queue):
        badNum = 0
        goodNum = 0
        mutex = threading.Lock()

        threads = []
        pool_num = 30
        for i in range(pool_num):
            t = Thread(self.detect,self.ip_queue)
            threads.append(t)
        for i in  range(pool_num):
            threads[i].start()
        for i in range(pool_num):
            threads[i].join()

class Thread(threading.Thread):
    def __init__(self,func,ip_queue):
        threading.Thread.__init__(self)
        self.func =  func
        self.ip_queue = ip_queue

    def run(self):
        while self.ip_queue.qsize() > 0:
            self.func() #self.ip_queue.get()['ip'],self.ip_queue.get()['port']

希望这点体会对在学习python多线程的同学有帮助,如果文章有不妥之处,欢迎留言交流。

你可能感兴趣的:(Python)