多线程

  • 什么是多线程
  • 开启线程的两种方式
  • 进程和线程的区别
  • Thread对象的其他属性和方法
  • 守护线程
  • 死锁现象与递归锁
  • 信号量、Event定时器
  • 线程Queue
  • 进程池和线程池

什么是多线程

在传统意义上,每个进程有一个地址空间,而且默认就会有一个控制线程。

线程顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于CPU),而一条流水线必须属于一个车间,一个车间的工作过程是一个进程,车间负责把资源整合到一起,是一个资源单位,而一个车间至少要有一条流水线。

所以,进程只是用来把资源整合到一起,而线程才是CPU上的执行单位。

多线程(多个控制线程)的概念是:在一个进程中存在多个线程,多个线程共享该进程的地址空间,相当于一个车间内有多条流水线,都公用一个车间的资源。

so:进程之间数据不共享,线程之间数据是共享的。

开启线程的两种方式

  1. # 第一种
  2. from threading import Thread
  3. import time
  4. def task(name):
  5. print('%s is running.'%name)
  6. time.sleep(2)
  7. print('%s is done.'%name)
  8. if __name__ == '__main__':
  9. t = Thread(target=task,args=('子线程',))
  10. t.start()
  11. print('主线程')
  12. # 运行结果
  13. 子线程 is running.
  14. 主线程
  15. 子线程 is done.
  16. ---------------------------------------------------
  17. # 第二种
  18. from threading import Thread
  19. import time
  20. class MyThread(Thread):
  21. def __init__(self,name):
  22. super(MyThread, self).__init__()
  23. self.name = name
  24. def run(self):
  25. print('%s is running.'%self.name)
  26. time.sleep(2)
  27. print('%s is done.'%self.name)
  28. if __name__ == '__main__':
  29. t = MyThread('子线程')
  30. t.start()
  31. print('主线程')
  32. # 运行结果
  33. 子进程 is running.
  34. 主进程
  35. 子进程 is done.

进程和线程的区别

  • 开进程的开销远大于开线程
  • 同一进程内的多个线程共享该进程的地址空间
  • PID

验证开进程的开销远大于开线程

  1. # 开启多进程
  2. from multiprocessing import Process
  3. import time
  4. class MyProcess(Process):
  5. def __init__(self,name):
  6. super(MyProcess, self).__init__()
  7. self.name = name
  8. def run(self):
  9. print('%s is running.'%self.name)
  10. time.sleep(2)
  11. print('%s is done.'%self.name)
  12. if __name__ == '__main__':
  13. p = MyProcess('子进程')
  14. p.start()
  15. print('主进程')
  1. # 开启多线程
  2. from threading import Thread
  3. import time
  4. class MyThread(Thread):
  5. def __init__(self,name):
  6. super(MyThread, self).__init__()
  7. self.name = name
  8. def run(self):
  9. print('%s is running.'%self.name)
  10. time.sleep(2)
  11. print('%s is done.'%self.name)
  12. if __name__ == '__main__':
  13. p = MyThread('子线程')
  14. p.start()
  15. print('主线程')

如果这两段代码你分别运行一遍,你就可以明显的看出来,在开启多进程的时候,首先打印的是主进程,因为子进程此时在申请内存地址,然后才会把子进程打印出来;

而在开启多线程的时候,会立马把这个子进程首先打印出来。

同一个进程内的多个进程共享该进程的地址空间

说白了就是线程之间数据共享,因为之前做过了进程之间数据不共享的实验,所以这次就写一个线程之间共享的代码:

  1. from threading import Thread
  2. from multiprocessing import Process
  3. import time
  4. n = 100
  5. def task(name):
  6. global n
  7. n = 99
  8. print('[%s]内n的值为<%s>'%(name,n))
  9. if __name__ == '__main__':
  10. t = Thread(target=task,args=('子线程',))
  11. t.start()
  12. print('主线程n的值为%s'%n)
  13. # 运行结果为
  14. [子线程]内n的值为<99>
  15. 主线程n的值为99

足以证明线程之间数据是共享的。

