python中的多线程 threading

多线程

什么是python多线程

多线程是加速程序计算的有效方式,Python的多线程模块 threading 是挺容易学习的。

线程在执行过程中与进程还是有区别的。每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

下面通过一个实例学习:

import threading
import time
import subprocess
def thread_job():
    print('T1 start\n')
    for i in range(10):
        time.sleep(0.1)
    print('T1 finish\n')

def T2_job():
    print('T2 start \n')
    print('T2 finish\n')
def main():
    # 通过 threading.Thread 创建一个线程,
    # 参数1 是线程函数;注意这里只是将函数的索引赋值给 target , 没有括号
    # 参数2 是该线程的名字
    added_thread = threading.Thread(target=thread_job, name='T1')
    thread2 = threading.Thread(target=T2_job, name='T2')
    # 启动线程
    added_thread.start()
    thread2.start()    
    # 阻塞调用线程直至线程的join() 方法被调用中止,后面的程序才会执行
    added_thread.join()
    thread2.join()
    print('all done\n')
    # 打印出当前的线程变量,打印正在运行的线程数量,打印正在运行的线程的list
    print(threading.current_thread())

    print(threading.active_count())
    print(threading.enumerate())

if __name__ == '__main__':
    main()

上述程序运行结果:

T1 start

T2 start 

T2 finish

T1 finish

all done

<_MainThread(MainThread, started 38104)>
1
[<_MainThread(MainThread, started 38104)>]

Process finished with exit code 0

可以看到当两个线程的主函数执行完之后,后面的程序才开始执行。打印 all done

常用的线程模块及方法

Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。

  • threading 模块提供的其他方法:

  • threading.currentThread(): 返回当前的线程变量。

  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

存储进程结果 Queue

由于线程函数中不能使用 return 语句,于是我们考虑将线程函数中要保存的值放到队列中。

继续看一个实例:

import threading
import time
from queue import Queue
def job(l, q):
    for i in range(len(l)):
        l[i] = l[i]**2
    # return l
    # 调用线程不能用return,因此我们将它放入队列中
    q.put(l)
def multithreading():
    q = Queue()
    # 使用一个list来存放创建的线程
    threads = []
    data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
    # 通过一个循环建立四个线程
    for i in range(4):
        t = threading.Thread(target=job, args=(data[i], q))
        t.start()
        threads.append(t)
    # 将循环中给的当前线程附加到主线程里,即,执行完该线程才能往下执行
    for thread in threads:
        thread.join()
    result = []
    # 循坏四次取出队列中的值
    for _ in range(4):
        result.append(q.get())
    print(result)

if __name__ == '__main__':
    multithreading()

多线程执行的效率?

在人们的常识中,多进程,多线程都是通过并发的方式充分利用硬件资源提高程序的运行效率,但是在python中却不一定是这样。相反,这个功能有点鸡肋。

这是为什么呢?GIL的存在使得多行程的运行效率真不一定高。GIL待会说,先看多线程是不是鸡肋,我们用代码来说明。

import time
import threading
def decrement(n):
    while n > 0:
        n -= 1

start = time.time()
# 单线程
decrement(100000000)
cost = time.time() - start
print('single thread: ', cost)
# 多线程
start = time.time()

t1 = threading.Thread(target=decrement, args=[50000000])
t2 = threading.Thread(target=decrement, args=[50000000])

t1.start() # 启动线程,执行任务
t2.start() # 同上

t1.join() # 主线程阻塞,直到t1执行完成,主线程继续往后执行
t2.join() # 同上

cost = time.time() - start
print('multi thread:',cost)

运行结果;

single thread:  4.635588884353638
multi thread: 4.316659688949585

可以看到,使用多线程后效率并没有提升,相反,有时效率反而会下降。

GIL

是什么原因导致多线程不快反慢的呢?

原因就在于 GIL ,在 Cpython 解释器(Python语言的主流解释器)中,有一把全局解释锁(Global Interpreter Lock),在解释器解释执行 Python 代码时,先要得到这把锁,意味着,任何时候只可能有一个线程在执行代码,其它线程要想获得 CPU 执行代码指令,就必须先获得这把锁,如果锁被其它线程占用了,那么该线程就只能等待,直到占有该锁的线程释放锁才有执行代码指令的可能。

线程锁 Lock

python解释器在执行程序时,在任何时候只有一个线程在执行代码。我们定义的多个线程是在不停的切换,只是速度快到我们以为踏实多个线程并行,。那么这种情况下可能会出现一个问题:如果我们定义一个全局变量,而我们的线程函数中也使用了该变量,那么多个线程切换着执行可能会对该变量进行不同的运算。

这种情况下,就需要Python提供的进程锁 lock了。在线程函数中调用锁,执行完在释放,使得整个过程不收其他进程干扰。

一个关于线程锁的示例:

import threading
def job1():
    global A, lock
    lock.acquire()
    for i in range(5):
        A += 1
        print('job1 ', A)
    lock.release()
def job2():
    global A, lock
    lock.acquire()
    for i in range(10):
        A += 10
        print('job2 ', A)
    lock.release()
if __name__ == '__main__':
    lock = threading.Lock()
    A = 0
    t1 = threading.Thread(target=job1)
    t2 = threading.Thread(target=job2)
    t1.start()
    t2.start()
    # t2.join()
    # t1.join()

执行结果:

job1  1
job1  2
job1  3
job1  4
job1  5
job2  15
job2  25
job2  35
job2  45
job2  55
job2  65
job2  75
job2  85
job2  95
job2  105

我们将线程函数中的结果打印出来,可以看到首先对全局变量A 进行+1操作,循环打印5次。之后再线程函数job2中执行+10操作。

可以将线程锁去掉,其他部分保持不变,执行原程序,观察打印的结果是否还是如此整齐一致?

欢迎关注我的微信公众号,谈风月之余谈技术
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/ZT7524/article/details/97960370
今日推荐