什么是线程?
线程是进程内的独立的运行线路,是操作系统能够进行运算调度的最小单位,同时也是处理器调度的最小单位。线程被包含在进程之内,是进程中实际运作单位。
一个线程指的是进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
Python中如何使用多线程?
首先要import threading
线程有两种调用方式,第一种是直接调用
import threading
import time
# 直接调用
def run(n):
print("task {0} {1}".format(n,threading.current_thread()))
time.sleep(2)
if __name__ == "__main__":
t1 = threading.Thread(target=run,args=("t1",)) # 参数括号内的逗号不能省略
t2 = threading.Thread(target=run,args=("t2",)) # 生成一个线程对象
t1.start() # 启动线程
t2.start()
# 现在同时启动50个线程
start = time.time()
for i in range(50):
t= threading.Thread(target=run,args=("t{0}".format(i),))
t.start()
end = time.time()
cost = end - start
print("cost time:",cost)
第二种是继承式
import threading
import time
'''继承式调用'''
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("running task{0} {1}".format(self.n,threading.current_thread()))
time.sleep(1)
if __name__ == "__main__":
start = time.time()
obj = []
for i in range(50):
t= MyThread("t{0}".format(i))
t.start()
end = time.time()
cost = end - start
print("cost time:",cost)
使用多线程对比函数调用,得出结论是:线程是并发执行的(同时执行),而函数调用只能是顺序执行,多线程执行大大提高了运行效率。
但是以上两个程序在运行的时候发现了一个共同的问题:拿继承式的代码来说,理论上50个线程执行完需要经过一秒多才会执行完毕,退出程序,但是实际情况却是开启50个线程之后立马就退出程序,执行时间不足0.01s。为什么呢?
答案是多线程。当前运行的线程是主线程,主线程启动了50个子线程,启动完毕后继续做自己的事情,每个线程之间互不干扰,并行运行。因此无法用此方式测定50个程序到底运行了多久。
主线程运行完毕即退出程序,如果不特殊处理,它不会等待子线程处理完毕。所以我们如果想要等待子线程的运行结果,需要加上join()语句。
import threading
import time
'''继承式调用'''
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("running task{0} {1}".format(self.n,threading.current_thread()))
time.sleep(1)
if __name__ == "__main__":
start = time.time()
obj = []
for i in range(50):
t= MyThread("t{0}".format(i))
t.start()
obj.append(t) # 为了不阻塞后面线程的启动,现将其加入列表
for j in obj:
j.join()
print(threading.current_thread()) # 证明主线程
end = time.time()
cost = end - start
print("cost time:",cost)
当线程被启动后,如果调用join()方法,则会在此线程执行完之前程序不往下走,也就是阻塞当前程序,使程序变为串行执行,这当然不是我们愿意看到的。在此程序中,我们希望它并发执行的同时,满足主线程等待所有子线程执行完毕后再结束这个条件。只需要在开启所有线程之后,一一地join()即可。
线程锁
在线程锁之间讲一下GIL全局性解释器锁。
首先要明白GIL并不是Python的特性,它是在实现Python解释器(CPython)时引入的一个概念。简单来说,GIL锁的存在使得:无论你开启多少个线程,无论你的CPU有多少核,在执行程序的时候同一时间只会占用一个核。所以,你以为的同时占用多核只是假象。再说一遍,这不是Python的特性,这只是CPyhton解释器的特性,其他类型的解释器如JPyhon,pypy等没有这个特性。之所以我们在使用Python多线程时没有感觉是单线程是因为上下文的切换。
在这里不详细多说,关于GIL全局性解释器锁的详细信息,有兴趣可以参考:https://www.cnblogs.com/cjaaron/p/9166538.html
我们最重要的是需要理解,Python多线程适合IO密集型操作,但在计算密集型操作中,多线程甚至没有单线程快。
再来讲互斥锁(也就是线程锁)
当多个线程同时修改一个数据的时候,可能会发生无法预估的错误,所以这时候要上锁。在这里叫做互斥锁。
def run(): lock.acquire() # 获取锁 global num num += 1 lock.release() # 释放锁 time.sleep(1) if __name__ == "__main__": lock = threading.Lock() # 生成锁的实例 num = 0 obj = [] start = time.time() for i in range(1000): t= threading.Thread(target=run) t.start() obj.append(t) for j in obj: j.join() end = time.time() cost = end - start print("cost time:",cost) print("num:",num)
递归锁
互斥锁之间可以嵌套,但是没有理清逻辑容易造成死锁,无法解开。这里使用递归锁,实现多层上锁和多层解锁
def run1():
print("grab the first part data")
lock.acquire()
global num
num += 1
lock.release()
return num
def run2():
print("grab the second part data")
lock.acquire()
global num2
num2 += 1
lock.release()
return num2
def run3():
lock.acquire()
res = run1()
print('--------between run1 and run2-----')
res2 = run2()
lock.release()
print(res, res2)
if __name__ == '__main__':
num, num2 = 0, 0
lock = threading.RLock() # 递归锁,设锁和解锁必须是成对的
for i in range(10):
t = threading.Thread(target=run3)
t.start()
while threading.active_count() != 1:
print(threading.active_count())
else:
print('----all threads done---')
print(num, num2)
需要注意的是:上锁和解锁必须是成对的。
信号量
信号量其实也是一种锁,用于限制线程的并发量,即同一时间只允许几个线程运行。
def run(n):
semaphore.acquire()
time.sleep(1)
print("run the thread: {0}
".format(n))
semaphore.release()
if __name__ == '__main__':
semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行
for i in range(20):
t = threading.Thread(target=run, args=(i+1,))
t.start()
while threading.active_count() != 1:
pass # print threading.active_count()
else:
print('----all threads done---')
Timer
创建一个有延迟的线程。
def hello():
print("hello, world")
t = Timer(30.0, hello)
t.start() # 30秒后执行此线程
Event
threading.Event()用来实现两个或多个线程之间的交互
有四个方法:
set() 设置标志位
clear()清空标志位
wait()如果已经设置标志位不会阻塞,如果没有设置标志位则产生阻塞
isSet() 判定是否设置了标志位。
接下来是一个车等红绿灯的例子,车是停还是行需要根据红绿灯来判定。将实例化若干个车(线程)和一个红绿灯(线程),来实现线程间交互
import threading
import time
import random
def light():
"红绿灯"
while True:
count = 1
event.set() # 标志位设定了,wait就不阻塞 #绿灯状态
while True:
if count == 1:
print('