Python线程简介
Python通过两个标准库threadhethreading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。
threading模块提供的其他方法:
threading.currentThread():返回当前的线程变量。
threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
run():用于表示线程活动的方法。
start():启动线程活动。
创建线程
cat threading01.py
#!/usr/bin/env python3
#coding:utf8
import threading
from time import sleep
def download(n):
images=['a.jpg','b.jpg','c.jpg']
for image in images:
print('正在下载',image)
sleep(n)
print('下载{}成功!'.format(image))
if __name__ == "__main__":
t=threading.Thread(target=download,name='aa',args=(1,))
t.start()
# n=1
# while True:
# print(n)
# sleep(3)
# n+=1
[root@ceph01 python]# python3 threading01.py
正在下载 a.jpg
下载a.jpg成功!
正在下载 b.jpg
下载b.jpg成功!
正在下载 c.jpg
下载c.jpg成功!
创建多线程
cat threading01.py
#!/usr/bin/env python3
#coding:utf8
import threading
from time import sleep
def download(n):
images=['a.jpg','b.jpg','c.jpg']
for image in images:
print('正在下载',image)
sleep(n)
print('下载{}成功!'.format(image))
def listenMusic():
musics = ['大碗宽面','土耳其冰淇淋','烤面筋','烤馒头片']
for music in musics:
sleep(0.5)
print('正在听{}歌!'.format(music))
if __name__ == "__main__":
t1=threading.Thread(target=download,name='aa',args=(1,))
t1.start()
t2=threading.Thread(target=listenMusic,name='bb')
t2.start()
# n=1
# while True:
# print(n)
# sleep(3)
# n+=1
[root@ceph01 python]# python3 threading01.py
正在下载 a.jpg
正在听大碗宽面歌!
下载a.jpg成功!
正在下载 b.jpg
正在听土耳其冰淇淋歌!
正在听烤面筋歌!
下载b.jpg成功!
正在下载 c.jpg
正在听烤馒头片歌!
下载c.jpg成功!
多线程共享全局变量
cat threading02.py
#!/usr/bin/env python3
#coding:utf8
import threading
from time import sleep
money=1000
def run1():
global money
for i in range(100):
money -= 1
def run2():
global money
for i in range(100):
money -= 1
if __name__ == "__main__":
th1=threading.Thread(target=run1,name='线程1')
th1.start()
th1.join()
th2=threading.Thread(target=run2,name='线程2')
th2.start()
th2.join()
print("money:",money)
# n=1
# while True:
# print(n)
# sleep(3)
# n+=1
[root@ceph01 python]# python3 threading02.py
money: 800
全局解释器锁(GIL)
据说在Python3.8后会去掉全局解释器锁。
Python只要用线程默认加全局解释器锁。global interpreter lock,由于这个GIL的存在,Python的线程是伪线程,并且当计算量大的时候会造成计算错误。
运行以下程序
cat threading03.py
#!/usr/bin/env python3
#coding:utf8
import threading
from time import sleep
ticket=1000
def run1():
global ticket
for i in range(100):
ticket -= 1
def run2():
global ticket
for i in range(100):
ticket -= 1
def run3():
global ticket
for i in range(100):
ticket -= 1
def run4():
global ticket
for i in range(100):
ticket -= 1
if __name__ == "__main__":
th1=threading.Thread(target=run1,name='线程1')
th1.start()
th1.join()
th2=threading.Thread(target=run2,name='线程2')
th2.start()
th2.join()
th3=threading.Thread(target=run3,name='线程3')
th3.start()
th3.join()
th4=threading.Thread(target=run4,name='线程4')
th4.start()
th4.join()
print("剩余票数:",ticket)
[root@ceph01 python]# python3 threading03.py
剩余票数: 600
可以看到结果如正常预期。
python3 threading03.py
剩余票数: 600
继续看一个计算量大的例子。
cat threading04.py
#!/usr/bin/env python3
#coding:utf8
import threading
from time import sleep
n=0
def task1():
global n
for i in range(10000000):
n+=1
print("task1中n的值是:",n)
def task2():
global n
for i in range(10000000):
n+=1
print("task2中n的值是:",n)
if __name__ == "__main__":
th1=threading.Thread(target=task1)
th1.start()
th2=threading.Thread(target=task2)
th2.start()
th1.join()
th2.join()
print("最后n的值:",n)
可以看到最后的结果并不是20000000,这是由Python全局解释器锁(GIL)造成的。大计算量的时候,全局解释器锁直接释放导致结果出错(锁未成功)。如果想保证数据一致性,请看线程同步-锁中的例子。
[root@ceph01 python]# python3 threading04.py
task1中n的值是: 12736929
task2中n的值是: 12771032
最后n的值: 12771032
线程同步-锁
共享数据:
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
同步:
一个一个地完成,一个做完另一个才能进来。效率就会降低。
使用Thread的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样),但是当线程需要共享数据时,可能存在数据不同步的问题,为了避免这种情况,引入了锁的概念。
cat threading05.py
#!/usr/bin/env python3
#coding:utf8
import threading
import time
import random
lock=threading.Lock()
list1=[0]*10
def task1(name):
#获取线程锁,如果已经上锁(被别人拿到),则等待锁的释放
lock.acquire() #阻塞
for i in range(len(list1)):
list1[i]=1
time.sleep(0.5)
print('{}正在执行结果是{}:'.format(name,list1[i]))
lock.release() #释放
def task2(name):
lock.acquire() #阻塞
for i in range(len(list1)):
print('{}正在执行结果是{}:'.format(name,list1[i]))
time.sleep(0.5)
lock.release()
if __name__ == "__main__":
th1=threading.Thread(target=task1,args=('task1',))
th2=threading.Thread(target=task2,args=('task2',))
th2.start()
th1.start()
th1.join()
th2.join()
print('最后的结果是:',list1)
[root@ceph01 python]# python3 threading05.py
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task2正在执行结果是0:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
task1正在执行结果是1:
最后的结果是: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
cat threading04.py
#!/usr/bin/env python3
#coding:utf8
import threading
from time import sleep
lock=threading.Lock()
n=0
def task1():
lock.acquire() #阻塞
global n
for i in range(1000000):
n+=1
print("task1中n的值是:",n)
lock.release() #释放
def task2():
lock.acquire() #阻塞
global n
for i in range(1000000):
n+=1
print("task2中n的值是:",n)
lock.release() #释放
if __name__ == "__main__":
th1=threading.Thread(target=task1)
th1.start()
th2=threading.Thread(target=task2)
th2.start()
th1.join()
th2.join()
print("最后n的值:",n)
可以看到,加了锁后,数据正常。
[root@ceph01 python]# python3 threading04.py
task1中n的值是: 1000000
task2中n的值是: 2000000
最后n的值: 2000000
线程死锁
死锁:开发过程中使用线程,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程序不做任何事情。
cat threading06.py
#!/usr/bin/env python3
#coding:utf8
from threading import Thread,Lock
import time
lockA=Lock()
lockB=Lock()
class MyThread(Thread):
def run(self):
if lockA.acquire(): #如果可以获取到锁则返回True
print(self.name+'获取了A锁')
time.sleep(0.1)
if lockB.acquire():
print(self.name+'又获取了B锁,现在有A,B锁')
lockB.release()
lockA.release()
class MyThread1(Thread):
def run(self):
if lockB.acquire(): #如果可以获取到锁则返回True
print(self.name+'获取了B锁')
time.sleep(0.1)
if lockA.acquire():
print(self.name+'又获取了A锁,现在有A,B锁')
lockA.release()
lockB.release()
if __name__ == "__main__":
t1=MyThread()
t2=MyThread1()
t2.start()
t1.start()
#t1.join()
#t2.join()
可以看到卡住了,产生了死锁。
线程通信-生产者与消费者
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue和优先级队列PriorityQueue,这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用,可以使用队列来实现线程间的同步。
cat threading07.py
#!/usr/bin/env python3
#coding:utf8
import threading
import queue
import random
import time
def produce(q):
i=0
while i < 10:
num=random.randint(1,100)
q.put('生产者产生数据:%d'%num)
print('生产者产生数据:%d'%num)
time.sleep(1)
i += 1
q.put(None)
#完成任务
q.task_done()
def consume(q):
while True:
item = q.get()
if item is None:
break
print('消费者获取到:%s'%item)
time.sleep(4)
#完成任务
q.task_done()
if __name__ == "__main__":
q=queue.Queue(10)
arr=[]
#创建生产者
th=threading.Thread(target=produce,args=(q,))
th.start()
#创建消费者
tc=threading.Thread(target=consume,args=(q,))
tc.start()
th.join()
tc.join()
[root@ceph01 python]# python3 threading07.py
生产者产生数据:21
消费者获取到:生产者产生数据:21
生产者产生数据:12
生产者产生数据:17
生产者产生数据:40
生产者产生数据:69
消费者获取到:生产者产生数据:12
生产者产生数据:100
生产者产生数据:89
生产者产生数据:66
生产者产生数据:65
消费者获取到:生产者产生数据:17
生产者产生数据:77
消费者获取到:生产者产生数据:40
消费者获取到:生产者产生数据:69
消费者获取到:生产者产生数据:100
消费者获取到:生产者产生数据:89
消费者获取到:生产者产生数据:66
消费者获取到:生产者产生数据:65
消费者获取到:生产者产生数据:77