下面我们来想象一个场景,放学后教室要打扫卫生,我们一般是怎么分配任务的,一般来讲不可能说小明你去把今天的卫生全部打扫了,这样就太残暴了;一般来说,我们分组来打扫,比如小明打扫第一组,小刚打扫第二组,小智打扫第三组……这样合作,很快就可以将打扫卫生这个任务完成了;那么对于我们程序来讲也是一样的,我需要下载音乐,如果就是在一个main函数里面进行,这就好比让一个人打扫全部的卫生,虽然也能够完成任务,但是速度肯定是很慢的,这样我就可以创建新的进程,按照歌手的名字同时进行下载,这样效率就会快很多了,那么这就是多线程的作用和优势;
那么我们再来讲一下什么是线程,线程和进程有什么样的区别呢?
在创建线程任务之前呢,先来看一下程序运行后有几个线程,这些线程的信息我们可以怎么样获取:
import threading # 先导入关于线程操作的库
def main():
print(threading.active_count()) # 查看当前有几个线程在运行
print(threading.enumerate()) # 查看当前有哪些线程在运行
print(threading.current_thread()) # 查看当前运行的哪个线程
if __name__ == '__main__':
main()
# 输出结果:
# 1
# [<_MainThread(MainThread, started 14340)>]
# <_MainThread(MainThread, started 14340)>
我们会发现,当程序运行后,因为只有main函数在运行,所以就只有一个线程,就是我们的MainThread,下面我们来看下怎么添加新的线程任务:
import threading
def new_thread(): # 这个就是我们创建的线程需要完成的任务
print('这是我的线程信息:%s' % threading.current_thread())
def main():
add_thread = threading.Thread(target=new_thread) # 创建一个新的线程,线程的任务是new_thread
add_thread.start() # 开始执行线程任务
if __name__ == '__main__':
main()
# 输出结果:
# 这是我的线程信息:<Thread(Thread-1, started 15624)>
通过下面的例子,我们发现当有程序有两个线程的时候,他们是同时工作的:
import threading
import time
def n1_thread_job():
for i in range(10):
print(i)
time.sleep(0.1) # 加入延迟时为了造成时间差,使结果更加明显
def n2_thread_job():
for i in range(5):
print('hahaha')
time.sleep(0.1)
def main():
n1_thread = threading.Thread(target=n1_thread_job)
n2_thread = threading.Thread(target=n2_thread_job)
n1_thread.start()
n2_thread.start()
if __name__ == '__main__':
main()
# 输出结果:
# 0
# hahaha
# 1
# hahaha
# hahaha
# 2
# 3
# hahaha
# 4
# hahaha
# 5
# 6
# 7
# 8
# 9
下面我们设想一个情况,我想当一个线程完成之后,在开始另一个线程,那应该怎么办呢?
import threading
import time
def n1_thread_job():
for i in range(10):
print(i)
time.sleep(0.1)
def n2_thread_job():
for i in range(2):
print('hahaha')
time.sleep(0.1)
def main():
n1_thread = threading.Thread(target=n1_thread_job)
n2_thread = threading.Thread(target=n2_thread_job)
n1_thread.start()
n1_thread.join() # 我们可以用join()方法,让n1_thread线程执行完毕之后再执行后面的语句
n2_thread.start()
print('done!')
if __name__ == '__main__':
main()
# 输出结果:
# 0
# 1
# 2
# 3
# 4
# hahaha
# done!
# hahaha
下面问题来了,我们有时候执行方法是有返回值的,如果添加到进程中,return肯定是不行的,那么返回值有要怎么获取呢?
import threading
from queue import Queue # 我们就是将返回值放在这里的
def calc(list1,q): # 进程需要完成的工作,计算传进来每个参数的平方
for i in range(len(list1)):
list1[i] = list1[i] ** 2
q.put(list1) # 将计算的结果放在q这个空间中
def create_thread(data):
q = Queue() # 相当于创建了一个空间来存储返回的结果
threads = [] # 用来存放进程的列表
for i in range(len(data)): # 我们看一共有几组数据,然后就创建几个进程
t = threading.Thread(target=calc,args=(data[i],q)) # 创建新的进程,并且把数据传参进去
t.start() # 开始进程
threads.append(t) # 将创建的进程添加到列表中
for thread in threads:
thread.join() # 通过遍历我们要将所有的结果都计算完毕之后,再提取出来,显示结果
result = [] # 用来保存结果
for i in range(len(data)):
result.append(q.get()) # 将结果一个一个提取出来,然后添加到列表中
print(result) # 输出结果
if __name__ == '__main__':
data = [[3,4,3,2],[2,4],[5,4,3,2,9],[3]] # 需要处理的数据
create_thread(data)
# 输出结果:
# [[9, 16, 9, 4], [4, 16], [25, 16, 9, 4, 81], [9]]
我们可以用queue创建一个空间,然后将结果都先暂存在这块中间里面,最后一次性将计算好的结果从这块空间里面提取处理;
最后一块内容,上面的多个线程之间是没有什么关联的,但是接下来我需要处理的数据有一个公用的变量,那么同一时间势必只能有一个变量能被使用或修改,那么我们可以在一个进程在使用这个变量的时候,将这个变量先锁住,当使用完毕之后再解锁,这样就不会产生冲突了:
import threading
def job1():
global data,lock
lock.acquire()
for i in range(5):
data += 1
print(data)
lock.release()
def job2():
global data,lock
lock.acquire()
for i in range(5):
data *= 2
print(data)
lock.release()
if __name__ == '__main__':
data = 0
lock = threading.Lock()
thread1 = threading.Thread(target=job1)
thread2 = threading.Thread(target=job2)
thread1.start()
thread2.start()
# 输出结果:
# 1
# 2
# 3
# 4
# 5
# 10
# 20
# 40
# 80
# 160