瞅一眼pid

  1. # 查看主进程和子进程的pid
  2. from multiprocessing import Process,current_process
  3. import time
  4. def task():
  5. print('子进程:',current_process().pid)
  6. time.sleep(2)
  7. if __name__ == '__main__':
  8. p = Process(target=task,name='子进程')
  9. p.start()
  10. print('主进程:',current_process().pid)
  11. # 运行结果为:
  12. 主进程: 7912
  13. 子进程: 12092
  1. # 查看主线程和子线程的pid
  2. from threading import Thread,current_thread
  3. import time,os
  4. def task():
  5. print('子线程:',os.getpid())
  6. time.sleep(2)
  7. if __name__ == '__main__':
  8. t = Thread(target=task,name='Thread子线程')
  9. t.start()
  10. print('主线程:',os.getpid())
  11. # 运行结果为:
  12. 子线程: 9060
  13. 主线程: 9060
  14. 因为在多线程中,每个线程都是平级的,没有子线程的概念,为了方便理解,所以叫为'子线程'

Thread对象的属性和方法

name和getName()方法,其中name设置线程的名字,getName获取线程的名字

  1. from threading import Thread,current_thread
  2. import time,os
  3. def task():
  4. print('子线程名为:',current_thread().getName()) # 获取当前线程的名字
  5. time.sleep(2)
  6. if __name__ == '__main__':
  7. t = Thread(target=task,name='Thread子线程') # 设置子线程的名字
  8. t.start()
  9. print('主线程')
  10. # 运行结果
  11. 主线程
  12. 子线程名为: Thread子线程

join()方法和is_alive()方法

join()方法让主线程等待子线程完成之后再进行,is_alive()方法检测当前线程是否存活

  1. from threading import Thread,current_thread
  2. import time,os
  3. def task(name):
  4. print('%s is running'%name)
  5. time.sleep(2)
  6. print('%s is done'%name)
  7. if __name__ == '__main__':
  8. t = Thread(target=task,args=('子进程1',))
  9. t.start()
  10. print(t.is_alive()) # 判断是否存活
  11. t.join() #让主进程等待
  12. print('主线程')
  13. print(t.is_alive()) # 判断是否存活
  14. # 运行结果为
  15. 子进程1 is running
  16. True
  17. 子进程1 is done
  18. 主线程
  19. False

active_count()检查存活的线程数量

  1. from threading import Thread,current_thread,active_count # 导入这个模块
  2. import time,os
  3. def task(name):
  4. print('%s is running'%name)
  5. time.sleep(2)
  6. print('%s is done'%name)
  7. if __name__ == '__main__':
  8. t1 = Thread(target=task,args=('子进程1',))
  9. t2 = Thread(target=task,args=('子进程2',))
  10. t1.start()
  11. t2.start()
  12. print(active_count()) # 判断当前线程数
  13. t1.join()
  14. t2.join()
  15. print('主线程')
  16. # 运行结果
  17. 子进程1 is running
  18. 子进程2 is running
  19. 3
  20. 子进程2 is done
  21. 子进程1 is done
  22. 主线程

eumecate()把当前活跃线程对象拿出来

  1. from threading import Thread,current_thread,active_count,enumerate
  2. import time,os
  3. def task(name):
  4. print('%s is running'%name)
  5. time.sleep(2)
  6. print('%s is done'%name)
  7. if __name__ == '__main__':
  8. t1 = Thread(target=task,args=('子进程1',))
  9. t2 = Thread(target=task,args=('子进程2',))
  10. t1.start()
  11. t2.start()
  12. print('当前线程存活数目:',active_count())
  13. print('当前活跃线程对象:',enumerate())
  14. print('主线程')
  15. # 运行结果:
  16. 子进程1 is running
  17. 子进程2 is running
  18. 当前线程存活数目: 3
  19. 当前活跃线程对象: [<_MainThread(MainThread, started 14472)>, <Thread(Thread-1, started 8036)>, <Thread(Thread-2, started 12328)>]
  20. 主线程
  21. 子进程1 is done
  22. 子进程2 is done

守护线程

无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁

需要强调的是,运行完毕并非是终止运行

  1. 1.对主进程来说,运行完毕指的是主进程代码运行完毕
  2. 2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕后,主线程才算是运行完毕

