Python多线程

时间: 2019-03-18
开发Python多线程程序时,主要涉及到3个模块,分别是thread,threading和Queue。

1.串行执行例子

下面举一个简单的例子,没有使用多线程的情况:

import time

def loop1():
    print 'start loop1 at ', time.ctime()
    time.sleep(4)
    print 'end loop1 at ', time.ctime()

def loop2():
    print 'start loop2 at ', time.ctime()
    time.sleep(2)
    print 'end loop2 at ', time.ctime()

def main():
    print 'start main at ', time.ctime()
    loop1()
    loop2()
    print 'end main at ', time.ctime()


if __name__ == '__main__':
    main()

这个串行执行的例子,先执行loop1 ,再执行loop2 。

thread模块提供了基本的线程和锁定支持;而threading模块提供了更高级别、功能更全面的线程管理。
应该避免使用thread模块,尽量使用threading模块,原因如下:
(1)threading模块更加先进,有更好的线程支持,并且thread模块中的一些属性会和threading模块有冲突.
(2)thread模块拥有的同步原语很少,而threading模块有很多。
(3)thread模块对于进程何时退出没有控制。当主线程结束时,所有其他线程也都强制结束,不会发出警告或者进行适当的清理。

2.thread.start_new_thread实现多线程

由于使用thread模块时,主线程结束时,所有其他线程也都强制结束。下面例子采取让主线程sleep的方式来保证子线程先完成。

import thread
import time

def loop1():
    print 'start loop1 at ', time.ctime()
    time.sleep(4)
    print 'end loop1 at ', time.ctime()

def loop2():
    print 'start loop2 at ', time.ctime()
    time.sleep(2)
    print 'end loop2 at ', time.ctime()

def main_thread():
    print 'start main at ', time.ctime()
    thread.start_new_thread(loop1, ())
    thread.start_new_thread(loop2, ())
    time.sleep(6)
    print 'All done at ', time.ctime()


if __name__ == '__main__':
    main_thread()

由于loop1和loop2函数过于简单,我们可以预估loop1和loop2的完成时间,然后在main_thread函数直接相加两个时间,同时,在4s时,loop1和loop2都已经执行完了,没有实现多线程的优势。在实际情况中,这种方法是十分不可取的。

3.使用锁机制实现多线程

import time
import thread

loops = [44, 42]

def loop(nsecs, loopname, lock):
    print 'start ', loopname, ' at: ', time.ctime()
    time.sleep(nsecs)
    print 'end ', loopname, ' at: ', time.ctime()
    lock.release()

def main():
    print 'start main at ', time.ctime()
    # locks保存锁对象的列表
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate()
        lock.acquire()
        locks.append(lock)

    for i in nloops:
        thread.start_new_thread(loop, (loops[i], ('loop' + str(i)), locks[i]))

    for i in nloops:
        while locks[i].locked():
            pass

    print 'All done at: ', time.ctime()


if __name__ == '__main__':
    main()

每个线程将被分配一个已获得的锁。当sleep()的时间到了的时候,释放对应的锁,向主线程表明该线程已完成。
通过使用thread.allocate_lock()函数得到锁对象,然后通过acquire()方法取得(每个锁)。取得锁效果相当于“把锁锁上”。
最后一个循环只是坐在那里等待(暂停主线程),直到所有锁都被释放之后才会继续执行。

4. Threading模块实现多线程

上边提到,应该使用更高级的threading模块实现多线程,这里研究使用Thread类来实现多线程,threading模块支持守护线程。使用Thread类,有多种创建线程的方法:
(1)创建Thread 的实例,传给它一个函数。
(2)创建Thread 的实例,传给它一个可调用的类实例。
(3)派生Thread 的子类,并创建子类的实例。
Thread类的常用方法:

方法 说明
init(group=None, tatget=None, name=None, args=(),kwargs ={}, verbose=None, daemon=None) 实例化一个线程对象,需要有一个可调用的target,以及其参数args或kwargs。还可以传递name 或group 参数,不过后者还未实现。此外, verbose 标志也是可接受的。而daemon 的值将会设定thread.daemon 属性/标志
start() 开始执行该线程
run() 定义线程功能的方法(通常在子类中被应用开发者重写)
join(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞

4.1 创建一个Thread的实例,并传给它一个函数

在第3节的多线程例子中,需要自己管理锁来实现多线程,这样其实比较麻烦。使用threading.Thread类时,就省去了锁的管理,实例化Thread(调用Thread())和调用thread.start_new_thread()的最大区别是新线程不会立即开始执行。

from time import ctime
from time import sleep
import threading

sleepsecs = [4, 8]

def loop(loopname, secs):
    print 'start loop',loopname, ' at: ', ctime()
    sleep(secs)
    print 'end loop',loopname, ' at: ', ctime()


if __name__ == '__main__':
    # Thread对象列表
    threads = []

    for i in range(len(sleepsecs)):
        thread = threading.Thread(target=loop, args=(i, sleepsecs[i]))
        threads.append(thread)

    print 'start main at :', ctime()
    # 逐个启动线程
    for i in range(len(sleepsecs)):
        threads[i].start()
        sleep(0.001)

    for i in range(len(sleepsecs)):
        threads[i].join()

    print 'end main at ', ctime()

4.2 创建一个Thread的实例,传给它一个可调用的类实例

下面添加一个ThreadFunc类,在实例化Thread类时,实际做了2次实例化,包括Thread和ThreadFunc。
当创建新线程时,Thread 类的代码将调用ThreadFunc 对象,此时会调用call()这个特殊方法。

from time import sleep
from time import ctime
from threading import Thread

class ThreadFunc(object):
    # func: 多线程要执行的函数  该参数就是一个函数对象
    # loopname: 执行函数名称
    # secs: sleep等待时间
    def __init__(self, func, loopname, secs):
        self.func = func
        self.loopname = loopname
        self.secs = secs

    def __call__(self):
        self.func(self.loopname, self.secs)

def loop(loopname, secs):
    print 'start loop ', loopname, ' at: ', ctime()
    sleep(secs)
    print 'end loop ', loopname, ' at: ', ctime()


if __name__ == '__main__':
    threads = []
    secs = [4, 8]

    print 'start main at: ', ctime()
    for i in range(len(secs)):
        thread = Thread(target=ThreadFunc(loop, i, secs[i]))
        threads.append(thread)

    for i in range(len(secs)):
        threads[i].start()
        sleep(0.001)

    for i in range(len(secs)):
        threads[i].join()

    print 'end main at:', ctime()

4.3 派生Thread的子类,并创建子类的实例

派生子类时,只需要创建子类的实例即可,不用再创建Thread类实例。需要注意下边2点:
1.MyThread子类的构造函数必须先调用其基类的构造函数。
2.必须在子类中定义run()方法。

from threading import Thread
from time import ctime
from time import sleep

class MyThread(Thread):
    # func:该参数是多线程执行的函数
    # args:func函数的参数列表,多个参数的列表
    # name:名称
    def __init__(self, func, args, name):
        # MyThread子类的构造函数必须先调用其基类的构造函数
        Thread.__init__(self)
        self.func = func
        self.args = args
        self.name = name

    # 必须在子类中定义run()方法
    def run(self):
        self.func(*self.args)

def loop(nloop, secs):
    print 'start loop ', nloop, ' at: ', ctime()
    sleep(secs)
    print 'end loop ', nloop, ' at: ', ctime()


if __name__ == '__main__':
    seconds = [4, 8]
    threads = []
    print 'start main at: ', ctime()

    # 创建线程,但是未启动
    for i in range(len(seconds)):
        thread = MyThread(loop, (i, seconds[i]), '')
        threads.append(thread)

    # 启动线程
    for i in range(len(seconds)):
        threads[i].start()
        sleep(0.001)

    for i in range(len(seconds)):
        threads[i].join()

    print 'end main at: ', ctime()

4.4 独立抽象myThread模块

为了应用更加方便,将MyThread类独立抽象出来,后边直接引入就可以使用,并且保存下返回结果,做得更加通用。

from threading import Thread
from time import ctime

class MyThread(Thread):
    def __init__(self, func, args, name):
        Thread.__init__(self)
        self.func = func
        self.args = args
        self.name = name

    def getResult(self):
        return self.res

    def run(self):
        print 'starting ', self.name, ' at: ', ctime()
        self.func(*self.args)
        print 'finish ', self.name , ' at: ', ctime()

5 斐波那契、阶乘、求和的 多线程实现

为了展示多线程的实现结果,在每次计算的时候,sleep 0.1秒,并试图用多线程优化。

from time import sleep
from time import ctime
from MyThread import MyThread


# 递归斐波那契
def recursion_fib(x):
    sleep(0.1)
    if x < 3:
        return 1
    else:
        return recursion_fib(x-2) + recursion_fib(x-1)

# 递归阶乘
def recursion_multiply(x):
    sleep(0.1)
    if x < 2:
        return 1
    else:
        return x*recursion_multiply(x-1)

# 递归实现时,只需要考虑这个函数在不同条件下的返回值,需要有终止条件
def recursion_sum(x):
    sleep(0.1)
    if x < 2:
        return 1
    else:
        return x+recursion_sum(x-1)


if __name__ == '__main__':
    func_names = ['fib', 'recMul', 'recSum']
    funcs = [recursion_fib, recursion_multiply, recursion_sum]
    threads = []

    print 'start Single Thread -------------------'
    for i in range(len(funcs)):
        print 'start function ', func_names[i], ' at: ', ctime()
        funcs[i](10)
        print 'end function ', func_names[i], ' at: ', ctime()
    print 'end Single Thread -------------------'

    print 'start Multiple Thread -------------------'
    for i in range(len(funcs)):
        thread = MyThread(funcs[i], (10,), func_names[i])
        threads.append(thread)

    for i in range(len(funcs)):
        threads[i].start()
        sleep(0.001)

    for i in range(len(funcs)):
        threads[i].join()
        print threads[i].getResult()

    print 'end Multiple Thread -------------------'

6 多线程实际应用项目

实现多线程肯定有自己的实际应用场景,在实际使用过程中,遇到一个示例,对于一些I/O密集型的程序,使用python程序可以发挥很大的效果。
给定一批POI名称(共4万5千个左右),通过GD提供的公开搜索API接口,补全POI的id、地址、分类、经纬度等信息。

import random
import urllib2
import json
import AttrN
import Const
import time
import pandas as pd
from threading import Thread

class Regeo:
    def __init__(self):
        pass

    # GD POI搜索接口 key id
    AMAP_POI_SEARCH_URL = "https://restapi.amap.com/v3/place/text?key=%s&keywords=%s&city=%s&output=json&&extensions=all"

    PAGE_SIZE = 20

    STATUS = 'status'

    POIS = 'pois'

    COUNT = 'count'
    REGEOCODE = 'regeocode'

    POIID = 'id'
    PARENT_POIID = 'parent'
    POINAME = 'name'
    POITYPE = 'type'
    POITYPECODE = 'typecode'
    POIADDR = 'address'
    POILOCATION = 'location'
    POILNG = 'poi_lng'
    POILAT = 'poi_lat'
    POIADCODE = 'adcode'
    POIADNAME = 'adname'
    NAVI_POIID = 'navi_poiid'
    ENTR_LOCATION = 'entr_location'
    ENTR_LNG = 'entr_lng'
    ENTR_LAT = 'entr_lat'
    EXIT_LOCATION = 'exit_location'
    EXIT_LNG = 'exit_lng'
    EXIT_LAT = 'exit_lat'
    BUSINESSAREA = 'business_area'


def load_amap_key(amapkey_path):
    amap_keys = []
    fin = open(amapkey_path)
    while True:
        line = fin.readline()
        if not line:
            break
        amap_keys.append(line.strip("\n"))
    return amap_keys

def random_amap_key(amap_keys):
    """
    从amap_key库中随机获取一个amap_key
    :return: amap_key
    """
    return amap_keys[random.randint(0, len(amap_keys)) - 1]

def recurAmapParentIDSearch(poi_name,city_id):
    amap_key_search = random_amap_key(load_amap_key("datafiles/key.rtf"))
    if( city_id == 10 ):
        city_name = 'shanghai'
    else:
        city_name = 'nanjing'
    amap_restapi_search_url = Regeo.AMAP_POI_SEARCH_URL % (amap_key_search, poi_name, city_name)
    for j in range(5):
        try:
            response = urllib2.urlopen(amap_restapi_search_url)
            ret = json.load(response)
            status = ret[Regeo.STATUS]
            if (int(status) != 1):
                return "null", "null", "null", "null", "null"
            pois = ret[Regeo.POIS]
            poi = pois[0]  # 搜索结果第一
            name = poi[AttrN.NAME].encode(Const.UTF8)
            id = poi[AttrN.ID].encode(Const.UTF8)
            type = poi[AttrN.TYPE].encode(Const.UTF8)
            loc = poi[AttrN.LOCATION].encode(Const.UTF8)
            if isinstance(poi[AttrN.ADDRESS], list):
                if len(poi[AttrN.ADDRESS]) == 0:
                    addr = "null"
                else:
                    addr = poi[AttrN.ADDRESS][0].encode(Const.UTF8)
            else:
                addr = poi[AttrN.ADDRESS].encode(Const.UTF8)
            return id, name, type, addr, loc
        except Exception, e:
            if j<4 :
                time.sleep(0.1)  # sleep 1s
                continue
            else:
                return "null", "null", "null", "null", "null"


def threadCallingDumpFilesSearch(pd_table, data_range, file_name):
    dump_filename = file_name + '_' + data_range.split(',')[0] + '_' + data_range.split(',')[1]
    start_p = int(data_range.split(',')[0])
    end_p = int(data_range.split(',')[1])
    i = start_p
    with open(dump_filename, 'w') as f:
        for index, row in pd_table.iloc[start_p:end_p, :].iterrows():
            id, name, type, addr, loc = recurAmapParentIDSearch(row['poi_name'], row['city_id'])
            f.write(row['geohash'] + '\t' + row['poi_name'] + '\t' + type + '\t' + id + '\t' + name + '\n')
            print "第" + str(i) + "行已写入!!"
            if (i % 10000) == 0:
                time.sleep(1)
            i = i + 1
    f.close()


if __name__ == '__main__':
    geo_pois = pd.read_table(filepath_or_buffer='datafiles/poi_name_for_search_buchong_final.txt',sep='\t').iloc[0:5000, :]
    data_range = ['0,1000',
                  '1000,2000',
                  '2000,3000',
                  '3000,4000',
                  '4000,5000']
    # 线程列表
    threads = []
    for i in range(len(data_range)):
        thread = Thread(target=threadCallingDumpFilesSearch, args=(geo_pois, data_range[i], 'datafiles/poi_name_for_search_buchong_final_output'))
        threads.append(thread)

    for i in range(len(data_range)):
        threads[i].start()

    for i in range(len(data_range)):
        threads[i].join()

7 同步原语

上边部分介绍了多线程的实现方式,多线程中还有一个非常重要的内容,就是关于竞态。当多个线程同时访问并修改一个变量(或者同时写入,更新同一个文件、记录)时,如果不对资源先进行锁定的话,就有可能出现错误。这就是多线程程序需要保持同步的原因。
Python有多种同步类型来实现同步,包括锁机制、信号量等等。

7.1 锁机制

你可能感兴趣的:(Python多线程)