[python基础]多任务并发(进程,线程)


更多关于python线程

线程

  1. Thread

    class threading.Thread(*group=None*, *target=None*, *name=None*, args=(), kwargs={}, daemon=None*)
    参数 说明
    group 应该为 None;为了日后扩展 ThreadGroup 类实现而保留
    target 用于 run() 方法调用的可调用对象(可以是函数).默认是 None,表示不需要调用任何方法
    name 是线程名称.默认情况下,由"Thread-N"格式构成一个唯一的名称,其中 N 是小的十进制数。
    args 是用于调用目标函数的参数元组。默认是 ()
    kwargs 是用于调用目标函数的关键字参数字典。默认是 {}
    daemon 如果是 None (默认值),线程将继承当前线程的守护模式属性,显式地设置该线程是否为守护模式
    函数 说明
    start() 开始线程活动。同一线程对象在一个线程中最懂只能被调用一次,他安排的对象run()方法在一个独立的控制进程中调用;如果同一线程对象中调用这个方法的次数大于一次,会抛弃RuntimeError
    run() 代表线程活动的方法
    join(timeout=None) 等待,直到线程终结。这会阻塞调用这个方法的线程,知道被调用join()的线程终结
    name 只用于识别的字符串。它没有语义。多个线程可以赋予相同的名称。 初始名称由构造函数设置
    getName() 旧的name取值,直接当作特征属性使用它
    setName() 旧的name设值API;可以直接做特征属性使用它
    ident 这个线程的 ‘线程标识符’,如果线程尚未开始则为 None 。这是个非零整数。注意:一个线程退出而另外一个线程被创建,线程标识符会被复用。即使线程退出后,仍可得到标识符
    native_id 此线程的原生集成线程 ID。 这是一个非负整数,或者如果线程还未启动则为 None。 这表示线程 ID (TID) 已被 OS (内核) 赋值给线程。 它的值可能被用来在全系统范围内唯一地标识这个特定线程(直到线程终结,在那之后该值可能会被 OS 回收再利用)
    is_alive() 返回线程是否存活当 run() 方法刚开始直到 run() 方法刚结束,这个方法返回 `True
    daemon 一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False
    isDaemon 旧的 name 取值 API;建议直接当做特征属性使用它
    setDaemon 旧的 name 设值 API;建议直接当做特征属性使用它
    import threading
    import time
    
    
    def show_mes(mes="我才刚开始"):
        print(mes)
    
    
    if __name__ == "__main__":
        t1 = threading.Thread(target=show_mes)
        t1.start()
        time.sleep(1)
        t1.join(timeout=2)
        print("hahhah")
    
    
    • 返回当前存活的线程类对象:threading.alive_count()

    • 返回当前调试者调用的控制线程的对象:threading.current_thread()

    • 获取当前主线程: threading.main_threading

    • 获取线程默认最大回响延迟:threading.TIMEOUT_MAX

    • 获取当前线程对象的"线程标识符":threading.get_ident()

    • 获取内核分配给当前线程的原生集成线程:threading.get_native_id

    • 以列表形式返回当前所有存活的线程对象:threading.enumerate()

    import threading
    
    
    g_num = 0
    
    
    def test1(num):
        print("当前正在调用的线程是{}".format(threading.current_thread()))
        global g_num
        for i in range(num):
            g_num += 1
    
            
    def test2(num):
        print("当前正在调用的线程是{}".format(threading.current_thread()))
        global g_num
        for i in range(num):
            g_num += 1
            
            
    def main():
        t1 = threading.Thread(target=test1, args=(100, ))
        t2 = threading.Thread(target=test2, args=(100, ))
        print("当前存活的线程数是%d " % threading.active_count())
        print("当前存活的线程是{} ".format(threading.enumerate()))
        t1.start()
        print("当前存活的线程数是%d " % threading.active_count())
        print("当前存活的线程是{} ".format(threading.enumerate()))
        t2.start()
        print("当前存活的线程数是%d " % threading.active_count())
        print("当前存活的线程是{} ".format(threading.enumerate()))
        
    	
    if __name__ == "__main__":
        print("当前主线程是{}".format(threading.main_threading))
        print("系统默认线程调用最大的延时为 %d s" % threading.TIMEOUT_MAX)
        main()
    

    观察到threading.alive_count*() == len(threading.enumerate())

    创建线程本地数据

    import threading
    
    
    if __name__ == "__main__":
    	mydata = threading.local()
        mydata.x = 1
        print("已创建本地线程{},其中本地存储的数据为{}".format(mydata, mydata.x))
    
    1. 互斥锁

      class threading.Lock

      [2]原始锁处于 “锁定” 或者 “非锁定” 两种状态之一。它被创建时为非锁定状态。它有两个基本方法, acquire()release() 。当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError 异常

      在开始系统的学习之前,我们先看下面一段代码

      import time
      import threading
      
      
      def text1(num):
          global g_num
          for i in range(num):
          	g_num += 1
      
      
      def text2(num):
          global g_num
          for i in range(num):
       		g_num += 1
              
              
      def main():
          t1 = threading.Thread(target=text1, args=(100, ))
          t2 = threading.Thread(target=text2, args=(100, ))
          print(g_num)
          
      
      if __name__ == "__main__":
          main()
      

      可以发现返回了200,在小范围数字内多做了几次实验,发现都是如此,说明__线程之间的全局变量是共享的__

      def text1(num):
          global g_num
          for i in range(num):
          	g_num += 1
      
      
      def text2(num):
          global g_num
          for i in range(num):
              g_num += 1
      
      
      def main():
          t1 = threading.Thread(target=text1, args=(100000, ))
          t2 = threading.Thread(target=text2, args=(100000, ))
          print(g_num)
          
      
      if __name__ == "__main__":
          main()
      

      我们发现得出的结果和我们刚刚的结论有所不符,这位是为什么呢?

      # 可以发现 当大数据运行时, 数值与预期不符合
      # 这里就发生了资源竞争[1]
      """
          [1]多线程任务进行时,全局变量是共享的
          这就可能会导致在某一时刻多个线程同时调用同一全局变量的情况
          (各个线程之间是相对独立的)     
      """
      # 这时候就需要一个新的概念————互斥锁了[2]
      
      

      创建互斥锁对象threading,Lock()

      import threading 
      import time
      
      
      lock = threading,Lock()
      
      
      def text1(num):
          global g_num
          try:
              num = int(num)
              for i in range(num):
                  mes = "the state of block whther is locked or not is %s" % block.locked()
                  print(mes)
      
                  try:
                      block.acquire(blocking=True, timeout=1)
                      g_num += 1
                      block.release()
                  except RuntimeError:
                      print("没有上锁哦,不要随意解锁")
          except Exception as e:
              print("输入数据类型无法转化为整形对象")
          """
              threading.Lock().aquire(blocking=True, timeout=1) 锁上互斥锁
              :param timeout 互斥锁最大延迟时间
              :param blocking 互斥锁的初始状态
              threading.Lock().release() 打开互斥锁
              threading.Lock().locked() 查看互斥锁的状态
          """
      
      
      def text2(num):
          global g_num
          try:
              num = int(num)
              for i in range(num):
                  mes = "the state of block whther is locked or not is %s" % block.locked()
                  print(mes)
      
                  try:
                      block.acquire(blocking=True, timeout=1)
                      g_num += 1
                      block.release()
                  except RuntimeError:
                      print("没有上锁哦,不要随意解锁")
          except Exception as e:
              print("输入数据类型无法转化为整形对象")
              
      
      def main():
      	t1 = threading.Thread(target=text1, args=(100000, ))
          t2 = threading.Thread(target=text2, args=(100000, ))
          time.sleep(5)
          print(g_num)
      	
      	
      if __main__ == "__main__"":
      	main()
      
      • 阻塞或非阻塞的获取锁 threading.Lock().acquire(blocking=True/*初始化锁的是否处于堵塞状态*/,timeout=1/*超时回响*/)
      • 释放一个锁:threading.Lock().release()
      • 获取锁的状态的布尔返回真值:threading.Lock().locked()

      互斥锁的出现,使得同一全局变量不能同时在多个线程被使用,保证了一定程度上变量的可靠,但也大大降低了代码的运行速度

        ### 特殊情况——死锁
      
      import threading
      
      lock1 = threading.Lock()
      lock2 = threading.Lock()
      
      
      def test1():
          lock1.acquire()
          for i in range(10):
              lock2.acquire()
              	print("current value:{}".format(i))
              lock2.release()
          lock1.release()
          
         
      def test2():
          lock2.acquire()
          for i in range(10):
              lock1.acquire()
              	print("current value:{}".format(i))
              lock1.release()
          lock2.release()
      
          
      def main():
          t1 = threading.Thread(target=test1)
          t2 = threading.Thread(target=test2)
          t1.start()
          t2.start()
          
          
      if __name__ == "__main__":
          main()
      
    2. 递归锁

      class threading.RLock

      import threading
      import time
      
      
      lock = threading.Lock()
      lock1 = threading.Lock()
      Rlock = threading.RLock()
      temp = 0
      
      
      def index1():
          global  temp
          for i in range(10):
              lock.acquire()
              print(lock)
              temp += 1
              lock.release()
      
      
      def index2():
          global temp
          for i in range(10):
              Rlock.acquire()
              print(Rlock)
              temp += 1
              Rlock.release()
      
      
      def main():
          global temp
          t1 = threading.Thread(target=index1)
          t2 = threading.Thread(target=index2)
          t1.start()
          t2.start()
          time.sleep(2)
          print(temp)
      
      
      if __name__=="__main__":
          main()
      

      threading.RLock()中的注意点:

      1. threading.RLock().start()当一个程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。一旦锁被解锁(不属于任何线程),则抢夺所有权,设置递归等级为一,并返回。如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权。在这种情况下,没有返回值。
  2. 各种继承对象

    1. 定时器对象

      class Threading.timer(interval, function, args=None, kwargs=None)

      此类表示一个操作应该在等待一定的时间之后运行 — 相当于一个定时器。 Timer 类是 Thread 类的子类,因此可以像一个自定义线程一样工作

      from threading import * 
      
      
      def hello():
          print("hello, world")
      
      
      def main()
      	t = Timer(30.0, hello)
      	t.start() 
      
          
      if __name__ == "__main__":
          main()
      

      其他方法: cancel()

      停止定时器并取消执行计时器将要执行的操作。仅当计时器仍处于等待状态时有效

    2. 事件对象

      class threading.Event

      通过网上的一个实例:
      # ---- Event
      # ---- 捉迷藏的游戏
      import threading, time
      
      
      class Hider(threading.Thread):
          def __init__(self, cond, name):
              super(Hider, self).__init__()
              self.cond = cond
              self.name = name
      
          def run(self):
              time.sleep(1)
              # 确保先运行Seeker中的方法
      
              print(self.name + ': 我已经把眼睛蒙上了')
      
              self.cond.set()
      
              time.sleep(1)
      
              self.cond.wait()
      
              print(self.name + ': 我找到你了 ~_~')
      
              self.cond.set()
      
              print(self.name + ': 我赢了')
      
      
      class Seeker(threading.Thread):
          def __init__(self, cond, name):
              super(Seeker, self).__init__()
              self.cond = cond
              self.name = name
      
          def run(self):
              self.cond.wait()
      
              print(self.name + ': 我已经藏好了,你快来找我吧')
              self.cond.set()
      
              time.sleep(1)
              self.cond.wait()
      
              print(self.name + ': 被你找到了,哎~~~')
      
      
      def main():
          cond = threading.Event()
          seeker = Seeker(cond, 'seeker')
          hider = Hider(cond, 'hider')
          seeker.start()
          hider.start()
      
      
      if __name__ == "__main__":
          main()
      
      函数 说明
      is_set() 当且仅当内部标识为 true 时返回 True
      set() 将内部标识设置为 true 。所有正在等待这个事件的线程将被唤醒。当标识为 true 时,调用 wait() 方法的线程不会被被阻塞
      clear() 将内部标识设置为 false 。之后调用 wait() 方法的线程将会被阻塞
      wait(timeout=None) 阻塞线程直到内部变量为 true 。如果调用时内部标识为 true,将立即返回。否则将阻塞线程,直到调用 set() 方法将标识设置为 true 或者发生可选的超时
      1. class threading.Condition

        def mes_obj():
            t1 = threading.Thread(target=sum_all)
            t1.start()
        
        
        def sum_all():
            condition.acquire()
            with condition:
                while not f(input("请输入你想输入的内容>>")):
                    condition.wait(timeout=1.)
                    """
                        wait_for(predicate, timeout=None)
                        等待,直到条件计算为真
                         predicate 应该是一个可调用对象而且它的返回值可被解释为一个布尔值
                         可以提供 timeout 参数给出最大等待时间
                         
                        while not predicate():
                        cv.wait()
                        
                        notify(n=1)
                        默认唤醒一个等待这个条件的线程
                        如果调用线程在没有获得锁的情况下调用这个方法,会引发 RuntimeError 异常
                        
                        notify_all()
                        唤醒所有正在等待这个条件的线程
                    """
                    print("能运行了")
        
            with condition:
                condition.notify()
        
            condition.release()
        
        
        def f(mes):
            if mes:
                return True
            else:
                return False
        
        参数 说明
        wait(timeout=None) 等待直到被通知或发生超时。如果线程在调用此方法时没有获得锁,将会引发 RuntimeError 异常
        wait_for(predicate, timeout=None) 等待,直到条件计算为真。 predicate 应该是一个可调用对象而且它的返回值可被解释为一个布尔值。可以提供 timeout 参数给出最大等待时间
        notify(n=1) 默认唤醒一个等待这个条件的线程。如果调用线程在没有获得锁的情况下调用这个方法,会引发 RuntimeError 异常
        notify_all() 唤醒所有正在等待这个条件的线程
        1. 栅栏对象

          class Barrier(parties, action=None, timeout=None)

          • class threading.``Barrier(parties, action=None, timeout=None)

            函数
            wait(timeout=None) 可用于从所有线程中选择唯一的一个线程执行一些特别的工作
            reset() 重置栅栏为默认的初始态。如果栅栏中仍有线程等待释放,这些线程将会收到 BrokenBarrierError 异常。
            abort() 使栅栏处于损坏状态。 这将导致任何现有和未来对 wait() 的调用失败并引发 BrokenBarrierError
            parties 冲出栅栏所需要的线程数量
            n_waiting 当前时刻正在栅栏中阻塞的线程数量
            broken 一个布尔值,值为 True 表明栅栏为破损态

            创建一个需要 parties 个线程的栅栏对象。如果提供了可调用的 action 参数,它会在所有线

进程

  1. 先看个实例

    import multiprocessing
    
    
    def f(x):
        return x*x
    
    
    def main():
        with multiprocessing.Pool(5) as p:
            print(p.map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
    
    
    if __name__ == "__main__":
        main()
    

    上下文和启动方式

    模式 说明
    spawn 父进程启动一个新的Python解释器进程。子进程只会继承那些运行进程对象的 run() 方法所需的资源。特别是父进程中非必须的文件描述符和句柄不会被继承。相对于使用 fork 或者 forkserver,使用这个方法启动进程相当慢
    fork 父进程使用 os.fork() 来产生 Python 解释器分叉。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全分叉多线程进程是棘手的
    forkserver 程序启动并选择* forkserver * 启动方法时,将启动服务器进程。从那时起,每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。分叉服务器进程是单线程的,因此使用 os.fork() 是安全的
    • 选择一个启动方式:在if __name__ == '__main__'中调用multiprocessing.set_start_method(mode)来获取上下文

      请尽量避免multiprocessing.set_start_method()的使用

    import multiprocessing
    import os
    
    
    def info(title):
        print(title)
        print("module name", __name__)
        print("parent process", os.getppid())
        print("process id", os.getpid())
    
    
    def f(name):
        info("function f")
        print("hello", name)
    
    
    def foo(q):
        q.put("hello")
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method('spawn')
        q = multiprocessing.Queue()
        p = multiprocessing.Process(target=foo, args=(q, ))
        p.start()
        print(q.get())
        p.join()
    

    也可以使用multiprocessing.get_context()来获取上下文

    在进程之间交换对象

    1. 队列

      Queue类是一个近似queue.Queue的克隆

      import multiprocessing
      
      
      def f(q):
          q.put([42, None, 'hello'])
      
      
      if __name__ == "__main__":
          q = multiprocessing.Queue()
          p = multiprocessing.Process(target=f, args=(q, ))
          p.start()
          print(q.get())
          p.join()
      

      队列是线程和进程安全

      1. multiprocessing.SimpleQueue是一个简化的Queue类

        方法:

        • multiprocessing.SimpleQueue().close()

          关闭队列:释放内存

        • multiprocessing.SimpleQueue().empty()

          判断队列是否为空

        • multiprocessing.SimpleQueue().get()

          从队列中移除并返回一个对象

        • multiprocessing.SimpleQueue().put()

          将item放入队列

        import multiprocessing
        
        
        def make_simple_queue():
            q = multiprocessing.SimpleQueue()
            print(q.empty())
            q.put({
                  
                  'name': 'Mike'})
            print(q.empty())
            q.get()
            print(q.empty())
        
        
        if __name__ == "__main__":
            make_simple_queue()
        
        1. multiprocessing.Queue([maxsize])返回一个使用一个管道和少量锁和信号量实现的共享队列实例

          方法:

          • multiprocessing.Queue().qsize()

            返回队列的大致长度(由于多线程或多进程的上下文,所以不可靠)

          • `multiprocessing.Queue().empty()

            返回队列是否为空

          • multiprocessing.Queue().full()

            返回队列是否为满

          • multiprocessing.Queue().get([block[,timeout]])

            从队列中取出并返回对象,

          • multiprocessing.Queue().get_nowait()

            相当于get(False)

          • multiprocessing.Queue().put(obj[, block[, timeout]])

            将obj放入队列

          • multiprocessing.Queue().put_nowait(obj)

            相当于put(obj, False)

          • multiprocessing.Queue().close()

            指示当前进度将不再往队列中放入对象

          • multiprocessing.Queue().join_thread()

            等待后台线程,这个方法仅在调用了close()方法之后可用,这会阻塞当前进程,直到后台线程退出

          • multiprocessing.Queue().cancel_join_thread()

            防止join_thread()方法阻塞当前进程

          import multiprocessing
          
          
          def make_queue():
              q = multiprocessing.Queue(5)
              print("当前队列状态是否为空{},队列中数据元素的个数为{}".format(q.empty(), q.qsize()))
              q.put({
                      
                      'name': 'Mike', 'age': 10})
              print("当前队列状态是否为空{},队列中数据元素的个数为{}".format(q.empty(), q.qsize()))
              print("当前获获取数据的为{}".format(q.get()))
              print("当前队列状态是否为空{},队列中数据元素的个数为{}".format(q.empty(), q.qsize()))
              q.put({
                      
                      'name': 'Mike', 'age': 10})
              q.put({
                      
                      'name': 'John', 'age': 10})
              q.put({
                      
                      'name': 'Alice', 'age': 10})
              q.put({
                      
                      'name': 'Google', 'age': 10})
              q.put({
                      
                      'name': 'FireFox', 'age': 10})
              print("当前队列是否已满{}".format(q.full()))
              print(q.get())
              print("当前队列是否已满{}".format(q.full()))
              q.close()
              q.join_thread()
              q.cancel_join_thread()
          
          
          if __name__ == "__main__":
              make_queue()
          
    2. 管道

      multiprocessing.Pipe([duplex])

      返回一对Connection对象(conn1, conn2),分别表示管道两端

      如果duplex被设置为True(默认值),那么该管道是双向。如果duplex被设置为False,那么该管道是单向的

    3. 进程

      multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

      进程对象表示在单独进程中运行的活动

      方法:

      • multiprocessing.Process().run()

        表示进程活动的方法

      • multiprocessing.Process().start()

        启动进程活动,会将对象的run()方向安排在一个单独的进程中调用

      • multiprocessing.Process().name

        获取进程的名称,该名称是一个字符串,及用于识别目的

      • multiprocessing.Process().is_alive()

        返回进程是否还活着

      • multiprocessing.Process().daemon

        判断进程是否是守护进程

      • multiprocessing.Process().join([timeout])

        是当前进程进入堵塞状态,进程无法join自身,这会导致死锁

      • multiprocessing.Process().pid

        返回进程ID

      • multiprocessing.Process().authkey

        进程的身份验证密钥(字节字符串)

      • multiprocessing.Process().sentinel

        系统对象的数字句柄,当前进程结束时将变为"ready"

      • multiprocessing.Process().kill()

        终止进程

      • multiprocessing.Process().terminate()

        终止进程

      • multiprocessing.Process().close()

        关闭Process对象,释放与之关联的所有资源

      • multiprocessing.Process().exitcode

        子进程退出的代码,如果进程尚未中止,这将是None。负值-N表示子进程被信号N中止

    其他类属性:

    • exception multiprocessing.``ProcessError

      所有 multiprocessing 异常的基类。

    • exception multiprocessing.``BufferTooShort

      当提供的缓冲区对象太小而无法读取消息时, Connection.recv_bytes_into() 引发的异常。如果 e 是一个 BufferTooShort 实例,那么 e.args[0] 将把消息作为字节字符串给出。

    • exception multiprocessing.``AuthenticationError

      出现身份验证错误时引发。

    • exception multiprocessing.``TimeoutError

      有超时的方法超时时引发。

    其它类方法:

    • multiprocessing.active_children()

      返回当前进程存活的子进程的列表

    • multiprocessing.cpu_count()

      返回当前系统的CPU数量

    • multiprocessing.current_process()

      返回当前进程相对应的Process对象

    • multiprocessing.parent_process()

      返回父进程Prograss对象

    • multiprocessing.get_all_start_method()

      返回支持的启动上下文的方法列表

    • multipricessing.freeze_support()

      提供冻结以产生Windows可执行文件的支持

    • multipricessing.get_all_start_methods()

      返回支持的启动方法的列表

    • multipricessing.get_start_method()

      返回进程时使用的启动方法名

    • multipricessing.get_context(method=None)

      返回一个Context对象,进程支持的上下文启动方法

    • multipricessing.set_start_method(method)

      设置启动子进程的方法(最多只能调用1次)

猜你喜欢

转载自blog.csdn.net/hide_in_darkness/article/details/109266855