多任务的实现有3种方式:
多进程模式;
多线程模式;
多进程+多线程模式。

** 进程: **

不同任务,例如打开一个写字本,就是开启一个新进程。

多进程

Unix/Linux操作系统提供了一个fork()系统调用
fork()调用一次,返回两次,操作系统自动把当前进程(称为父进程)复制一份(称为子进程),分别在父进程和子进程内返回。
子进程永远返回0,父进程返回子进程的ID(好处在于一个父进程可以fork()调用很多个子进程,父进程要记住子进程ID,即 `getpid()`;子进程如果想要拿到父进程的ID则调用 `getppid()` 即可。)   

** Pyhon的 os模块封装了常见的系统调用,其中就包括 fork() **

Unix/Linux/Mac: os模块

import osprint('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
结果:
Process (754) start...
I (754) just created a child process (755).
I am child process (755) and my parent is 754.

** 由于windows没有fork()调用,需要跨平台的库支持多线程。 **

跨平台: multiprocessing 模块

- Process类 进程

步骤:1. Process(target=func, args=(pro_name,)) # 创建一个Process实例,(参数:执行函数,进程名)2. start()方法启动进程3. join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步
from multiprocessing import Process
import os# 子进程要执行的代码
def run_proc(name):print('Run child process %s (%s)...' % (name, os.getppid()))if __name__=='__main__':print('Parent process %s.' % os.getppid())p = Process(target=run_proc, args=('p1',)) # 创建Process实例print('Child process will start.')p.start() # 启动p.join() # 结束print('Child process end.')
结果:
Parent process 4484.
Child process will start.
Run child process p1(7106)...
Child process end.

** 如果需要创建大量的子进程,则需要使用 进程池的方式批量创建子进程 **

- Pool类 进程池

步骤:1. Pool(4) # 进程池里面有四个进程的限制,必须有进程销毁才能继续添加,默认是CPU数2. apply_async(func, args=(pro_name,))方法,(参数:执行函数,进程名)3. close() # 禁止继续添加新的Process,在join之前4. join() # 等待所有进程执行完毕
from multiprocessing import Pool
import os, time, randomdef long_time_task(name):print('Run task %s (%s)...' % (name, os.getpid()))start = time.time()time.sleep(random.random() * 3)end = time.time()print('Task %s runs %0.2f seconds.' % (name, (end - start)))if __name__=='__main__':print('Parent process %s.' % os.getpid())p = Pool(4) # 进程池限制4个进程for i in range(1, 6):p.apply_async(long_time_task, args=('pro'+str(i),))print('Waiting for all subprocesses done...')p.close() # 禁止继续添加新的Processp.join() # 等待所有进程执行完毕print('All subprocesses done.')
结果:
Parent process 2868.
Waiting for all subprocesses done...
Run task pro1 (1280)...
Run task pro2 (3692)...
Run task pro3 (6316)...
Run task pro4 (6000)...
Task pro2 runs 1.15 seconds.
Run task pro5 (3692)...
Task pro3 runs 2.04 seconds.
Task pro4 runs 2.06 seconds.
Task pro1 runs 2.48 seconds.
Task pro5 runs 2.00 seconds.
All subprocesses done.

** 进程之间需要通信,用于交换数据等,Queue、Pipes 等多个类 **

-Queue类 进程间通信

都写两个进程共用同一个Queue队列
from multiprocessing import Process, Queue
import os, time, random# 写数据进程执行的代码:
def write(q):print('Process to write: %s' % os.getpid())for value in ['A', 'B', 'C']:print('Put %s to queue...' % value)q.put(value)time.sleep(random.random())# 读数据进程执行的代码:
def read(q):print('Process to read: %s' % os.getpid())while True:value = q.get(True)print('Get %s from queue.' % value)if __name__=='__main__':# 父进程创建Queue,并传给各个子进程:q = Queue()pw = Process(target=write, args=(q,))pr = Process(target=read, args=(q,))# 启动子进程pw,写入:pw.start()# 启动子进程pr,读取:pr.start()# 等待pw结束:pw.join()# pr进程里是死循环,无法等待其结束,只能强行终止:pr.terminate()
结果:
Process to read: 9376
Process to write: 11476
Put A to queue...
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

* 线程: *

每个任务中的不同操作,例如word中的输入文字,插入图片等

多线程

线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。  
  • 线程可以被抢占(中断)
  • 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让

线程可以分为:

  • 内核线程:由操作系统内核创建和撤销。
  • 用户线程:不需要内核支持而在用户程序中实现的线程。

Python3 线程中常用的两个模块为:

  • _thread
  • threading(推荐使用)
thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。   两种使用方式: * 函数*或者用 *类*来包装线程对象。

1. _thread

_thread.start_new_thread ( function, args[, kwargs] )

参数说明:

  • function -- 线程函数。
  • args -- 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs -- 可选参数。

2. threading 线程模块

步骤:

threading.Thread(target=func, name='Threadname')
start()
join()

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

注:由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

import time, threading# 新线程执行的代码:
def loop():print('thread %s is running...' % threading.current_thread().name)n = 0while n < 5:n = n + 1print('thread %s >>> %s' % (threading.current_thread().name, n))time.sleep(1)print('thread %s ended.' % threading.current_thread().name)print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

执行结果:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

- 线程同步: Lock() 锁

1. lock = threading.Lock()
2. lock.acquire() # 获取锁
3. lock.release() # 释放锁

注意:

获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用 `try...finally`来确保锁一定会被释放。

优点:

确保了某段关键代码只能由一个线程从头到尾完整地执行

缺点:

阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率大大下降。
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
import threading
import timeclass myThread (threading.Thread):def __init__(self, threadID, name, counter):threading.Thread.__init__(self)self.threadID = threadIDself.name = nameself.counter = counterdef run(self):print ("开启线程: " + self.name)# 获取锁,用于线程同步threadLock.acquire()print_time(self.name, self.counter, 3)# 释放锁,开启下一个线程threadLock.release()def print_time(threadName, delay, counter):while counter:time.sleep(delay)print ("%s: %s" % (threadName, time.ctime(time.time())))counter -= 1threadLock = threading.Lock()
threads = []# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)# 开启新线程
thread1.start()
thread2.start()# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)# 等待所有线程完成
for t in threads:t.join()
print ("退出主线程")

执行结果:

开启线程: Thread-1
开启线程: Thread-2
Thread-1: Thu Jan 25 13:52:57 2016
Thread-1: Thu Jan 25 13:52:58 2016
Thread-1: Thu Jan 25 13:52:59 2016
Thread-2: Thu Jan 25 13:53:01 2016
Thread-2: Thu Jan 25 13:53:03 2016
Thread-2: Thu Jan 25 13:53:05 2016
退出主线程

- 线程优先级队列( Queue)

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。

这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。

  • Queue 模块中的常用方法:

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

import queue
import threading
import timeexitFlag = 0class myThread (threading.Thread):def __init__(self, threadID, name, q):threading.Thread.__init__(self)self.threadID = threadIDself.name = nameself.q = qdef run(self):print ("开启线程:" + self.name)process_data(self.name, self.q)print ("退出线程:" + self.name)def process_data(threadName, q):while not exitFlag:queueLock.acquire()if not workQueue.empty():data = q.get()queueLock.release()print ("%s processing %s" % (threadName, data))else:queueLock.release()time.sleep(1)threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1# 创建新线程
for tName in threadList:thread = myThread(threadID, tName, workQueue)thread.start()threads.append(thread)threadID += 1# 填充队列
queueLock.acquire()
for word in nameList:workQueue.put(word)
queueLock.release()# 等待队列清空
while not workQueue.empty():pass# 通知线程是时候退出
exitFlag = 1# 等待所有线程完成
for t in threads:t.join()
print ("退出主线程")

执行结果:

开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出线程:Thread-3
退出线程:Thread-2
退出线程:Thread-1
退出主线程

- 多核CPU

如果你不幸拥有一个多核CPU,你肯定在想,多核应该可以同时执行多个线程。

如果写一个死循环的话,会出现什么情况呢?

打开Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以监控某个进程的CPU使用率。

我们可以监控到一个死循环线程会100%占用一个CPU。

如果有两个死循环线程,在多核CPU中,可以监控到会占用200%的CPU,也就是占用两个CPU核心。

要想把N核CPU的核心全部跑满,就必须启动N个死循环线程。

试试用Python写个死循环:

import threading, multiprocessingdef loop():x = 0while True:x = x ^ 1for i in range(multiprocessing.cpu_count()):t = threading.Thread(target=loop)t.start()

启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占用率仅有102%,也就是仅使用了一核。

但是用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?

因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。

不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

转载于:https://www.cnblogs.com/darksouls/p/8350767.html

Python中级 —— 03进程与线程相关推荐

  1. 怎么更进一步学python_【百尺竿头,更进一步学Python】Python进阶课程——进程,线程和协程的区别...

    本文带来各类奇怪的IT百科知识. [百尺竿头,更进一步学Python]Python进阶课程--进程:线程和协程的区别 现在多进程多线程已经是老生常谈了:协程也在最近几年流行起来.今天我们本文主要介绍进 ...

  2. python线程与进程视频教程_[PYTHON系列教程]→进程 vs. 线程

    我们介绍了多进程和多线程,这是实现多任务最常用的两种方式.现在,我们来讨论一下这两种方式的优缺点.首先,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Work ...

  3. python笔记06_进程vs线程

    进程 vs. 线程 多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程.(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apa ...

  4. Python中的进程和线程

    一.Python中两种创建进程的方式 1.os.fork只适用于Linux/Mac 2.使用multiprocessing模块可以跨平台实现多进程 使用os.fork(),像Linux平台上那样创建多 ...

  5. python并发编程-进程池线程池-协程-I/O模型-04

    目录 进程池线程池的使用***** 进程池/线程池的创建和提交回调 验证复用池子里的线程或进程 异步回调机制 通过闭包给回调函数添加额外参数(扩展) 协程*** 概念回顾(协程这里再理一下) 如何实现 ...

  6. Python基础:进程、线程、协程(2)

    进程与线程 什么是进程(process)? An executing instance of a program is called a process. Each process provides ...

  7. Python中的进程和线程(20)

    进程和线程 进程 创建多进程 进程和全局变量 传递参数和进程号 进程的状态 进程之间通信 put() get()方法 生产者和消费者 进程子类化 生产者和消费者子类化 进程池 线程 线程子类化 共享全 ...

  8. Python 并发1 进程,线程

    Python 并发1: 进程,线程 相信大家在操作系统就了解过 进程,线程,死锁,系统调度等等,对基本的概念还是有的.但是,在实践的过程上我在python上的实现却略有不足.所以重新入门Python并 ...

  9. python并发编程--进程、线程、协程、锁、池、队列

    文章目录 操作系统的概念 进程 multiprocessing模块 守护进程 使用多进程实现一个并发的socket的server 锁 生产者消费者模型 数据共享 线程threading模块 守护线程和 ...

最新文章

  1. 腾讯云答治茜:云计算为独角兽和传统企业提供了哪些沃土?
  2. 真的有能开光追的手游了!自带实机演示的那种,OPPO这次玩“大”了
  3. 深入Python(1): 字典排序 关于sort()、reversed()、sorted()、cmp()等
  4. python包里面的dll是什么_Python中的包ImportError
  5. nodejs后台启动
  6. php插件 pycharm_原来Pycharm中有这么多好用的插件|Pycharm精选插件
  7. java保存数字_Java:JFormattedTextField保存数字
  8. Python回归模型评估
  9. 电力系统matlab实验报告,基于matlab语言计算电力系统暂态稳定仿真程序实验报告.docx...
  10. TPO-22 C1 Complain about a biased article
  11. 计算机或与非门原理,依据基本原理构建现代计算机:从与非门到俄罗斯方块(基于项目的课程)...
  12. 爬虫-爬取感兴趣图片(python code 直接运行)
  13. listview选中高亮
  14. 剑指 只出现一次的数字
  15. AD制作gerber文件详细步骤
  16. 全网最详细的下载、安装和使用绘图神器draw.io(drawio),它比visio,processon,wps,亿图图示等更好用,最重要的是它免费
  17. IM即时通讯实现原理
  18. matlab数据行次序反转,如何在Excel表格中将数据顺序翻转-excel怎么把一行数据颠倒...
  19. 计算机的工作原理(冯诺依曼体系)
  20. 大学教编程的老师为何不去当高收入的程序员?

热门文章

  1. model.fit()模型编译时报错:ValueError: Failed to convert a NumPy array to a Tensor (Unsupported object type
  2. 丹枫虽老犹多态 – 继承与多态
  3. 造梦西游online十殿阎罗篇(下)(莫等闲,空悲切)
  4. 808操作系统 文件管理
  5. Jpg转gif动画怎么操作?简单的jpg制作gif方法分享
  6. Mail SQR Expert全方位电子邮件管理专家
  7. 浅聊一下那些营销工具—优惠券
  8. 2022-2028年中国异戊二烯行业市场运营格局及前景战略分析报告
  9. 考研积极自救day3
  10. Altium Designer规则