目录

1. 简介

2. Python多进程编程

3. 进程间通信

3.1 Python multiprocessing模块下的Queue类

3.2 Pipe,又称为“管道”

4. 多线程

4.1 创建自定义的线程类

5. 练习

练习1 :将耗时的任务放到线程中以换取更好的用户体验

练习2 :使用多进程对复杂任务进行同时管理


1. 简介

简单了解关于进程和线程的基本原理。如果想更深入的学习可以去慕课app学习电子科技大学蒲晓蓉老师的《计算机操作系统》,会使你从计算机系统的原理掌握一些关于系统的知识。

进程(process),是指计算机中已经运行的程序。进程曾是分时系统的基本运作单位。在早期进程是程序的基本执行实体;而现如今面向线程设计的系统,进程是线程的容器。

线程(thread),是操作系统能够进行运算的调度的最小单位。多数情况下,被包含在进程中,是进程的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程可以并发多个线程,每条线程并行执行不同的任务,线程具有许多传统进程所具有的特征,又被称为轻型进程(Light—Weight Process)或进程元;把进程称为重型进程(Heavy—Weight Process)。

进程和线程的区别。进程是面向操作系统的基本单位,由操作系统资源分配的;而线程是面向任务调度和执行的基本单位。不同的进程间都有独立的代码和数据空间,程序之间切换存在较大的开销,作为轻量级的进程(线程),一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。线程之间是相互影响的,一个线程崩溃整改进程都会死掉,进程崩溃后在保护模式下不会对其他进程产生影响,多进程要比多线程冗余性更高。进程是独立,程序运行的入口、顺序执行序列和程序出口对于每个进程来说都是独立的。但是线程是不能独立执行的,必须依存在应用程序中,有应用程序控制线程执行。

上面说进程是独立的所以进程间要数据共享就需要通过进程通信机制(IPC,Inter-Process Communication),具体的方式包含管道、信号、套接字、共享内存区等。

当然对于线程间的通信要更容易,线程处于同一个进程内,可以共享相同的上下文。在单核的CPU系统中,多线程是不可能的,在某个时刻能够获得CPU资源的线程是唯一的,多个线程通过时间差共享了CPU的资源。使用多线程实现并发编程能较大的提升程序的性能和时间,在操作系统中使用系统自带的监控管理工具查看系统使用的软件的线程数。

Python既支持多进程又支持多线程,因此使用Python实现并发编程主要有3种方式:多进程、多线程、多进程+多线程。

2. Python多进程编程

在UnixOS和LinuxOS中提供了 fork()函数来创建进程,调用fork()函数的是父进程,然后创建出子进程,子进程类似父进程的克隆版,但是子进程仍然拥有自己的PID。fork()函数会返回两次,父进程可以通过fork()函数的返回值得到子进程的PID,而子进程中的返回值永远都是0。Python的os模块提供了fork()函数,但是在windows系统中没有fork()可以调用,因此要实现跨平台的多进程编程,就要使用multiprocessing模块的process类来创建子进程,该模块还提供了更高级的封装,例如批量启动进程的进程池(pool)、用于进程间通信的队列(Queue)和管道(Pipe)等。

例:

