Python-多线程

本文详细介绍了Python中的多线程,包括threading模块的使用、线程的创建与控制、守护线程、线程安全及锁的概念,以及线程替代方案如多进程和并发库。通过实例展示了如何解决线程同步问题,如死锁,并介绍了生产者消费者模型。

多线程

  • 程序:一堆代码以文本形式存入一个文档

  • 进程:程序运行的一个状态

    • 包含地址空间、内容、数据栈等
    • 每个进程由自己完全独立的运行环境,多进程共享数据是一个问题
  • 线程

    • 一个进程的独立运行片段,一个进程可以由有多个线程
    • 轻量化的进程
    • 一个进程的多个线程间共享数据和上下文运行环境
    • 共享互斥问题
  • 全局解释器锁(GIL)

    • Python代码的执行是由python虚拟机进行控制
    • 在主循环中只能拥有一个控制线程在执行
  • Python包

    • thread:有问题,不好用,Python3改成了_thread
    • threading:通行的包
    # 不使用线程,顺序执行,程序运行时间长
    # 利用time函数,生成两个函数,顺序调用,计算总的运行时间
    
    import time
    
    def loop1():
        # ctime得到当前时间
        print("Start loop 1 at:",time.ctime())
        # 睡眠一定时间
        time.sleep(4)
        print("End loop 1 at:",time.ctime())
        
    def loop2():
        # ctime得到当前时间
        print("Start loop 2 at:",time.ctime())
        # 睡眠一定时间
        time.sleep(2)
        print("End loop 2 at:",time.ctime())
    
    def main():
        print("Starting at:",time.ctime())
        loop1()
        loop2()
        print("All done at:",time.ctime())
    if __name__ == "__main__":
        main()
        
    

    输出结果为:

    Starting at: Tue Nov 27 20:35:53 2018
    Start loop 1 at: Tue Nov 27 20:35:53 2018
    End loop 1 at: Tue Nov 27 20:35:57 2018
    Start loop 2 at: Tue Nov 27 20:35:57 2018
    End loop 2 at: Tue Nov 27 20:35:59 2018
    All done at: Tue Nov 27 20:35:59 2018
    

    _thread

    • 改用多线程,缩短总时间,使用_thread
    import time
    import _thread as thread
    
    def loop1():
        # ctime得到当前时间
        print("Start loop 1 at:",time.ctime())
        # 睡眠一定时间
        time.sleep(4)
        print("End loop 1 at:",time.ctime())
        
    def loop2():
        # ctime得到当前时间
        print("Start loop 2 at:",time.ctime())
        # 睡眠一定时间
        time.sleep(2)
        print("End loop 2 at:",time.ctime())
    
    def main():
        print("Starting at:",time.ctime())
        thread.start_new_thread(loop1,())
        thread.start_new_thread(loop2,())
        print("All done at:",time.ctime())
    
    if __name__ == "__main__":
        main()
        while True:
            time.sleep(1)
    

    主线程已经结束而子线程仍在继续运行,输出结果为:

    Starting at: Tue Nov 27 21:42:32 2018
    All done at: Tue Nov 27 21:42:32 2018
    Start loop 1 at: Tue Nov 27 21:42:32 2018
    Start loop 2 at: Tue Nov 27 21:42:32 2018
    End loop 2 at: Tue Nov 27 21:42:34 2018
    End loop 1 at: Tue Nov 27 21:42:36 2018
    
    • 使用多线程传参数
    # 使用多线程传递参数
    import time
    import _thread as thread
    
    def loop1(in1):
    	# ctime得到当前时间
    	print("Start loop 1 at:", time.ctime())
    	print("我是参数 ", in1)
    	time.sleep(4)
    	print("End loop 1 at:", time.ctime())
    
    def loop2(in1, in2):
    	# ctime得到当前时间
    	print("Start loop 2 at:", time.ctime())
    	print("我是参数 ", in1, "和参数 ", in2)
    	time.sleep(2)
    	print("End loop 2 at:", time.ctime())
    
    def main():
    	print("Starting at:", time.ctime())
    	# 启动多线程的意思是用多线程去执行某个函数
    	# 启动多线程函数为start_new_thread
    	# 参数两个,一个是需要运行的函数名,第二是函数的参数作为元组使用,为空则使用空元组
    	# 注意:如果函数只有一个参数,需要参数后面加一逗号
    	thread.start_new_thread(loop1, ("往",))
    	thread.start_new_thread(loop2, ("生", "经"))
    	print("All done at:", time.ctime())
    
    if __name__ == "__main__":
    	main()
    	while True:
        	time.sleep(10)
    

    执行结果如下:

    Starting at: Tue Nov 27 21:50:16 2018
    All done at: Tue Nov 27 21:50:16 2018
    Start loop 1 at: Tue Nov 27 21:50:16 2018
    我是参数  往
    Start loop 2 at: Tue Nov 27 21:50:16 2018
    我是参数  生 和参数  经
    End loop 2 at: Tue Nov 27 21:50:18 2018
    End loop 1 at: Tue Nov 27 21:50:20 2018
    

