Python线程

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空无限

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值