博客园

并发编程之⏸多线程

1、线程相关理论知识

2、开启线程的方式

3、线程对象的相关方法

4、守护线程

5、线程互斥锁

6、信号量

7、死锁与递归

8、事件Event

9、定时器⏰

10、线程queue

1、📝📝📝线程相关知识点📝📝📝

一、什么是线程❓❓❓

  线程:一个流水线运行的过程
  线程是一个执行单位,cpu执行的就是线程
  进程是一个资源单位
        公司的运行,部门是资源单位,任务的运行的实质是其中的人在运行

二、线程⚔⚔⚔⚔⚔⚔进程

  1. 线程共享创建它的进程的地址空间;进程有自己的地址空间
  2. 线程可以直接访问其进程的数据段;进程有自己的父进程的数据段副本。
  3.线程可以直接与其进程中的其他线程通信;进程必须使用进程间通信来与同级进程通信。
  4. 新线程很容易创建;新的进程需要父进程的复制。
  5. 线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
  6. 主线程的改变(取消,优先级的改变,等等)可能会影响进程中其他线程的行为;父进程的更改不会影响子进程。

2、🐱‍🐉🐱‍🐉🐱‍🐉🐱‍🐉🐱‍🐉🐱‍🐉开启线程的方式🤺

🐱‍👤方式一

  import os
  from threading import Thread,current_thread
  
  def task(name):
        print('%s is running' %current_thread().name)
  if __name__ == '__main__':
        t = Thread(traget=task,args=('线程1',))
        t.start()
        print('主线程',current_thread().name)
  #ps: 太快了,所以会出现屏幕打印在同一行的情况。莫慌,这是正常的🐳
      比如-----  Thread-1 is running主线程 MainThread

🐱‍👓方式二

  from threading import Thread
  class Mythread(Thread):
        def __init__(self,n):
              super().__init__()
              self.n=n
        def run(self):
              print('%s is running' %self.name)
  if __name__ == '__main__':
        t = Mythread('线程1')
        t.start()

🔈🔈🔈根据打印结果可以看的出 线程的开销是远远小于进程的,开启的速度非常快

🐱‍🚀线程的内存空间共享

  n = 100
  def task():
        global n
        n = 0
  if __name__ == '__main__':
        t = Thread(target=task)
        t.start()
        t.join()           # 等待结束是为了防止还没有运行task就打印了主线程哦
        print('主线程',n)
   #ps:打印结果为0

🔈🔈🔈所以,同一进程下的多个线程 共享该进程的内存资源

3、📜📜📜线程对象的相关方法📜📜📜

  from threading import Tread,current_thread,active_count
  def task():
        print('%s is running'%current_thread().name)
  if __name__ == '__main__':
        t = Thread(target=task)
        t.start()    
        print(active_count())   # 查看子线程个数
        print('主线程') 
        print(t.is_alive())          # 判断线程是否存活
        print('主线程',current_thread().name)

4、🛡⚔⚔🛡守护线程🛡⚔⚔🛡

一、对于⏰主进程⏰来说,守护进程随着主进程的代码运行完毕而结束,主进程代码运行完毕,但是主进程并未终止运行,会等着非守护的子进程结束

二、对于⏰主线程⏰来说,守护进程等着主进程运行完毕才结束,是指非守护的所有子线程运行结束

三、守护线程🍭小例子🍭

  import time
  from threading import Thread,current_thread
  
  def task(n):
        print('%s is running' %current_thread().name)
        time.sleep(n)
        print('%s is ending' %current_thread().name)
  if __name__ == '__main__':
        t1 = Thread(target=task,args=(3,))
        t2 = Thread(target=task,args=(5,))
        t3 = Thread(target=task, args=(100,))   # 没有运行完毕 随着主进程结束而结束,看不到Thread-3 is ending
        t3.daemon=True
        t1.start()
        t2.start()
        t3.start()
        print('主')

5、🔐🔐🔐互斥锁🔐🔐🔐(线程)

锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

测试代码

  improt time
  from threading import Thread,Lock
  
  n = 100
  mutex = Lock()
  def task():
        global n
        with mutex:
        temp = n
        time.sleep(0.01)
        n = temp-1            🔈🔈🔈#ps: 线程太快了。如果不加🔒 在测试过程中也会看到 
  if __name__ == '__main__':
        thread_1 =  []
        for i in range (100):
              t = Thread(targat=task)
              thread_1.append(t)
              t.start()
  
        for obj in thread_1:
              obj,join()
        print('主线程',n)

开启子线程太快了。如果不加锁🔒 测试的结果也是0,看起来数据安全没有问题,但是用timesleep睡个一秒就会发现修改的问题。

6、📡📡📡信号量📡📡📡

同进程的一样、Semaphore管理一个内置的计数器

每当调用acquire()时内置计数器-1

调用release() 时内置计数器+1;