threading

  • 直接使用threading.Thread生成Thread实例

    • t = threading.Thread(target=xxx,args=(xxx,))
    • t.start() 启动多线程
    • t.join() 等待多线程执行完成
    # threading的使用
    import time
    import threading
    
    def loop1(in1):
    	# ctime得到当前时间
    	print("Start loop 1 at:",time.ctime())
    	print("我是参数 ",in1)
    	time.sleep(4)
    	print("End loop 1 at:",time.ctime())
    
    def loop2(in1,in2):
    	# ctime得到当前时间
    	print("Start loop 2 at:",time.ctime())
    	print("我是参数 ",in1+"和参数 ",in2)
    	time.sleep(2)
    	print("End loop 2 at:",time.ctime())
    
    def main():
    	print("Starting at:",time.ctime())
    	t1 = threading.Thread(target=loop1,args=("往",))
    	t1.start()
    	t2 = threading.Thread(target=loop2,args=("生","经"))
    	t2.start()
    	print("All done at:",time.ctime())
    
    if __name__ == "__main__":
    	main()
    	while True:
    		time.sleep(10)
    

    输出结果:

    Starting at: Tue Nov 27 21:56:22 2018
    Start loop 1 at: Tue Nov 27 21:56:22 2018
    我是参数  往
    Start loop 2 at: Tue Nov 27 21:56:22 2018
    All done at: Tue Nov 27 21:56:22 2018
    我是参数  生和参数  经
    End loop 2 at: Tue Nov 27 21:56:24 2018
    End loop 1 at: Tue Nov 27 21:56:26 2018
    
  • 守护线程daemon

    • 如果在程序中将子线程设置为守护线程,则子线程会在主线程结束的时候自动退出
    • 一般认为,守护线程不重要或者不允许离开主线程独立运行
    • 守护线程能否有效跟环境相关
    # 非守护线程
    import time 
    import threading
    
    def fun():
        print("Start fun")
        time.sleep(2)
        print("End fun")
        
    print("Main thread")
    
    t1 = threading.Thread(target=fun,args=())
    t1.start()
    
    time.sleep(1)
    print("Main thread end")
    

    输出结果:

    Main thread
    Start fun
    Main thread end
    End fun
    
    # 守护线程
    import time
    import threading
    
    def fun():
        print("Start fun")
        time.sleep(2)
        print("End fun")
    
    print("Main thread")
    t1 = threading.Thread(target=fun, args=())
    # 设置守护线程方法,必须在start之前设置,否则无效
    t1.setDaemon(True)
    t1.start()
    
    time.sleep(1)
    print("Main thread end")
    

    输出结果为:

    Main thread
    Start fun
    Main thread end
    
  • 线程常用属性

    • threading.currentThread 返回当前线程变量
    • threading.enumerate 返回一个包含正在运行的线程的list,正在运行的线程指的是线程启动后,结束前
    • threading.activeCount 返回正在运行的线程数量,效果跟len(threading.enumerate)相同
    • thr.setName 给线程设置名字
    • thr.getName 得到线程的名字
    import time
    import threading
    
    def loop1():
        # ctime得到当前时间
        print("Start loop 1 at:", time.ctime())
        time.sleep(4)
        print("End loop 1 at:", time.ctime())
    
    def loop2():
        # ctime得到当前时间
        print("Start loop 2 at:", time.ctime())
        time.sleep(2)
        print("End loop 2 at:", time.ctime())
    
    def loop3():
        # ctime得到当前时间
        print("Start loop 3 at:", time.ctime())
        time.sleep(5)
        print("End loop 3 at:", time.ctime())
    
    def main():
        print("Starting at:", time.ctime())
        t1 = threading.Thread(target=loop1, args=())
        t1.setName("THR_1")
        t1.start()
    
        t2 = threading.Thread(target=loop2, args=())
        t2.setName("THR_2")
        t2.start()
    
        t3 = threading.Thread(target=loop3, args=())
        t3.setName("THR_3")
        t3.start()
        
        time.sleep(3)
    
        for thr in threading.enumerate():
            print("正在运行的线程的名字是:{0}".format(thr.getName()))
    
        print("正在运行的子线程数量为:{0}".format(threading.activeCount()))
        print("All done at:", time.ctime())
    
    if __name__ == "__main__":
        main()
        while True:
            time.sleep(10)
    

    程序结果为:

    Starting at: Tue Nov 27 22:01:56 2018
    Start loop 1 at: Tue Nov 27 22:01:56 2018
    Start loop 2 at: Tue Nov 27 22:01:56 2018
    Start loop 3 at: Tue Nov 27 22:01:56 2018
    End loop 2 at: Tue Nov 27 22:01:58 2018
    正在运行的线程的名字是:MainThread
    正在运行的线程的名字是:THR_1
    正在运行的线程的名字是:THR_3
    正在运行的子线程数量为:3
    All done at: Tue Nov 27 22:01:59 2018
    End loop 1 at: Tue Nov 27 22:02:00 2018
    End loop 3 at: Tue Nov 27 22:02:01 2018
    
  • 直接继承来自threading.Thread

    • 直接继承Thread
    • 重写run函数
    • 类实例可以直接运行
    import threading
    import time
    
    # 类需要继承threading.Thread
    class MyThread(threading.Thread):
        def __init__(self,arg):
            super(MyThread,self).__init__()
            self.arg = arg
    
        # 必须重写run函数,run函数代表的是真正执行的功能
        def run(self):
            time.sleep(2)
            print("The args for this class is {0}".format(self.arg))
    
    for i in range(5):
        t = MyThread(i)
        t.start()
        t.join()
    
    print("Main thread is done!!!!!!")
    

    结果为:

    The args for this class is 0
    The args for this class is 1
    The args for this class is 2
    The args for this class is 3
    The args for this class is 4
    Main thread is done!!!!!!
    

