简单明了,让计算机在同一时间内同时运行多个程序,并且每个程序的计算互不干扰,我们称这样的操作为多线程运算。
首先,我们要在代码中导入线程模块也就是import threading。介绍几个常用的方法
import threading
'''
1.threading.active_count():计算当前激活的线程个数,也就是说,当前程序有几个线程在运行。
2.threading.enumerate():返回一个当前激活的线程名称Name的列表
3.threading.current_thread():当前运行的线程的名称Name
'''
def main():
print(threading.active_count())
print(threading.enumerate())
print(threading.current_thread())
if __name__ == '__main__':
main()
# result:根据每个人的计算机不同,打印出来的个数,名称也不一样
'''
Add Thread:
添加一个线程:变量名 = threading.Thread(target = 操作方法名, name = "别名"),参数中的操作方法名是指你定义给这个线程需要做的事情,比如打印某个东西,计算某个东西,写成一个方法,在传入该方法名即可,name参数是给线程起一个方便我们观察的别名。如下:
'''
def thread_job_1():
print("This is an added Thread, Name is %s" % (threading.current_thread()))
def main():
added_thread1 = threading.Thread(target = thread_job_1, name = "Thread1")
#print(threading.active_count())
#print(threading.enumerate())
#print(threading.current_thread())
added_thread1.start() # 一定要启动刚刚添加的线程,不然线程不会自动进行工作.当代码运行后,至少会有一个main线程去执行
if __name__ == '__main__':
main()
Join(): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。先来看一个例子:
import threading
import time
def thread_job_1():
print("Thread1 start")
for i in range(10):
time.sleep(0.5) #time.sleep(t)让程序睡眠t秒
print("Thread1 end")
def main():
added_thread1 = threading.Thread(target = thread_job_1)
added_thread1.start() # 一定要启动刚刚添加的线程,不然线程不会自动进行工作.当代码运行后,至少会有一个main线程去执行
print("All finish")
if __name__ == '__main__':
main()
'''
result:
Thread1 start
All finish
Thread1 end
可以看出来主线程没有等待Thread1运行完就开始执行了,而join函数可以很好避免这种情况的发生。
'''
def thread_job_1():
print("Thread1 start")
for i in range(10):
time.sleep(0.5) #time.sleep(t)让程序睡眠t秒
print("Thread1 end")
def main():
added_thread1 = threading.Thread(target = thread_job_1)
added_thread1.start() # 一定要启动刚刚添加的线程,不然线程不会自动进行工作.当代码运行后,至少会有一个main线程去执行
added_thread1.join() #在这里加入join函数就可以让主线程等待子线程1执行完再继续执行了
print("All finish")
if __name__ == '__main__':
main()
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 模块中的常用方法:
由于Threading中的工作是不能返回值的,所以线程之间的通信依靠Queue来完成,如下:
import threading
import time
from queue import Queue
def thread_work(l, q):
for i in range(len(l)):
l[i] = l[i] ** 2
q.put(l)
def multithreading_work():
q = Queue()
threads = []
data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for i in range(3):
t = threading.Thread(target= thread_work, args=(data[i], q)) # 创建线程中方法使用args添加参数
t.start()
threads.append(t)
for thread in threads: # 控制每个线程依次执行
thread.join()
results = []
for i in range(3):
results.append(q.get())
print(results)
if __name__ == '__main__':
multithreading_work()
由于Python中拥有GIL(Global Interpreter Lock)全局解释器锁的存在。会导致多线程状况下也不能够很大程序的将多个任务执行的效率提高。所以我们要使用multiprocessing(多进程)来加快不同任务的计算。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UWgOf9jp-1594565574835)(/Users/liyan/Library/Application Support/typora-user-images/image-20200709154232785.png)]
下面举一个例子,让一个线程循环执行多个操作。让多个线程分别执行多个操作。比较二者的速度
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)
#result :normal: 0.06258893013000488 multithreading: 0.05690884590148926
#可以发现多线程处理并没有比单线程快多少时间
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下:
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。
考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。
那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。
锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。
经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。
参考例子:
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()
代码地址:Code欢迎给个star