【Note】MV-python基础系列之 多线程Threadin

2018-12-05  本文已影响0人  火禾子_

多线程是加速程序计算的有效方式。多线程Threading是一种让程序拥有分身效果,能同时处理多件事情.。一般的程序只能从上到下一行行执行代码, 不过多线程 (Threading) 就能打破这种限制,让你的程序鲜活起来。

1、添加线程 Thread

方法一:直接调用threading.Thread来构造thread对象

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})  
group 为None;
target 为线程将要执行的功能函数;
name 为线程的名字,也可以在对象构造后调用setName()来设定;
args 为 tuple 类型的参数,可以为多个,如果只有一个也的使用 tuple 的形式传入,例如(1,);
kwargs 为 dict 类型的参数,也即位命名参数

  threading.Thread 对象的其他方法:

start(),用来启动线程;
join(), 等待直到线程结束;
isAlive(),获取线程状态
setDeamon(), 设置线程为 deamon 线程,必须在start()调用前调用,默认为非demon。
注意: python的主线程在没有非deamon线程存在时就会退出。

threading.active_count() # 获取已激活的线程数
threading.enumerate() # 查看所有的线程信息
threading.current_thread() #查看现在正在运行的线程

  添加线程,threading.Thread()接收参数target代表这个线程要完成的任务,需自行定义:

def thread_job():
    print('This is a thread of %s' % threading.current_thread())

def main():
    thread = threading.Thread(target=thread_job)   # 定义线程 
    thread.start()  # 让线程开始工作
    
if __name__ == '__main__':
    main()

方法二:直接从 threading.Thread 继承,然后重写 init 方法和 run 方法

#coding:utf-8
import threading
class MyThread(threading.Thread): #继承父类threading.Thread
    def __init__(self, num ):  
        threading.Thread.__init__(self)
        self.num = num
         
    #把要执行的代码写到run函数里面 线程在创建后会直接运行run函数  
    def run(self):
        for i in range(self.num):
            print('I am %s.num:%s' % (self.getName(), i))
                
for i in range(3):
    t = MyThread(3)
    t.start()
    t.join()
##########运行结果#########
>>> I am Thread-1.num:0
I am Thread-1.num:1
I am Thread-1.num:2
I am Thread-2.num:0
I am Thread-2.num:1
I am Thread-2.num:2
I am Thread-3.num:0
I am Thread-3.num:1
I am Thread-3.num:2
2、储存进程结果 Queue
#!/usr/bin/python
# -*- coding:utf-8 -*-
import threading
import time
from queue import Queue

def job(l, q):

    for i in range(len(l)):
        l[i] = l[i]**2
    q.put(l) # 多线程调用的函数不能用 return 返回值

def multiThreading():
    q = Queue() # q 中存放返回值,代替 return 的功能
    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)) #被调用的 job 函数只是一个索引,故不用带括号
        t.start()
        threads.append(t)
    for thread in threads:
        thread.join()

    results = []
    for _ in range(4):
        results.append(q.get()) # q.get() 按顺序从 q 中拿出一个值
    print(results)
if __name__ == '__main__':
    multiThreading()
3、GIL不一定有效率

python 的多线程 threading 有时候并不是特别理想。最主要的原因是就是,Python 的设计上,有一个必要的环节,就是 Global Interpreter Lock (GIL)。这个东西让 Python 还是一次性只能处理一个线程。

尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的。 实际上,解释器被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。 GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。

在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。

测试 GIL
  我们创建一个 job, 分别用 threading 和 一般的方式执行这段程序. 并且创建一个 list 来存放我们要处理的数据. 在 Normal 的时候, 我们这个 list 扩展4倍, 在 threading 的时候, 我们建立4个线程, 并对运行时间进行对比.

import threading
from queue import Queue
import copy
import time

def job(l, q):
    res = sum(l)
    q.put(res)

def multithreading(l):
    q = Queue()
    threads = []
    for i in range(4):
        t = threading.Thread(target=job, args=(copy.copy(l), q), name='T%i' % i)
        t.start()
        threads.append(t)
    [t.join() for t in threads]
    total = 0
    for _ in range(4):
        total += q.get()
    print(total)

def normal(l):
    total = sum(l)
    print(total)

if __name__ == '__main__':
    l = list(range(1000000))
    s_t = time.time()
    normal(l*4)
    print('normal: ',time.time()-s_t)
    s_t = time.time()
    multithreading(l)
    print('multithreading: ', time.time()-s_t)

  如果你成功运行整套程序, 你大概会有这样的输出. 我们的运算结果没错, 所以程序 threading 和 Normal 运行了一样多次的运算. 但是我们发现 threading 却没有快多少, 按理来说, 我们预期会要快3-4倍, 因为有建立4个线程, 但是并没有. 这就是其中的 GIL 在作怪.

1999998000000
normal:  0.10034608840942383
1999998000000
multithreading:  0.08421492576599121
4、线程锁 Lock
import threading

def job1():
    global A
    for i in range(10):
        A+=1
        print('job1',A)

def job2():
    global A
    for i in range(10):
        A+=10
        print('job2',A)

if __name__== '__main__':
    lock=threading.Lock()
    A=0
    t1=threading.Thread(target=job1)
    t2=threading.Thread(target=job2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

  运行结果:

job1job2 11
job2 21
job2 31
job2 41
job2 51
job2 61
job2 71
job2 81
job2 91
job2 101
 1
job1 102
job1 103
job1 104
job1 105
job1 106
job1 107
job1 108
job1 109
job1 110

  lock 在不同线程使用同一共享内存时,能够确保线程之间互不影响,使用 lock 的方法是, 在每个线程执行运算修改共享内存之前,执行lock.acquire()将共享内存上锁, 确保当前线程执行时,内存不会被其他线程访问,执行运算完毕后,使用lock.release()将锁打开, 保证其他的线程可以使用该共享内存。

import threading

def job1():
    global A,lock
    lock.acquire()
    for i in range(10):
        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()
    t1.join()
    t2.join()

  运行结果为:

job1 1
job1 2
job1 3
job1 4
job1 5
job1 6
job1 7
job1 8
job1 9
job1 10
job2 20
job2 30
job2 40
job2 50
job2 60
job2 70
job2 80
job2 90
job2 100
job2 110

  当然,要想实现执行结果有序化,还可以将程序改为:

if __name__== '__main__':
    lock=threading.Lock()
    A=0
    t1=threading.Thread(target=job1)
    t2=threading.Thread(target=job2)
    t1.start()
    t1.join()
    t2.start()
    t2.join()
上一篇下一篇

猜你喜欢

热点阅读