python进阶-------进程线程(二)

时间:2023-01-16 16:40:27

  Python中的进程线程(二)

一、python中的“锁”

1.GIL锁(全局解释锁)

含义:

Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。
GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
在调用任何Python C API之前,要先获得GIL。
GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作。

1.1GIL的影响

无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
所以,python是无法利用多核CPU实现多线程的。
这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

IO密集型案例:IO密集型即线程中存在大量的IO操作。

import threading
import time
def foo(n):

  time.sleep(n)
  print("foo....%s" % n)

  print(threading.activeCount())

def bar(n):

  time.sleep(n)
  print("bar......%s" % n)

s=time.time()
t1=threading.Thread(target=foo,args=(2,))
#t1.setDaemon(True)
t1.start()

t2=threading.Thread(target=bar,args=(5,))
#t2.setDaemon(True)
t2.start()

t1.join() # 阻塞主线程
t2.join()

print("++++++",threading.activeCount())
print("ending!")
print("cost time:",time.time()-s)

计算密集型案例:

import threading

import time

def foo(n):
  ret=0
  for i in range(n):
    ret+=i
  print(ret)

def bar(n):#计算密集型的任务指的是线程中存在大量的计算任务我们以阶乘和累加为例,通过传统的串行执行函数和计算。
  ret=1
  for i in range(1,n):
    ret*=i
  print(ret)

s=time.time()
t1=threading.Thread(target=foo,args=(100000000,))
t1.start()
t2=threading.Thread(target=bar,args=(100000,))
t2.start()
t1.join()
t2.join()
print("cost time:",time.time()-s)

2.互斥锁

import threading

import time

def sub():

  global num # 掌握为什么加global num,在每个线程中都获取这个全局变量

  lock.acquire()#加锁操作,
  temp=num
  time.sleep(0.1 )
  num=temp-1# 对此公共变量进行-1操作

  lock.release()#解锁操作

time.sleep(2)

num=100

l=[]

lock=threading.Lock()

for i in range(100):
t=threading.Thread(target=sub,args=())
t.start()
l.append(t)

for t in l:
  t.join()#等待所有线程运行完毕。

print(num)

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。

3.死锁与递归锁

所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

import threading
import time

mutexA = threading.Lock()
mutexB = threading.Lock()

class MyThread(threading.Thread):

def __init__(self):
threading.Thread.__init__(self)

def run(self):
self.fun1()
self.fun2()

def fun1(self):

mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放

print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))

mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
mutexB.release()
mutexA.release()

def fun2(self):

mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
time.sleep(0.2)

mutexA.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutexA.release()

mutexB.release()

if __name__ == "__main__":

print("start---------------------------%s"%time.time())

for i in range(0, 10):
my_thread = MyThread()
my_thread.start()

在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

Rlock=threading.RLock()

二.Event对象

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就 会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。

主要执行的方法有:

  • Event.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数timeout)。

  • Event.set() :将标识位设为Ture

  • Event.clear() : 将标识伴设为False。

  • Event.isSet() :判断标识位是否为Ture。

  • python进阶-------进程线程(二)

    代码示例:模拟红绿灯

设计一个关于红绿灯的线程,5个关于车的线程;
对于车线程,每隔一个随机秒数,判断红绿灯的状态,是红灯或者黄灯,打印waiting;是绿灯打印running。
对于红绿灯线程:
首先默认是绿灯,做一个计数器,十秒前,每隔一秒打印“light green”;第十秒到第十三秒,每隔一秒打印“light yellow”,13秒到20秒,
‘light red’,20秒以后计数器清零。重新循环。 知识点:event对象(提示:event对象即红绿灯,为true是即绿灯,false时为黄灯或者红灯)
import threading
import time
import random
event=threading.Event()
def light():
count=1
while True:
if count<10:
event.set()
time.sleep(1)
print('\033[32m light green.....\033[0m')
elif 10 < count <13:
event.clear()
time.sleep(1)
print('\033[33m light yellow....\033[0m')
elif 13 < count <20:
event.clear()
time.sleep(1)
print('\033[31m light red .....\033[0m')
elif count >20:
count =0
count +=1
def car():
while True:
r=random.randint(1,3)
time.sleep(r)
if event.is_set():
print("car is running.....")
elif not event.is_set():
print("car is waiting....") lig=threading.Thread(target=light,args=())
lig.start()
for i in range(5):
ca=threading.Thread(target=car,args=())
ca.start()
三.信号量(Semaphore)

Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):


import threading
import time semaphore = threading.Semaphore(5) def func():
if semaphore.acquire():
print (threading.currentThread().getName() + ' get semaphore')
time.sleep(2)
semaphore.release() for i in range(20):
t1 = threading.Thread(target=func)
t1.start()
四、队列

'''


创建一个“队列”对象


import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数
maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。


将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;
第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,
put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且
block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

'''

队列方法:

'''
join() 阻塞进程,直到所有任务完成,需要配合另一个方法task_done。

def join(self):
with self.all_tasks_done:
while self.unfinished_tasks:
self.all_tasks_done.wait()

task_done() 表示某个任务完成。每一条get语句后需要一条task_done。

import queue
q = queue.Queue(5)
q.put(10)
q.put(20)
print(q.get())
q.task_done()
print(q.get())
q.task_done()

q.join()

print("ending!")

其他常用方法:

'''

此包中的常用方法(q = Queue.Queue()):

q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)非阻塞
q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作

'''

其他的模式

'''


Python Queue模块有三种队列及构造函数:


1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize)


import queue


#先进后出


q=queue.LifoQueue()


q.put(34)
q.put(56)
q.put(12)


#优先级
q=queue.PriorityQueue()
q.put([5,100])
q.put([7,200])
q.put([3,"hello"])
q.put([4,{"name":"alex"}])


while 1:
data=q.get()
print(data)


'''

2.生产者消费者模型

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。

import threading
import random
import queue
import time
q=queue.Queue(20)
def Producer():#生产者负责生产数据
count=1
while count <10:
# if q.qsize()<20:
s=random.randint(1,100)
q.put(s)
print("has mod baozi %s"%s)
time.sleep(5)
count+=1
def Consumer(id):#消费者负责取数据。
while True:
s=q.get()
print("Consumer"+id+"has eat %s "%s)
time.sleep(5)
for i in range(50):
t1= threading.Thread(target=Producer,args=())
t1.start()
for i in range(10):
t=threading.Thread(target=Consumer,args=(str(i),))
t.start()