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