from random import randint
from time import time, sleepdef download_task(filename):print('开始下载%s......' % filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成!耗时%s' % (filename, time_to_download))def main():start = time()download_task('test.python')download_task('中国话.zh_cn')end = time()print('花费时间%.2f秒。' % (end - start))if __name__ == '__main__':main()

运行结果截图:

由例子可以看出,程序代码是一点一点去执行的,即便是两个不相干的任务,也需要先等待一个任务完成后才能开始下一个任务,这样效率是很低的。那如果使用多进程的方式会发生什么呢?

from multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleepdef download_task(filename):print('启动下载进程,进程号[%d]' % getpid())print('开始下载%s' % filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成!用时%d' % (filename, time_to_download))def main():start = time()Process1 = Process(target=download_task, args=('test.py', ))Process1.start()Process2 = Process(target=download_task, args=('中国话.zh_cn', ))Process2.start()Process1.join()Process2.join()end = time()print('文件下载完成,累计耗时%.2f秒.' % (end - start))if __name__ == "__main__":main()

运行结果截图:

使用对进程执行任务时,能明显的缩短程序运行的时间通过Process类创建了进程对象,通过target参数传入一个函数来表示进程启动后要执行的代码,后面的args是一个元组,它代表了传递给函数的参数。Process对象的start方法来启动进程,join方法来表示等待进程结束。运行上面的代码可以明显的发现两个下载任务“同时”启动了,程序的执行时间也会大大的缩短。

3. 进程间通信

Python提供了多种实现进程间通信的机制,主要有以下两种:

3.1 Python multiprocessing模块下的Queue类

Queue是构造方法,函数签名是Queue(maxsize=0) ,其中maxsize设置队列的大小。

put(item, block=True, timeout=None): 往队列里放数据。如果满了的话,blocking = False 直接报 Full异常。如果blocking = True,就是等一会,timeout必须为 0 或正数。None为一直等下去,0为不等,正数n为等待n秒还不能存入,报Full异常。

get(item, block=True, timeout=None): 从队列里取数据。如果为空的话,blocking = False 直接报 empty异常。如果blocking = True,就是等一会,timeout必须为 0 或正数。None为一直等下去,0为不等,正数n为等待n秒还不能读取,报empty异常。

简单的介绍直接上代码吧,我感觉这个东西我越看越晕。

from multiprocessing import Process, Queue
from time import sleepdef sub_task(string, q):number = q.get()while number:print(number, string)sleep(0.01)number = q.get()
def main():q = Queue(10)for number in range(1,11):q.put(number)p1 = Process(target=sub_task, args=('ping', q)).start()p2 = Process(target=sub_task, args=('192.168.0.1', q)).start()if __name__ == '__main__':main()

代码运行结果:

3.2 Pipe,又称为“管道”

Pipe直译过来就是“管道”,用它来实现了多进程通信的编程方式,具体的理解方式等同于“管”类似的事物。一根管子管道有两个口,Pipe也常用来实现2个进程之间的通信,在管道的两端分别是两个进程,一端用来传输数据,一端用来接收数据。

使用Pipe实现进程通信,首先需要调用multiprocessing.Pip()函数来创建一个管道,语法格式如下:

conn1, conn2 = multiprocessing.Pipe( [duplex=True] )

conn1 和 conn2 分别用来接收Pipe函数返回的两个端口,duplex参数默认为True,表示该管道是双向的,就是两个端口既可以发数据,亦可以接收数据。如果duplexFalse,表示管道是单向的,conn1 只能接收数据,而 conn2 只能发送数据。conn1conn2 都属于 PipeConnection 对象,它们还可以调用下表所示的这些方法。

方法名 功能
send(obj) 发送一个 obj 给管道的另一端,另一端使用 recv() 方法接收。需要说明的是,该 obj 必须是可序列化的,如果该对象序列化之后超过 32MB,则很可能会引发 ValueError 异常。
recv() 接收另一端通过 send() 方法发送过来的数据。
close() 关闭连接。
poll([timeout]) 返回连接中是否还有数据可以读取。
send_bytes(buffer[, offset[, size]]) 发送字节数据。如果没有指定 offset、size 参数,则默认发送 buffer 字节串的全部数据;如果指定了 offset 和 size 参数,则只发送 buffer 字节串中从 offset 开始、长度为 size 的字节数据。通过该方法发送的数据,应该使用 recv_bytes() 或 recv_bytes_into 方法接收。
recv_bytes([maxlength]) 接收通过 send_bytes() 方法发送的数据,maxlength 指定最多接收的字节数。该方法返回接收到的字节数据。
recv_bytes_into(buffer[, offset]) 功能与 recv_bytes() 方法类似,只是该方法将接收到的数据放在 buffer 中。

代码展示如下,自己研究了很久感觉这些玩意儿不是那么容易,subprocess模块中的类和函数来创建和启动子进程,但是我弄了半天也没成功,我也想装*啊,可是实力不允许。

import multiprocessing
import random
import re
import subprocessdef proc_send(conn, ipaddr):ipInfo = subprocess.Popen(["ping.exe", ipaddr], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True)out = ipInfo.stdout.read().decode('gbk')conn.send(out)print(multiprocessing.current_process().pid,"进程发送数据:",ipaddr)def proc_recv(conn):judge = re.search(r'已接收 = \d', conn.recv())counter = int(judge.group()[6:])if counter > 0:print(multiprocessing.current_process().pid,"接收数据:It's OK")else:print('%d不通' % conn.recv())if __name__ == '__main__':pipe = multiprocessing.Pipe()p1 = multiprocessing.Process(target=proc_send,args=(pipe[0],['192.168.101.1']))p2 = multiprocessing.Process(target=proc_recv,args=(pipe[1],))p1.start()p2.start()p1.join()p2.terminate()

代码执行结果:

4. 多线程

Python早期的版本中使用thread模块(现在命名为_thread)来实现多线程编程。目前使用较多的是threading模块,该模块对多线程编程提供了更好的面对对象的封装。如何进行多线程编程呢?狗!狗!狗!

from random import randint
from threading import Thread
from time import time, sleepdef download_task(filename):print('开始下载%s......' % filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成!用时%d' % (filename, time_to_download))def main():start = time()task1 = Thread(target=download_task, args=('test.tar',))task1.start()task2 = Thread(target=download_task, args=('filename.txt',))task2.start()task1.join()task2.join()end = time()print('累计用时%.2f秒.' % (end - start))if __name__ == '__main__':main()

代码运行结果截图:

4.1 创建自定义的线程类

Python中使用threading模块的Thread类来创建线程,在面向对象一节中本人学习了一个相关概念叫“继承”,可以在已有的类基础上创建新类,因此也可以通过继承Thread类的方式创建自定义的线程类,并通过创建线程对象来启动线程。

from threading import Thread
from random import randint
from time import time, sleepclass DownloadTask(Thread):def __init__(self, filename):super().__init__()self._filename = filenamedef run(self):print('开始下载%s......' % self._filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成!耗费了%d秒' % (self._filename, time_to_download))def main():start = time()Thread1 = DownloadTask('test.txt')Thread1.start()Thread2 = DownloadTask('filename.py')Thread2.start()Thread1.join()Thread2.join()end = time()print('累计用时%.2f' % (end - start))if __name__ == '__main__':main()

代码运行结果截图:

多线程可以共享进程的内存空间,所以在线程间实现通信要简单很多。在多个线程共享一个全局变量的时候,很有可能产生不可控的结果导致程序崩溃。所以在多个线程竞争同一个资源的时候(通尝称之为"临界资源"),对"临界资源"的访问需要加上保护,否则就会出现“混乱”的状态。

例,在例子中启用100个线程同时向一个账户进行转账,演示结果如下:

"""
100个线程同时向一个账户转账一元钱的场景,银行账户作为一个临界资源
"""
from time import sleep
from threading import Threadclass Account(object):def __init__(self):self._balance = 0def deposit(self, money):# 计算余额new_balance = self._balance + moneysleep(0.01)self._balance = new_balance@propertydef balance(self):return self._balanceclass AddMonryThread(Thread):def __init__(self, account, money):super().__init__()self._account = accountself._money = moneydef run(self):self._account.deposit(self._money)def main():account = Account()threads = []for _ in range(100):t = AddMonryThread(account, 1)threads.append(t)t.start()for t in threads:t.join()print('账户余额:¥%d元' % account.balance)if __name__ == '__main__':main()

执行结果:

通过执行代码发现100个线程分别向同一个账户转入一元钱,结果远远没有达到理想的值。出现这样的情况是因为银行账户“临界资源”未受到保护,多个线程同时向账户中存钱时,会同时执行new_balance = self._balance + money,类似于百米赛跑无论多少个人跑,但每个人都是从0跑到100,但是多线程的目的是计算100个人跑了多少。线程在初始账户为0的情况下把1元存进余额因此得到了错误的结果。这里就将出现一个新的概念,也是早闻其名不见其人的“”,通过“”来保护“临界资源”,只有获得“”的线程才能访问“临界资源”,而其他没有得到“”的线程只能被阻塞,直到获得“”的线程释放了“”,其他线程才有机会获取“”,才能访问“临界资源”。简单的理解就是通过“”将本身属于同一时刻的不同跑道上的人让他变得有序,这样就能计算在时间段内每个人跑了多少距离。

from threading import Thread, Lock
from time import sleepclass Account(object):def __init__(self):self._balance = 0self._lock = Lock()def deposit(self, money):# 获取锁后执行代码self._lock.acquire()try:new_balance = self._balance + moneysleep(0.01)self._balance = new_balancefinally:# 在finally释放锁保证锁正常释放self._lock.release()@propertydef balance(self):return self._balance
class AddMoneyThread(Thread):def __init__(self, account, money):super().__init__()self._account = accountself._money = moneydef run(self):self._account.deposit(self._money)def main():account = Account()print("初始金额%d" % account.balance)threads = []for _ in range(100):t = AddMoneyThread(account, 1)threads.append(t)# print(threads)t.start()for t in threads:t.join()print('账户余额为%d' % account.balance)if __name__ == '__main__':main()

5. 练习

练习1 :将耗时的任务放到线程中以换取更好的用户体验

在实例中采用sleep()函数来模拟下载任务花费的时间,在不使用"多线程"的情况下,开始下载后整个程序的其他部分会被这个耗时的任务阻塞而导致异常。

import time
import tkinter
import tkinter.messageboxdef download():# Time delay is used to simulate download timetime.sleep(10)tkinter.messagebox.showinfo(title='提示', message='下载完成')def show_about():tkinter.messagebox.showinfo(title='Author', message='jiejie')def main():tp = tkinter.Tk()tp.title('单线程')tp.geometry('200x200')tp.wm_attributes('-topmost', True)panel = tkinter.Frame(tp)button1 = tkinter.Button(panel, text='下载', command=download)button1.pack(side='left')button2 = tkinter.Button(panel, text='Authon', command=show_about)button2.pack(side='right')panel.pack(side='bottom')tkinter.mainloop()if __name__ == '__main__':main()

使用多线程将耗时的任务放到一个独立的线程里去执行,这样耗时的任务就不会阻塞主线程运行,与上面的代码执行对比会发现,上面的代码执行时会发生卡死,即需要等待下载任务执行完毕才能进行下一步操作;而下面的代码执行时在开始下载任务的同时也能进行其他操作,比如点击Author按钮或者直接关闭弹窗。

import time
import tkinter
import tkinter.messagebox
from threading import Threaddef main():class DownloadTaskHandler(Thread):def run(self):time.sleep(10)tkinter.messagebox.showinfo(title='Tips', message='下载完成咯')# Enable download buttonbutton1.config(state=tkinter.NORMAL)def download():# Disable download buttonbutton1.config(state=tkinter.DISABLED)# The thread is set as a guard thread throught the daemon parameter# When the main thread exits,it is no longer retained.# Using threads to handle time-consuming download tasksDownloadTaskHandler(daemon=True).start()def show_about():tkinter.messagebox.showinfo(title='Author', message='jiejie')tp = tkinter.Tk()tp.title('单线程')tp.geometry('200x200')tp.wm_attributes('-topmost', 1)panel = tkinter.Frame(tp)button1 = tkinter.Button(panel, text='下载', command=download)button1.pack(side='left')button2 = tkinter.Button(panel, text='Author', command=show_about)button2.pack(side='right')panel.pack(side='bottom')tkinter.mainloop()
if __name__ == '__main__':main()

练习2 :使用多进程对复杂任务进行同时管理

实例为求1~100000000的和,该任务为计算密集型任务,常规做法如下:

from time import timedef main():total = 0number_list = [x for x in range(100000000)]start = time()for number in number_list:total += numberprint(total)end = time()print('耗时%.3f秒' % (end - start))if __name__ == '__main__':main()

在上面的代码中,创建一个列表作为容器装入1~100000000个数,然后再进行计算。那如果采用分解式解决方案呢?代码如下:

from multiprocessing import Process, Queue
from random import randint
from time import timedef task_handler(curr_list, result_queue):total = 0for number in curr_list:total += numberresult_queue.put(total)def main():processes = []number_list = [x for x in range(1, 100000001)]result_queue = Queue()index = 0# Start 8 processes to slice the data for calculationfor _ in range(8):p = Process(target=task_handler,args=(number_list[index:index + 12500000], result_queue))index += 12500000processes.append(p)p.start()# Start recording the time spent by all processes after the calculation is completedstart = time()for p in processes:p.join()# Merge resultstotal = 0while not result_queue.empty():total += result_queue.get()print(total)end = time()print('耗时:', (end - start), 's', sep='')if __name__ == '__main__':main()

在执行这两段代码后,比较结果。发现使用多进程能极大的节省计算的时间,多进程获得了更多的计算机资源充分的利用了计算机多核CPU的特性。当然了在实际的执行过程中感觉没有太大的区别,这部分原因让我在查查资料。

学到中年的python学习笔记12--进程和线程相关推荐

  1. Python学习笔记:进程和线程(起)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  2. Python学习笔记:进程和线程(承)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  3. 学到中年的python学习笔记06--面向对象基础编程

    面向对象编程基础 前言 一.类和对象 二.定义类 创建和使用对象 访问可见性 面向对象的三大特性 1.封装 2.继承 3.多态 练习1 定义一个类描述一下数字时钟 练习2 定义一个类描述平面上的点并提 ...

  4. python 学习笔记 12 -- 写一个脚本获取城市天气信息

    近期在玩树莓派,前面写过一篇在树莓派上使用1602液晶显示屏,那么可以显示后最重要的就是显示什么的问题了. 最easy想到的就是显示时间啊,CPU利用率啊.IP地址之类的.那么我认为呢,假设可以显示当 ...

  5. 进程process和线程thread应用和区别——Python学习笔记12

    Subprocess subprocess主要是在Python中执行外部的程序和命令.在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序. subpr ...

  6. python学习笔记——守护进程

    1 基本描述 守护进程:是系统中独立的后台服务进程, 特点:独立与终端并且周期性地执行某个任务,其生命周期长,一般随系统启动和终止. 缺点:进程的创建和销毁的时候需要消耗较多的计算机资源. 2 参考 ...

  7. Python自学笔记之进程和线程

    在使用计算机或者手机的时候可以发现他们都能同时使用多个程序,这里就要涉及到多线程编程,多线程编程能够帮助我们合理的分配并最高效的利用资源. 进程 概念进程(Process)是计算机中的程序关于某数据集 ...

  8. python字典内置方法_柳小白Python学习笔记 12 内置方法之字典方法

    学习字典的时候只学习了最基本的字典定义和创建方式.今天再学习两种字典的创建方法及字典内置方法的使用. 现在春暖花开,所以我用花的元素创建了garden(花园)系列字典,字典的键是flowers(花名) ...

  9. Python 学习笔记12 类 - 使用类和实例

    当我们熟悉和掌握了怎么样创建类和实例以后,我们编程中的大多数工作都讲关注在类的简历和实例对象使用,修改和维护上. 结合实例我们来进一步的学习类和实例的使用: 我们新建一个汽车的类: #-*- codi ...

最新文章

  1. java web 获取根目录_javaweb中获取服务器端跟目录方法总结
  2. iTerm2 for MacOS(终端模拟器/终端仿真器/命令终端工具)设置详解
  3. linux cpu uuid 查看,Linux下查看UUID方法介绍
  4. 微软解释:关于Outlook 2007的争议
  5. Netty入门笔记-BIO编程
  6. 怎么把matlab仿真数据压缩,JPEG图像压缩编码及其MATLAB仿真实现(1)
  7. ubuntu 串口 树莓派_linux系统(ubuntu)烧录安装树莓派及远程连接树莓派
  8. n986原生android,【极光ROM】-【三星NOTE20高通全系列(国行/港版/台版/韩版/美版/日版) N98XX】-【V8.0 Android-R-UDC】...
  9. 《数学分析新讲》_张筑生,12.5节:隐函数定理(1)
  10. wd移动硬盘不能识别_wd移动硬盘xp无法识别 移动硬盘无法识别的解决方法
  11. matlab sym是什么意思,SYM是什么意思 sym是什么意思
  12. centos linux安装网卡驱动,如何在CentOS系统下安装网卡驱动
  13. 搭建网站服务器必须开443端口,记录解决网站443端口不通的问题(启动HTTP或者更换域名)...
  14. 左神进阶班-KMP算法
  15. python2.7安装mysqldb_python2.7安装MySQLdb库
  16. Matplotlib-Python-绘制基础饼形图,分裂饼形图,环形饼形图
  17. 总结低代码海报平台编辑器难点
  18. 问题解决之Cannot find module ‘fs/promises‘
  19. 微信扫描二维码跳转手机默认浏览器打开下载app的链接是怎么实现的
  20. ⑰霍兰德EI*如何选选专业?高考志愿填报选专业

热门文章

  1. Python 的 print( )输出函数
  2. android usb调试软件,有没有什么android软件可以直接进行usb调试啊
  3. 全景丨0基础学习VR全景制作,平台篇第19章:热点功能-文本
  4. ossim5.0安装
  5. 微型计算机gl703评测,华硕ROG Strix GL503与GL703游戏本迎来八代酷睿处理器刷新
  6. 华为旗舰机升级鸿蒙OS,鸿蒙OS系统首发机型确认! 华为新旗舰已正式入网: 旧旗舰也能升级...
  7. flutter自定义appbar
  8. 微信小程序学习笔记(1)----学习资料整理
  9. 评估模型准确度(Forecasting: Principles and practice第四章)
  10. LTC4056/TP4056国产替代DP4056锂电池充电保护芯片