多线程是加速程序计算的有效方式。多线程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()