共享变量

  • 共享变量:当多个线程同时访问一个变量的时候,会产生共享变量的问题

  • 解决方法:锁

  • 锁(Lock):

    • 是一个标志,表示一个线程在占用一些资源
    • 使用方法
      • 上锁
      • 使用共享资源
      • 解锁,释放锁
    • 锁谁:哪个资源需要多个线程共享,就锁哪个
    • 理解:这个锁就类似于一个令牌,谁拿到这个令牌谁有使用权
    # 此程序使用锁
    import threading
    # 定义变量
    sum = 1
    loopSum = 10000
    #调用锁
    lock = threading.Lock()
    
    def myAdd():
        global sum,loopSum
        for i in range(1,loopSum):
            # 上锁
            lock.acquire()
            sum += 1
            # 解锁
            lock.release()
    
    def myMinu():
        global sum,loopSum
        for i in range(1,loopSum):
            lock.acquire()
            sum -= 1
            lock.release()
    
    if __name__ == "__main__":
        print("Starting...{0}".format(sum))
    
        t1 = threading.Thread(target=myAdd(),args=())
        t2 = threading.Thread(target=myMinu(),args=())
    
        t1.start()
        t2.start()
    
        t1.join()
        t2.join()
    
        print("Done... {0}".format(sum))
    
  • 线程安全问题

    • 如果一个资源/变量,它对于多线程来讲,不使用锁也不会引起任何问题,则称为线程安全
    • 线程不安全变量类型:list,set,dict
    • 线程安全变量类型:queue
  • 生产者消费者问题

    • 一个模型,可以用来搭建消息队列
    • queue是一个用来存放变量的数据结构,特点是先进后出,内部元素排队,可以理解成一个特殊的list
    • queue的使用
    #encoding = utf-8
    import threading
    import time
    
    #python 2
    #from Queue import Queue
    
    #python3
    import queue
    
    class Producter(threading.Thread):
        def run(self):
            global queue
            count = 0
            while True:
                #qsize返回queue内容长度
                if queue.qsize()<1000:
                    for i in range(100):
                        count = count+1
                        msg = "生产产品" + str(count)
                        #put是往queue中放入一个值
                        queue.put(msg)
                        print(msg)
                time.sleep(0.5)
    
    class Cousumer(threading.Thread):
        def run(self):
            global queue
            while True:
                if queue.qsize() > 100:
                    for i in range(3):
                        # get是从queue中取出一个值
                        msg = self.name + "消费了" + queue.get()
                        print(msg)
                time.sleep(1)
    
    if __name__ == "__main__":
        queue = queue.Queue()
    
        for i in range(500):
            queue.put("初始产品"+str(i))
        for i in range(2):
            p = Producter()
            p.start()
        for i in range(5):
            c = Cousumer()
            c.start()
    
  • 死锁问题

    • 函数1申请了锁1,函数2申请了锁2,函数1未释放锁1申请锁2,函数2未释放锁2申请锁1,这便陷入了死锁。
    • 解决方法:程序设计过程中格外注意锁的使用,一定要等释放了锁才去申请锁;或者设定申请锁的等待时间,等不到锁就跳过。
  • Semaphore()

    • 允许一个资源最多由几个多线程同时使用
    import threading
    import time
    
    # 参数定义最多3个线程同时使用线程
    semaphore = threading.Semaphore(3)
    
    def func():
        if semaphore.acquire():
            for i in range(5):
                print(threading.current_thread().getName() + ' get semaphore')
            time.sleep(15)
            semaphore.release()
            print(threading.current_thread().getName() + ' release semaphore')
    
    for i in range(8):
        t1 = threading.Thread(target=func)
        t1.start()
    
  • threading.Timer

    • Timer是利用多线程在指定时间后启动一个功能
  • 可重入锁(RLock())

    • 一个锁,可以被一个线程多次申请
    • 主要解决递归调用的时候需要申请锁的情况
    import threading
    import time
    
    class MyThread(threading.Thread):
    	def run(self):
    		global num
    		time.sleep(1)
    		
    		if mutex.acquire(1):
    			num = num + 1
    			msg = self.name+ ' set num to ' + str(num)
    			print(msg)
    			mutex.acquire()
    			mutex.release()
    			mutex.release()  
    
    num = 0
    
    #重入锁
    mutex = threading.RLock()
    def testTh():
    	for i in range(5):
    		t = MyThread()
    		t.start()
    

    如果此处使用Lock,普通锁,程序无法执行报错,使用RLock,结果如下

    Thread-6 set num to 1
    Thread-7 set num to 2
    Thread-8 set num to 3
    Thread-9 set num to 4
    Thread-10 set num to 5
    

