线程是计算机程序中的最小执行单元。多线程,可以理解为在一个统一的进程内,共享同一块内存空间,主线程调用不同的角色去并发完成任务。

  由于python的GTL限制(每个进程同一时间只许有一个线程参与CPU运算),线程大多不需要CPU参与,较适合I\O密集型程序使用。但,多线程在进行上下文交互时较慢。

  一、创建简单的多线程

  主线程将任务分配给多个子线程后,不用理会子线程的完成情况,主线程继续向下执行;然后等待子线程全部完成后,主线程结束并退出(默认情况)。

# 需求:停顿一秒后,打印输出0-9
import time
def func(i):
    time.sleep(1)
    print(i)

# 一、普通方法实现:先打印数字,再print时间
start_time = time.time()
for i in range(10):
    func(i)
run_time = time.time() - start_time
print('nomal:',run_time)  # nomal: 10.0

# 二、使用多线程:先print时间,再打印数字
import threading
start_time = time.time()
for i in range(10):
    t = threading.Thread(target=func,args=(i,))
    t.start()
run_time = time.time() - start_time
print('thread:%f' % run_time)  # thread:0.003906
View Code

  二、线程锁 RLock

  线程锁:锁定一块内存空间数据,使其他线程无法共享该数据,直至完成相应操作后线程锁打开。数据被释放后可继续共享使用。

  由于同一进程中的多个线程是共享内存的,当需要对同一数据进行修改时,未避免出现脏数据,需使用线程锁进行绑定

#---------------------线程锁-------------------------
# 需求: 1+2+3+。。。+10 = ?

import threading
import time

total_num = 0

# # 不使用线程锁: 由于线程间并发执行,且谁先谁后都是随机的,所以出现脏数据,结果混乱。
# def func(i):
#     time.sleep(0.5)
#     global total_num
#     total_num += i
#     print(i,total_num,)

# 使用线程锁,可得到正确的输出结果
lock = threading.RLock()
def func(i):
    lock.acquire()  # 开启线程锁
    time.sleep(0.5)
    global total_num
    total_num += i
    lock.release()  # 释放线程锁
    print(i,total_num,)


for i in range(11):
    t = threading.Thread(target=func,args=(i,))
    t.start()
View Code

  三、线程间通信 Event

  使用同一事件标识,同步控制多线程操作。Event默认为False,产生阻塞;可通过set设置为True,取消阻塞、向下执行。

# ------------------------多线程间通信Event-----------------------------
import threading
import time

def func(event,i):
    print(i,'before...')
    event.wait()   # 阻塞:等待True
    print(i,'after...')

event_obj = threading.Event()  # 创建通信对象

# how to use
for i in range(10):
    t = threading.Thread(target=func,args=(event_obj,i,))
    t.start()

time.sleep(1)

# 若通信对象未设置为True,进行设置
if not event_obj.isSet():
    event_obj.set()

# 清除Event的设置,还原默认值False
if event_obj.isSet:
    event_obj.clear()
View Code

  四、前台线程和后台线程

  需要主线程等待完成的子线程,是前台线程(默认情况);不需要主线程等待完成的子线程,是后台线程。可通过 setDaemon 和 join 进行设置。

  (一)setDaemon: True时后天线程,False时前台线程

import threading
import time

def func(i):
    time.sleep(1)
    print('hi,func,',i)

for i in range(3):
    t = threading.Thread(target=func,args=(i,))
    t.setDaemon(True)   # 设置为后台线程
    # t.setDaemon(False)  # 设置为前台线程
    t.start()

print('the end !')
View Code

  (二)join: 设置主线程等待子线程的最多时间

import threading
import time

def func(i):
    time.sleep(i)
    print('hi,func,',i)

for i in range(3):
    t = threading.Thread(target=func,args=(i,))
    t.start()
    t.join(3)  # 最多等待时间

print('the end !')
View Code

  五、线程池

   在实际应用过程中,由于线程共享同一块内存空间,所以线程不是越多越好。一般情况,会在程序中设定一个线程池,指定好可使用线程的数量,通常最大为20.

  (一)简单版线程池:创建固定长度的queue,每个queue中元素存放一个线程

  此方法创建的线程池,在线程未使用前已经创建,在执行任务过程中,有些线程一直空闲等待中;每一个线程执行完任务后被抛弃,需要重新添加线程到线程池,保证容量稳定。

#----------------------------------线程池------------

import threading
import queue
import time