详细解释:

  1. 1.主进程在其代码结束后就已经算是运行完毕了(守护进程此时就会被回收掉),然后主进程会一直等非守护进程的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。
  2. 2.主线程在其他非守护线程运行完毕后才算运行完毕(守护进程在此时就会回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都完毕后才能结束。

代码演示

  1. # 只有一个线程
  2. from threading import Thread
  3. import time
  4. def sayhi(name):
  5. time.sleep(2)
  6. print('%s is say hello'%name)
  7. if __name__ == '__main__':
  8. t = Thread(target=sayhi,args=('肖亚飞'))
  9. # t.setDaemon(True) # 设置成为守护线程
  10. t.daemon = True
  11. t.start()
  12. print('主线程') # 到此时主线程运行完毕
  13. print(t.is_alive()) # 判断线程是否存活
  14. # 运行结果为:
  15. 主线程
  16. True
  1. # 有多个线程
  2. from threading import Thread
  3. import time
  4. def foo():
  5. print(123)
  6. time.sleep(2)
  7. print('end123')
  8. def bar():
  9. print(456)
  10. time.sleep(2)
  11. print('end456')
  12. if __name__ == '__main__':
  13. t1 = Thread(target=foo,)
  14. t2 = Thread(target=bar,)
  15. t1.daemon = True # 将t1设置成为守护线程
  16. t1.start()
  17. t2.start()
  18. print('主线程') # 主线程代码
  19. # 运行结果为:
  20. 123
  21. 456
  22. 主线程
  23. end123
  24. end456

死锁现象与递归锁

死锁现象

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

  1. # 死锁
  2. from threading import Thread,Lock
  3. import time
  4. # 创建A锁和B锁
  5. mutexA = Lock() # 互斥锁只能acquire一次
  6. mutexB = Lock()
  7. class MyThread(Thread):
  8. def run(self):
  9. self.f1()
  10. self.f2()
  11. def f1(self):
  12. mutexA.acquire()
  13. print('%s 拿到了A锁'%self.name)
  14. mutexB.acquire()
  15. print('%s 拿到了B锁'%self.name)
  16. mutexB.release()
  17. mutexA.release()
  18. def f2(self):
  19. mutexB.acquire()
  20. print('%s 拿到了B锁'%self.name)
  21. time.sleep(0.05)
  22. mutexA.acquire()
  23. print('%s 拿到了A锁'%self.name)
  24. mutexA.release()
  25. mutexB.release()
  26. if __name__ == '__main__':
  27. for i in range(10):
  28. t = MyThread()
  29. t.start() # 执行run方法
  30. # 运行结果为:
  31. Thread-1 拿到了A
  32. Thread-1 拿到了B
  33. Thread-1 拿到了B
  34. Thread-2 拿到了A
  35. 说明:在这段代码中,f2 方法为首先拿到mutexB这把锁,在拿到之后会沉睡0.05s,操作系统会觉得这是一个IO堵塞,然后将COU切换给Thread-2使用,然后Thread-2拿到了mutexA锁后,发现:进入了死锁

而解决死锁的方法就是使用递归锁

递归锁

我们刚刚见过,互斥锁只能acquire一次,但是递归锁就可以acquire多次,就相当于每acquire一次,它的计数器就会增加1,每release一次就是减少1,当计数为0时,才可以被其它线程抢到acquire。

  1. # 使用递归锁解决死锁问题
  2. from threading import Thread,RLock
  3. import time
  4. # 创建锁
  5. mutexA = RLock()
  6. mutexB = mutexA # 相当于一把锁
  7. class MyThread(Thread):
  8. def run(self):
  9. self.f1()
  10. self.f2()
  11. def f1(self):
  12. mutexA.acquire()
  13. print('%s 拿到了A锁'%self.name)
  14. mutexB.acquire()
  15. print('%s 拿到了B锁'%self.name)
  16. mutexB.release()
  17. mutexA.release()
  18. def f2(self):
  19. mutexB.acquire()
  20. print('%s 拿到了B锁'%self.name)
  21. time.sleep(1)
  22. mutexA.acquire()
  23. print('%s 拿到了A锁'%self.name)
  24. mutexA.release()
  25. mutexB.release()
  26. if __name__ == '__main__':
  27. for i in range(10):
  28. t = MyThread()
  29. t.start()

信号量

互斥锁就是在同一时间只能有一个任务抢到锁去执行,而信号量也是一把锁,可以指定信号量为5,信号量就是同一时间有5个任务拿到锁去执行,如果说互斥锁是合租房屋里的人去抢一个厕所,那么信号量就相当于一群路人争夺公共厕所,公共厕所的人数有限制,这就意味着同一时间可以有多少人上公共厕所,但公共厕所容纳的人数也是一定的,这就是信号量的大小。

  1. # 信号量
  2. from threading import Thread,Semaphore,current_thread
  3. import time,random
  4. sm = Semaphore(3) # 设置信号量大小为3
  5. def task():
  6. sm.acquire()
  7. print('%s in'%current_thread().getName())
  8. time.sleep(random.randint(1,3))
  9. sm.release()
  10. if __name__ == '__main__':
  11. for i in range(10):
  12. t = Thread(target=task,)
  13. t.start()
  14. # 运行结果为
  15. Thread-1 in
  16. Thread-2 in
  17. Thread-3 in
  18. Thread-4 in
  19. Thread-5 in
  20. Thread-6 in
  21. Thread-7 in
  22. Thread-8 in
  23. Thread-9 in
  24. Thread-10 in
  25. # 其中,拿到锁和释放锁还可以有这么种写法
  26. with sm:
  27. print('%s in '%current_thread().getName())
  28. time.sleep(random.randint(1,3))

解析

  1. Semaphore管理一个内置的计数器
  2. 每当调用acquire()时内置计数器会-1
  3. 调用release()时内置计数器+1
  4. 计数器不能小于0;当计数器为0时,acquire()将阻塞线程知道其他线程调用release()

Event定时器

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

  1. from Threading import Event
  2. event = Event()
  3. event.isSet() # 返回event的状态值
  4. event.wait() # 如果event.isSet == False,则阻塞线程
  5. event.set() # 设置event的状态值为True,所有阻塞线程激活进入就绪状态,等到操作系统调度
  6. event.clear() # 恢复event的状态值为True

在高中上课时,老师讲课学生听课,学生想要下课必须等待老师发送下课信号

  1. from threading import Thread,Event
  2. import time
  3. event = Event()
  4. def student(name):
  5. print('%s 正在听课'%name)
  6. event.wait()
  7. print('%s 课件活动'%name)
  8. def teacher(name):
  9. print('老师 %s 正在讲课 '%name)
  10. time.sleep(7)
  11. event.set()
  12. if __name__ == '__main__':
  13. stu1 = Thread(target=student,args=('李鹏',))
  14. stu2 = Thread(target=student,args=('李坤',))
  15. stu3 = Thread(target=student,args=('魏文武',))
  16. t1 = Thread(target=teacher,args=('肖亚飞',))
  17. stu1.start()
  18. stu2.start()
  19. stu3.start()
  20. t1.start()
  21. # 运行结果
  22. 李鹏 正在听课
  23. 李坤 正在听课
  24. 魏文武 正在听课
  25. 老师 肖亚飞 正在讲课
  26. 李坤 课件活动
  27. 魏文武 课件活动
  28. 李鹏 课件活动

大学生活,老师讲他自己的,学生想下课就下课

  1. from threading import Thread,Event
  2. import time
  3. event = Event()
  4. def student(name):
  5. print('学生 <%s> 正在听课'%name)
  6. event.wait(3) # 设置超时时间,等待2秒就走
  7. print('学生 <%s> 课件活动'%name)
  8. def teacher(name):
  9. print('%s 正在讲课'%name)
  10. time.sleep(7)
  11. event.set()
  12. if __name__ == '__main__':
  13. stu1 = Thread(target=student,args=('李鹏',))
  14. stu2 = Thread(target=student,args=('李坤',))
  15. stu3 = Thread(target=student,args=('大山',))
  16. t1 = Thread(target=teacher,args=('肖亚飞',))
  17. stu1.start()
  18. stu2.start()
  19. stu3.start()
  20. t1.start()
  21. # 运行结果
  22. 学生 <李鹏> 正在听课
  23. 学生 <李坤> 正在听课
  24. 学生 <大山> 正在听课
  25. 肖亚飞 正在讲课
  26. 学生 <李坤> 课件活动 # 3s超时时间过去了
  27. 学生 <李鹏> 课件活动
  28. 学生 <大山> 课件活动

例如,有多个工作线程尝试连接MySQL,我们想要在连接前确保MySQL服务正常才能让那些工作线程去连接MySQL服务器,如果连接不成功,都回去尝试连接。那么我们需要采用Event机制来协调各个线程之间的连接操作

  1. # 检查mysql连接
  2. from threading import Thread,Event,current_thread
  3. import time,random
  4. # 先生成定时器对象
  5. event = Event()
  6. def conn_mysql():
  7. count = 1
  8. while not event.is_set(): # 默认为False
  9. if count > 3:
  10. raise TimeoutError('连接超时')
  11. print('<%s> 第%s次尝试连接'%(current_thread().getName(),count))
  12. event.wait(0.5)
  13. count += 1
  14. print('<%s> 连接成功'%current_thread().getName())
  15. def check_mysql():
  16. print('%s is checking'%current_thread().getName())
  17. time.sleep(5)
  18. event.set()
  19. if __name__ == '__main__':
  20. for i in range(3):
  21. t = Thread(target=conn_mysql,)
  22. t.start()
  23. t = Thread(target=check_mysql)
  24. t.start()
  25. # 运行结果为
  26. <Thread-1> 1次尝试连接
  27. <Thread-2> 1次尝试连接
  28. <Thread-3> 1次尝试连接
  29. Thread-4 is checking
  30. <Thread-1> 连接成功
  31. <Thread-2> 连接成功
  32. <Thread-3> 连接成功
  33. <Thread-3> 2次尝试连接
  34. <Thread-1> 2次尝试连接
  35. <Thread-2> 2次尝试连接
  36. <Thread-2> 连接成功
  37. <Thread-1> 连接成功
  38. <Thread-1> 3次尝试连接
  39. <Thread-3> 连接成功
  40. <Thread-2> 3次尝试连接
  41. <Thread-3> 3次尝试连接
  42. <Thread-2> 连接成功
  43. <Thread-1> 连接成功
  44. <Thread-3> 连接成功

定时器

定时器,指定n秒后执行某操作

  1. # 定时器
  2. from threading import Timer
  3. def hello():
  4. print('hello world')
  5. t = Timer(2,hello)
  6. t.start()

基于定时器实现验证码登录

  1. # 基于定时器实现验证码登录
  2. from threading import Thread,Timer
  3. import random
  4. class Code():
  5. def __init__(self):
  6. self.make_cache()
  7. # 在登录的时候就应该有一个验证码
  8. def make_cache(self,interval=5):
  9. self.cache = self.make_code()
  10. print(self.cache)
  11. self.t = Timer(interval,self.make_cache)
  12. self.t.start()
  13. # 生成验证码
  14. def make_code(self,n=4): # 验证码的个数
  15. res = ''
  16. for i in range(n):
  17. s1 = str(random.randint(0,9))
  18. s2 = chr(random.randint(65,90))
  19. res += random.choice([s1,s2]) # 随机字符串
  20. return res
  21. # 验证
  22. def check(self):
  23. while True:
  24. code = input('请输入验证码>>>').strip()
  25. if code.upper() == self.cache:
  26. print('验证码输入正确')
  27. self.t.cancel()
  28. break
  29. obj = Code()
  30. obj.check()
  31. # 运行结果为
  32. IFI4
  33. 请输入验证码>>>PRC0
  34. PRCO878M
  35. 请输入验证码>>>878M
  36. 验证码输入正确

线程Queue

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

线程有三种用法

  • 队列:先进先出
  • 堆栈:先进后出
  • 优先级队列:存储数据时可设置优先级的队列

下面依次来演示一下

队列:先进先出

  1. # 线程queue-->队列
  2. import queue
  3. q = queue.Queue(3) # 设置队列为3
  4. q.put('first')
  5. q.put(2)
  6. q.put('third')
  7. # 打印
  8. print(q.get())
  9. print(q.get())
  10. print(q.get())
  11. # 打印结果为
  12. first
  13. 2
  14. third

堆栈:先进后出

  1. # 线程queue-->堆栈
  2. import queue
  3. q = queue.LifoQueue(3)
  4. q.put(1)
  5. q.put(2)
  6. q.put(3)
  7. print(q.get())
  8. print(q.get())
  9. print(q.get())
  10. # 打印结果为
  11. 3
  12. 2
  13. 1

优先级队列:存储数据时可设置优先级的队列

  1. # 线程queue-->优先级
  2. import queue
  3. q = queue.PriorityQueue(3)
  4. q.put((10,'first')) # 10 代表优先级
  5. q.put((40,2))
  6. q.put((20,'third'))
  7. print(q.get()) # 数字越小优先级越高
  8. print(q.get())
  9. print(q.get())
  10. # 打印结果为
  11. (10, 'first')
  12. (20, 'third')
  13. (40, 2)

进程池和线程池

在刚开始学多线程或多进程时,我们迫不及待的基于多线程、多进程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数会随着并发的客户端的数目增多而增多,这会对服务器主机带来巨大的压力,甚至瘫痪,于是我们必须对服务器开启的进程数或线程数加以控制,让机器在一个自己能够承受的范围内运行,这就是进程池和线程池的用途,例如进程池,就是用来存放进程的池子,本质上还是基于多进程,只不过是对开启进程的数目加上了限制

介绍

  1. 官网:https://docs.python.org/dev/library/concurrent.futures.html
  2. concurrent.futures 模块提供了高度封装的异步调用接口
  3. ThreadPoolExecutor 线程池,提供异步调用
  4. ProcessPoolExecutor 进程池,提供异步调用
  5. Both implement the same interface, which is defined by the abstract Executor class.两者实现相同的接口,抽象Executor类定义该接口。

基本方法

  1. 1.submit(fn,*args,**kwargs)
  2. 异步提交任务
  3. 2.map(func,*iterables,timeout=None,chunksize=1)
  4. 取代for循环submit操作
  5. 3.shutdown(wait=True)
  6. 相当于进程池的pool.close()+pool.join()操作
  7. wait=True,等待池内所有任务执行完毕后收回资源才继续
  8. wait=False,立即返回,并不会原地等待池内的任务执行完毕
  9. 但不管wait参数为何值,整个程序都会等到所有任务执行完毕
  10. submitmap必须在shutdown之前
  11. 4.result(timeout=None)
  12. 取到结果
  13. 5.add_done_callbakc(n)
  14. 回调函数

进程池

用法

  1. from concurrent.futures import ProcessPoolExecutor
  2. import os,time, random
  3. def task(name):
  4. print('name:%s pid:%s'%(name,os.getpid()))
  5. time.sleep(random.randint(1,3))
  6. if __name__ == '__main__':
  7. pool = ProcessPoolExecutor(4) # 如果不指定的话,则为本机cpu的核数
  8. for i in range(10):
  9. pool.submit(task,'肖亚飞%s'%i) # 异步提交:提交完任务后不用在原地等待
  10. # 维持计数器,一共有10个任务,运行一个就会少1个
  11. pool.shutdown(wait=True)
  12. print('主进程')

那么把这段代码运行了之后,就可以很明显的看见有效的PID就只有4个,从而很好的控制了PID

线程池

用法

  1. from concurrent.futures import ThreadPoolExecutor
  2. import os,time,random
  3. def task(name):
  4. print('name:%s pid:%s'%(name,os.getpid()))
  5. time.sleep(random.randint(0.01-0.02))
  6. if __name__ == '__main__':
  7. start_time = time.time()
  8. pool = ThreadPoolExecutor(4)
  9. for i in range(10000):
  10. pool.submit(task,'肖亚飞%s'%i)
  11. pool.shutdown(wait=True)
  12. stop_time = time.time()
  13. print('主线程',stop_time-start_time)

查看线程的名字

  1. # 查看线程的名字
  2. from concurrent.futures import ThreadPoolExecutor
  3. import time,random,os
  4. from threading import current_thread
  5. def task():
  6. print('name:%s pid:%s'%(current_thread().getName(),os.getpid()))
  7. time.sleep(random.randint(1,3))
  8. if __name__ == '__main__':
  9. pool = ThreadPoolExecutor(max_workers=4)
  10. for i in range(100):
  11. pool.submit(task,)
  12. pool.shutdown(wait=True)
  13. print('主进程')

map方法

map(func,*iterables,timeout=None,chunksize=1) 取代for循环submit操作

  1. # map方法
  2. from concurrent.futures import ThreadPoolExecutor
  3. import time,os
  4. from threading import current_thread
  5. def task():
  6. print('name:%s pid:%s'%(current_thread().getName(),os.getpid()))
  7. time.sleep(1)
  8. if __name__ == '__main__':
  9. pool = ThreadPoolExecutor(max_workers=4)
  10. pool.map(task,range(1,12))

回调函数

可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程任务执行完毕后自动触发,并接受任务的返回值当做参数,该函数成为回调函数

  1. from concurrent.futures import ThreadPoolExecutor
  2. import requests
  3. import os
  4. def get_page(url):
  5. print('<进程%s> get %s' %(os.getpid(),url))
  6. respone=requests.get(url)
  7. if respone.status_code == 200:
  8. return {'url':url,'text':respone.text}
  9. def parse_page(res):
  10. res=res.result()
  11. print('<进程%s> parse %s' %(os.getpid(),res['url']))
  12. parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
  13. with open('db.txt','a') as f:
  14. f.write(parse_res)
  15. if __name__ == '__main__':
  16. urls=[
  17. 'https://www.baidu.com',
  18. 'https://www.python.org',
  19. 'https://www.openstack.org',
  20. 'https://help.github.com/',
  21. 'http://www.sina.com.cn/'
  22. ]
  23. p=ProcessPoolExecutor(3)
  24. for url in urls:
  25. p.submit(get_page,url).add_done_callback(parse_page) # parse_page拿到的是一个futuer对象,需要obj,result()拿到结果
  26. # 运行结果为
  27. <进程2392> get https://www.baidu.com
  28. <进程13288> get https://www.python.org
  29. <进程1268> get https://www.openstack.org
  30. <进程2392> get https://help.github.com/
  31. <进程13984> parse https://www.baidu.com
  32. <进程1268> get http://www.sina.com.cn/
  33. <进程13984> parse https://www.openstack.org
  34. <进程13984> parse http://www.sina.com.cn/
  35. <进程13984> parse https://www.python.org
  36. <进程13984> parse https://help.github.com/

提交任务的两种方式

  • 同步提交:也就是提交完任务后,就在原地等待任务执行完毕,拿到结果再去执行下一段代码
  • 异步提交:提交完任务后不在原地等待执行完毕

我们来模拟一下同步提交和异步提交

  1. # 同步提交
  2. from concurrent.futures import ThreadPoolExecutor
  3. import time,random
  4. def la(name):
  5. print('%s is laing'%name)
  6. time.sleep(random.randint(3,5))
  7. res = random.randint(7,13)*'#'
  8. return {'name':name,'res':res}
  9. def weight(shit):
  10. name = shit['name']
  11. size = len(shit['res'])
  12. print('%s 拉了 %s kg'%(name,size))
  13. if __name__ == '__main__':
  14. pool = ThreadPoolExecutor(13)
  15. shit1 = pool.submit(la,'李鹏').result()
  16. weight(shit1)
  17. shit2 = pool.submit(la,'李坤').result()
  18. weight(shit2)
  19. shit3 = pool.submit(la,'魏文武').result()
  20. weight(shit3)
  21. # 运行结果
  22. 李鹏 is laing
  23. 李鹏 拉了 9 kg
  24. 李坤 is laing
  25. 李坤 拉了 7 kg
  26. 魏文武 is laing
  27. 魏文武 拉了 9 kg
  1. # 异步调用
  2. from concurrent.futures import ThreadPoolExecutor
  3. import time,random
  4. def la(name):
  5. print('%s is laing'%name)
  6. time.sleep(random.randint(3,5))
  7. res = random.randint(7,13)*'#'
  8. return {'name':name,'res':res}
  9. def weight(shit):
  10. shit = shit.result() # 拿到这个结果
  11. name = shit['name']
  12. res = len(shit['res'])
  13. print('%s 拉了 %s kg'%(name,res))
  14. if __name__ == '__main__':
  15. pool = ThreadPoolExecutor(13)
  16. pool.submit(la,'李鹏').add_done_callback(weight) # 绑定回调函数,前面一个任务执行完成后,return返回的值(futuer对象)就会当做参数传递个weight函数
  17. pool.submit(la,'李坤').add_done_callback(weight)
  18. pool.submit(la,'魏文武').add_done_callback(weight)
  19. # 运行结果
  20. 李鹏 is laing
  21. 李坤 is laing
  22. 魏文武 is laing
  23. 魏文武 拉了 12 kg
  24. 李坤 拉了 12 kg
  25. 李鹏 拉了 11 kg

python中多线程的更多相关文章

  1. 通过编写聊天程序来熟悉python中多线程及socket的用法

    1.引言 Python中提供了丰富的开源库,方便开发者快速就搭建好自己所需要的应用程序.本文通过编写基于tcp/ip协议的通信程序来熟悉python中socket以及多线程的使用. 2.python中 ...

  2. Python 中多线程之 _thread

    _thread模块是python 中多线程操作的一种模块方式,主要的原理是派生出多线程,然后给线程加锁,当线程结束的 时候取消锁,然后执行主程序 thread 模块和锁对象的说明 start_new_ ...

  3. python中多线程相关

    基础知识 进程:进程就是一个程序在一个数据集上的一次动态执行过程 数据集:程序执行过程中需要的资源 进程控制块:完成状态保存的单元 线程:线程是寄托在进程之上,为了提高系统的并发性 线程是进程的实体 ...

  4. Python中多线程与多进程的恩恩怨怨

    概念: 并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运 ...

  5. Python 中多线程共享全局变量的问题

    写在前面不得不看的一些P话: Python 中多个线程之间是可以共享全局变量的数据的. 但是,多线程共享全局变量是会出问题的. 假设两个线程 t1 和 t2 都要对全局变量g_num (默认是0)进行 ...

  6. python中多线程,多进程,多协程概念及编程上的应用

    1, 多线程 线程是进程的一个实体,是CPU进行调度的最小单位,他是比进程更小能独立运行的基本单位. 线程基本不拥有系统资源,只占用一点运行中的资源(如程序计数器,一组寄存器和栈),但是它可以与同属于 ...

  7. python中多线程,多进程,队列笔记(一)

    threading简介:If you want your application to make better use of the computational resources of multi- ...

  8. python中多线程(1)

    一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 1.创建线程的开销比创建进程的开销小, ...

  9. python中多线程与非线程的执行性能对比

    此对比说明了一件事: 如果是IO型应用,多线程有优势, 如果是CPU计算型应用,多线程没必要,还有实现锁呢. #!/usr/bin/env python # -*- coding: utf-8 -*- ...

随机推荐

  1. python学习笔记(二十八)日志模块

    我们在写程序的时候经常会打一些日志来帮助我们查找问题,这次学习一下logging模块,在python里面如何操作日志.介绍一下logging模块,logging模块就是python里面用来操作日志的模 ...

  2. Hadoop NameNode 高可用 (High Availability) 实现解析

    转载自:http://reb12345reb.iteye.com/blog/2306818 在 Hadoop 的整个生态系统中,HDFS NameNode 处于核心地位,NameNode 的可用性直接 ...

  3. POJ2992:Divisors(求N!因子的个数,乘性函数,分解n!的质因子(算是找规律))

    题目链接:http://poj.org/problem?id=2992 题目要求:Your task in this problem is to determine the number of div ...

  4. js判断用户是在PC端或移动端访问

    js如何判断用户是在PC端和还是移动端访问.  最近一直在忙我们团队的项目“咖啡之翼”,在这个项目中,我们为移动平台提供了一个优秀的体验.伴随Android平台的红火发展.不仅带动国内智能手机行业,而 ...

  5. SEO笔记:Anatomy of a URL

    Dr. Peter J. Meyers 原文链接:https://moz.com/blog/seo-cheat-sheet-anatomy-of-a-url 原文主要通过对比讲解 SEO优化后的URL ...

  6. JavaScript实现Map功能

    JavaScript中没有类似Java中的Map集合类的实现,自己做了简单实现,如下: function Map() { this.elements = new Array(); this.size= ...

  7. linux 常用命令总结(二)

    1. linux下以指定的编码打开文件:LANG=zh_CN vi fileName 2. 查看系统内存使用,可以使用free -m 或 top 3. 使用env查看所有环境变量 4. df –h 查 ...

  8. C#——图片操作类简单封装

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Dr ...

  9. Linux笔记 #05# 断开远程连接后保持程序运行

    教程:Linux 技巧:让进程在后台可靠运行的几种方法 网上搜索了一下,方法很多,选用最流行的 screen 命令参考:http://man.linuxde.net/screen 1. 安装 root ...

  10. js中fn()和return fn()的区别

    参考文章:http://www.jb51.net/article/87977.htm 这文章中没有讲明白,其实只要把文章里的代码加和不加return调试一下就知道是怎么回事了. var i = 0; ...