线程替代方案

  • subprocess
    • 完全跳过线程,使用进程
    • 是派生进程的主要替代方案
    • python2.4后引入
  • multiprocessing
    • 使用threading接口派生,使用子线程
    • 允许多核或者多CPU派生进程,接口跟threading非常相似
    • python2.6
  • concurrent.futures
    • 新的异步执行操作
    • 任务级别的操作
    • python3.2后引入

多进程

  • 进程间通讯(InterprocessCommunication,IPC)

  • 进程之间无任何共享状态

  • 进程的创建

    • 直接生成Process实例对象
      直接实例化
      import multiprocessing
      from time import sleep,ctime
      
      def clock(interval):
      	while True:
      		print("The time is {0}".format(ctime()))
      		sleep(interval)
      	
      if __name__ == "__main__":
      	p = multiprocessing.Process(target=clock,args=(5,))
      	p.start()
      	while True:
      		print("Sleep...")
      		sleep(1)
      
    • 派生子类
      import multiprocessing
      from time import sleep,ctime
      
      class ClockProcess(multiprocessing.Process):
          '''
          两个函数比较重要:
          1. init构造函数
          2. run
      
          '''
          def __init__(self,interval):
              super().__init__()
              self.interval = interval
      
          def run(self):
              while True:
                  print("The time is {0}".format(ctime()))
                  sleep(self.interval)
      
      if __name__ == "__main__":
          p = ClockProcess(3)
          p.start()
          while True:
              print("Sleep...")
              sleep(1)	
      
  • 在os中查看pid、ppid以及关系

    from multiprocessing import Process
    import os
    
    def info(title):
        print(title)
        print('module name:',__name__)
        #得到父进程的id
        print("parent process id:",os.getppid())
        #得到自身进程的id
        print("process is:",os.getpid())
    
    def f(name):
        info("function-1")
        print("hello",name)
    
    if __name__ == '__main__':
        info('main line')
        p = Process(target=f,args=('cher',))
        p.start()
    

    程序结果:

    main line
    module name: __main__
    parent process id: 4780
    process is: 4776
    function-1
    module name: __mp_main__
    parent process id: 4776
    process is: 5772
    hello cher
    
  • 生产消费者模型

    produce
    Worker1
    仓库queue
    Worker2
    Cousumer1
    Cousumer2
    • JoinableQueue的使用,consumer函数依据队列queue从仓库中取出产品,producer函数生产产品根据队列queue放入仓库。

      import multiprocessing
      from time import sleep,ctime
      
      def consumer(input_q):
          print("Into consumer:{0}".format(ctime()))
          while True:
              #处理项
              item = input_q.get()
              print("pull",item,"out of q")
              #发出信号通知任务完成
              input_q.task_done()
          # 此句未被执行,因为q.join()收集到四个task_done()信号后,主进程启动,未等到print此句完
          # print("Out of consumer:{0}".format(ctime()))
      
      def producer(sequence,output_q):
          print("Into producer:",format(ctime()))
          for item in sequence:
              output_q.put(item)
              print("put",item,"into q")
          print("Out of producer:",format(ctime()))
      
      #建立进程
      if __name__ == "__main__":
          q = multiprocessing.JoinableQueue()
          #运行消费者进程
          cons_p = multiprocessing.Process(target=consumer,args=(q,))
          cons_p.daemon = True
          cons_p.start()
      
          #生产多个项,sequence代表要发送给消费者的项序列
          #在实践中,这可能是生成器的输出或通过一些其他方式生产出来
          sequence = [1,2,3,4]
          producer(sequence,q)
          #等待所有项被处理
          q.join()
      

      程序运行结果:

      Into producer: Wed Nov 28 22:00:41 2018
      put 1 into q
      put 2 into q
      put 3 into q
      put 4 into q
      Out of producer: Wed Nov 28 22:00:41 2018
      Into consumer:Wed Nov 28 22:00:41 2018
      pull 1 out of q
      pull 2 out of q
      pull 3 out of q
      pull 4 out of q
      
    • 队列queue中哨兵的使用,即控制生产消费者关系是否继续维持

      import multiprocessing
      from time import sleep,ctime
      
      #设置哨兵
      def consumer(input_q):
          print("Into consumer:{0}".format(ctime()))
          while True:
              item = input_q.get()
              if item is None:
                  break
              print("pull",item,"out of q")
          # 此句执行完成,在转入主线程
          print("Out of consumer:{0}".format(ctime()))
      
      def producer(sequence,output_q):
          print("Into producer:",format(ctime()))
          for item in sequence:
              output_q.put(item)
              print("put",item,"into q")
          print("Out of producer:",format(ctime()))
      
      
      if __name__ == "__main__":
          q = multiprocessing.Queue()
          #运行消费者进程
          cons_p1 = multiprocessing.Process(target=consumer,args=(q,))
          cons_p1.start()
      
          sequence = [1,2,3,4]
          producer(sequence,q)
      
          #有多少个消费者放置多少个None
          q.put(None)
      
          cons_p1.join()
      

      运行结果:

      Into producer: Wed Nov 28 22:21:56 2018
      put 1 into q
      put 2 into q
      put 3 into q
      put 4 into q
      Out of producer: Wed Nov 28 22:21:56 2018
      Into consumer:Wed Nov 28 22:21:56 2018
      pull 1 out of q
      pull 2 out of q
      pull 3 out of q
      pull 4 out of q
      Out of consumer:Wed Nov 28 22:21:56 2018
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值