class ThreadPool(object):

    def __init__(self,MaxNum = 20):
        self.q = queue.Queue(MaxNum)
        for i in range(MaxNum):
            self.q.put(threading.Thread)

    def get(self):
        return self.q.get()

    def put(self):
        self.q.put(threading.Thread)

def f(pool,args):
    print(i)
    pool.put()
    # 任务执行完成后,原线程无效并等待垃圾回收,需重新添加线程到queue,保证线程池容量稳定


pool = ThreadPool(10)  # 创建线程池

for i in range(4):
    ret = pool.get()  # 取线程
    t = ret(target=f,args=(pool,i))
    t.start()
View Code

  (二)绝版线程池:创建固定长度的queue放任务,随机创建线程去执行任务,线程执行完任务后重新去领取新任务,直至全部任务完成,线程被抛弃。

#----------------------------绝版线程池------------------------
import queue
import threading
import contextlib
import time

StopEvent = object()   # 全局变量,任务完结时的标识

class ThreadPool(object):

    def __init__(self, max_num, max_task_num = None):
        if max_task_num:
            # 将任务装进列表,max_task_num表示最多任务量,即列表长度
            self.q = queue.Queue(max_task_num)
        else:
            self.q = queue.Queue()  # 默认放无限多个任务

        self.max_num = max_num   # 最多创建的线程数(线程池的最大容量)
        self.cancel = False
        self.terminal = False    # 是否终止正在执行的任务的标识
        self.generate_list = []  # 真实创建的线程列表
        self.free_list = []      # 空闲的线程列表

    def run(self, func, args, callback=None):
        """
        线程池执行一个任务
        :param func: 任务函数
        :param args: 任务函数所需参数
        :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;
                        2、任务函数返回值(默认为None,即:不执行回调函数)
        :return: 如果线程池已经终止,则返回True否则None
        """
        if self.cancel:
            return

        if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
            # 如果没有空闲线程,且暂时未达到线程池的最大额度,创建新线程
            self.generate_thread()

        w = (func, args, callback,)     # 待执行任务包,含任务信息:函数、参数、返回调用。
        self.q.put(w)                   # 将任务元组添加至queue

    def generate_thread(self):
        """
        创建一个线程
        """
        t = threading.Thread(target=self.call)  # 创建新线程,并执行call方法
        t.start()

    def call(self):
        """
        循环去获取任务函数并执行任务函数
        """

        # 将当前新创建的线程添加到列表
        current_thread = threading.currentThread()
        self.generate_list.append(current_thread)

        # 领取任务,并执行
        event = self.q.get()

        while event != StopEvent:
            # 如果任务未全部完成:

            # 1、解开任务包
            func, arguments, callback = event

            # 2、开始执行任务
            try:
                result = func(*arguments)
                success = True   # 状态标识:任务正确执行
            except Exception as e:
                success = False  # 状态标识:任务未正确执行
                result = None

            # 3、根据任务执行状态和返回值,启动回调函数
            if callback is not None:
                try:
                    callback(success, result)
                except Exception as e:
                    pass

            # 4、完成任务,当前线程添加至空闲列表——》取新任务——》空闲列表中删除当前线程
            with self.worker_state(self.free_list, current_thread):
                if self.terminal:
                    # 如果需要立即结束线程:不到queue中提取新任务,新任务直接设置为StopEvent
                    event = StopEvent
                else:
                    # 如果不需要立即结束进程:到queue中提取新任务执行
                    event = self.q.get()
        else:
            # 列表中删除当前线程(被删掉的进程在内存中,编程垃圾,等待回收)
            self.generate_list.remove(current_thread)

    def close(self):
        """
        执行完所有的任务后,所有线程停止
        """
        self.cancel = True
        full_size = len(self.generate_list)
        while full_size:
            self.q.put(StopEvent)
            full_size -= 1

    def terminate(self):
        """
        无论是否还有任务,终止线程
        """
        self.terminal = True

        while self.generate_list:
            # 终止线程,清空队列
            self.q.put(StopEvent)

        self.q.queue.clear()

    @contextlib.contextmanager
    def worker_state(self, state_list, worker_thread):
        """
        用于记录线程中正在等待的线程数
        """
        state_list.append(worker_thread)
        try:
            yield
        finally:
            state_list.remove(worker_thread)

# How to use

pool = ThreadPool(5)

def callback(status, result):
    # status, execute action status
    # result, execute action return value
    pass

def action(i):
    print(i)

for i in range(30):
    ret = pool.run(action, (i,), callback)

time.sleep(5)
print(len(pool.generate_list), len(pool.free_list))
# pool.close()
# pool.terminate()
View Code