计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

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

  import random
  def func():
        sm.acquire():
        print('%s get sm' %threading.current_thread().getName())
        time.sleep(random.randint(1,2))
        sm.release()
  id __name__ == '__main__':
        sm = Semaphore(5)         #限制信号量为5
        for i in range(23):
              t = Thread(target=func)
              t.start()

7、☠死锁🔒与递归👣👣

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

  from threading import Thread,Lock
  import time

  mutexA = Lock()
  mutexB = Lock()

  class Mythread(Thread):
      def __init__(self,name):
          super().__init__()
          self.name = name

      def run(self) -> None:
          self.f1()
          self.f2()

      def f1(self):
          mutexA.acquire()                           #线程1 抢到了🔒A
          print("%s 抢到了A锁" %self.name)

          mutexB.acquire()
          print("%s 抢到了B锁" %self.name)         # 线程1 抢到了🔒B
          mutexB.release()                     # 线程1 释放🔒B

          mutexA.release()                      # 线程1 释放🔒A


      def f2(self):
          mutexB.acquire()
          print("%s 抢到了B锁" % self.name)    # 线程1 抢到了🔒B  
          time.sleep(0.1)                  #🔒A被释放,所以线程2 抢到🔒A

          mutexA.acquire()
          print("%s 抢到了A锁" % self.name)   # 线程1 继续抢🔒A 但是 🔒A已经被线程A抢了,进入阻塞状态
          mutexA.release()

          mutexB.release()

  if __name__ == '__main__':
      t1 = Mythread("线程1")
      t2 = Mythread("线程2")
      t3 = Mythread("线程3")
      t4 = Mythread("线程4")

      t1.start()
      t2.start()
      t3.start()
      t4.start()
      print("主线程")

解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁🔏RLock。

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

mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

  from threading import Thread,RLock,Lock
  import time
  mutexA = mutexB = RLock()
  class Mythread(Thread):
      def __init__(self,name):
          super().__init__()
          self.name = name

      def run(self) -> None:
          self.f1()
          self.f2()

      def f1(self):
          mutexA.acquire()
          print("%s 抢到了A锁" %self.name)

          mutexB.acquire()
          print("%s 抢到了B锁" %self.name)
          mutexB.release()
          mutexA.release()

      def f2(self):
          mutexB.acquire()
          print("%s 抢到了B锁" % self.name)
          time.sleep(0.1)

          mutexA.acquire()
          print("%s 抢到了A锁" % self.name)
          mutexA.release()
          mutexB.release()
  if __name__ == '__main__':
      t1 = Mythread("线程1")
      t2 = Mythread("线程2")
      t3 = Mythread("线程3")
      t4 = Mythread("线程4")

      t1.start()
      t2.start()
      t3.start()
      t4.start()
      print("主线程")

8、🌪🌪🌪事件Event🌪🌪🌪

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

  event.isSet():返回event的状态值;

  event.wait():如果 event.isSet()==False将阻塞线程;

  event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

  event.clear():恢复event的状态值为False。

🚦🚦🚦红绿灯小案例(真的只有红灯和绿灯)

  from threading import Event,Thread,current_thread
  import time
  import random
  
  e = Event()
  def task():
  while True:
        e.clear()
        print('红灯亮')
        time.sleep(2)
        
        e.set()
        print('绿灯亮')
        time.sleep(2)
  def task2():
        while True:
        if e.is_set():
              print('绿灯亮了,快跑啊%s' %current_thread().name)
              break
        else:
              print('stop!站住别动%s' %current_thread().name)
              e.wait()
  if __name__ == '__main__':
        Thread(target=task1).start()
        while True:
              time.sleep(random.randint(1,3))
              Thread(target=task2).start()

9、⌛⌛⌛定时器⏳⏳⏳

一句话:指定时间后操作

  from threading import Timer
  def hello():
      print("hello, world")
  t = Timer(1, hello)
  t.start()   # after 1 seconds, "hello, world" will be printed
  # 其他功能
        q = queue.PriorityQueue(3)
        q.put((10,111))  # 前面优先级别
  q = queue.lifoQueue(3) # 倒着取

10、线程queue

queue队列 :使用import queue,用法与进程Queue一样

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

当信息必须在多个线程之间安全交换时,队列在线程编程中特别有用。

import queue

  队列
  q = queue.Queue(3)
  q.put(111)
  q.put("aaa")
  q.put((1,2,3))
  
  print(q.get())
  print(q.get())
  print(q.get())

  堆栈
  q = queue.LifoQueue(3)
  q.put(111)
  q.put("aaa")
  q.put((1,2,3))
  
  print(q.get())
  print(q.get())
  print(q.get())

  优先级队列
  = queue.PriorityQueue(3)
  q.put((10,111))
  q.put((11,"aaa"))
  q.put((-1,(1,2,3)))

  print(q.get())
  print(q.get())
  print(q.get())
posted @ 2021-01-21 19:04  小刘学python  阅读(112)  评论(0编辑  收藏  举报