㈠ 一篇文章带你深度解析python线程和进程
使用Python中的线程模块,能够同时运行程序的不同部分,并简化设计。如果你已经入门Python,并且想用线程来提升程序运行速度的话,希望这篇教程会对你有所帮助。
线程与进程
什么是进程
进程是系统进行资源分配和调度的一个独立单位 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
什么是线程
CPU调度和分派的基本单位 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
进程与线程的关系图
线程与进程的区别:
进程
现实生活中,有很多的场景中的事情是同时进行的,比如开车的时候 手和脚共同来驾驶 汽车 ,比如唱歌跳舞也是同时进行的,再比如边吃饭边打电话;试想如果我们吃饭的时候有一个领导来电,我们肯定是立刻就接听了。但是如果你吃完饭再接听或者回电话,很可能会被开除。
注意:
多任务的概念
什么叫 多任务 呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒,这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。 其实就是CPU执行速度太快啦!以至于我们感受不到在轮流调度。
并行与并发
并行(Parallelism)
并行:指两个或两个以上事件(或线程)在同一时刻发生,是真正意义上的不同事件或线程在同一时刻,在不同CPU资源呢上(多核),同时执行。
特点
并发(Concurrency)
指一个物理CPU(也可以多个物理CPU) 在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
特点
multiprocess.Process模块
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
语法:Process([group [, target [, name [, args [, kwargs]]]]])
由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)。
注意:1. 必须使用关键字方式来指定参数;2. args指定的为传给target函数的位置参数,是一个元祖形式,必须有逗号。
参数介绍:
group:参数未使用,默认值为None。
target:表示调用对象,即子进程要执行的任务。
args:表示调用的位置参数元祖。
kwargs:表示调用对象的字典。如kwargs = {'name':Jack, 'age':18}。
name:子进程名称。
代码:
除了上面这些开启进程的方法之外,还有一种以继承Process的方式开启进程的方式:
通过上面的研究,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题,我们可以考虑加锁,我们以模拟抢票为例,来看看数据安全的重要性。
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改。加锁牺牲了速度,但是却保证了数据的安全。
因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。
mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性( 后续扩展该内容 )。
线程
Python的threading模块
Python 供了几个用于多线程编程的模块,包括 thread, threading 和 Queue 等。thread 和 threading 模块允许程序员创建和管理线程。thread 模块 供了基本的线程和锁的支持,而 threading 供了更高级别,功能更强的线程管理的功能。Queue 模块允许用户创建一个可以用于多个线程之间 共享数据的队列数据结构。
python创建和执行线程
创建线程代码
1. 创建方法一:
2. 创建方法二:
进程和线程都是实现多任务的一种方式,例如:在同一台计算机上能同时运行多个QQ(进程),一个QQ可以打开多个聊天窗口(线程)。资源共享:进程不能共享资源,而线程共享所在进程的地址空间和其他资源,同时,线程有自己的栈和栈指针。所以在一个进程内的所有线程共享全局变量,但多线程对全局变量的更改会导致变量值得混乱。
代码演示:
得到的结果是:
首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行(其中的JPython就没有GIL)。
那么CPython实现中的GIL又是什么呢?GIL全称Global Interpreter Lock为了避免误导,我们还是来看一下官方给出的解释:
主要意思为:
因此,解释器实际上被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。在多线程环境中,Python 虚拟机按以下方式执行:
由于GIL的存在,Python的多线程不能称之为严格的多线程。因为 多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行。
由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行, 既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?
由于以前的电脑基本都是单核CPU,多线程和单线程几乎看不出差别,可是由于计算机的迅速发展,现在的电脑几乎都是多核CPU了,最少也是两个核心数的,这时差别就出来了:通过之前的案例我们已经知道,即使在多核CPU中,多线程同一时刻也只有一个线程在运行,这样不仅不能利用多核CPU的优势,反而由于每个线程在多个CPU上是交替执行的,导致在不同CPU上切换时造成资源的浪费,反而会更慢。即原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源。
但是在使用多线程抓取网页内容时,遇到IO阻塞时,正在执行的线程会暂时释放GIL锁,这时其它线程会利用这个空隙时间,执行自己的代码,因此多线程抓取比单线程抓取性能要好,所以我们还是要使用多线程的。
GIL对多线程Python程序的影响
程序的性能受到计算密集型(CPU)的程序限制和I/O密集型的程序限制影响,那什么是计算密集型和I/O密集型程序呢?
计算密集型:要进行大量的数值计算,例如进行上亿的数字计算、计算圆周率、对视频进行高清解码等等。这种计算密集型任务虽然也可以用多任务完成,但是花费的主要时间在任务切换的时间,此时CPU执行任务的效率比较低。
IO密集型:涉及到网络请求(time.sleep())、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。
当然为了避免GIL对我们程序产生影响,我们也可以使用,线程锁。
Lock&RLock
常用的资源共享锁机制:有Lock、RLock、Semphore、Condition等,简单给大家分享下Lock和RLock。
Lock
特点就是执行速度慢,但是保证了数据的安全性
RLock
使用锁代码操作不当就会产生死锁的情况。
什么是死锁
死锁:当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。即死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将无法向前推进。
所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。
死锁代码
python线程间通信
如果各个线程之间各干各的,确实不需要通信,这样的代码也十分的简单。但这一般是不可能的,至少线程要和主线程进行通信,不然计算结果等内容无法取回。而实际情况中要复杂的多,多个线程间需要交换数据,才能得到正确的执行结果。
python中Queue是消息队列,提供线程间通信机制,python3中重名为为queue,queue模块块下提供了几个阻塞队列,这些队列主要用于实现线程通信。
在 queue 模块下主要提供了三个类,分别代表三种队列,它们的主要区别就在于进队列、出队列的不同。
简单代码演示
此时代码会阻塞,因为queue中内容已满,此时可以在第四个queue.put('苹果')后面添加timeout,则成为 queue.put('苹果',timeout=1)如果等待1秒钟仍然是满的就会抛出异常,可以捕获异常。
同理如果队列是空的,无法获取到内容默认也会阻塞,如果不阻塞可以使用queue.get_nowait()。
在掌握了 Queue 阻塞队列的特性之后,在下面程序中就可以利用 Queue 来实现线程通信了。
下面演示一个生产者和一个消费者,当然都可以多个
使用queue模块,可在线程间进行通信,并保证了线程安全。
协程
协程,又称微线程,纤程。英文名Coroutine。
协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。
通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。
在实现多任务时,线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。
greenlet与gevent
为了更好使用协程来完成多任务,除了使用原生的yield完成模拟协程的工作,其实python还有的greenlet模块和gevent模块,使实现协程变的更加简单高效。
greenlet虽说实现了协程,但需要我们手工切换,太麻烦了,gevent是比greenlet更强大的并且能够自动切换任务的模块。
其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
模拟耗时操作:
如果有耗时操作也可以换成,gevent中自己实现的模块,这时候就需要打补丁了。
使用协程完成一个简单的二手房信息的爬虫代码吧!
以下文章来源于Python专栏 ,作者宋宋
文章链接:https://mp.weixin.qq.com/s/2r3_ipU3HjdA5VnqSHjUnQ
㈡ 适合初学者的顶级Python书单
Python 新手?或者您已经是一位经验丰富的开发人员,希望提升您的 Python知识?可以看一下教务老师推荐的书单,适合所有级别的Python开发程序员。
如果您是初学者,请参考这两本书。
Python编程快速上手 让繁琐工作自动化 第2版
程序员不需要知道太多数学知识
我听到的关于学习编程的最常见的顾虑,就是人们认为这需要很多数学知识。其实,大多数编程需要的数学知识不外乎基本算术运算。实际上,善于编程与善于解决数独问题没有太大差别。
要解决数独问题,数字1~9必须填入9×9棋盘的每一行、每一列,以及每个3×3的内部方块。系统提供了一些数字来帮助你开始,然后你可以根据这些数字进行推算,从而找到答案。例如,在图 0-1的数独问题中,既然5出现在了第1行和第2行,它就不能在这些行中再次出现。因此,在右上角的3×3方块中,它必定在第3行;由于整个网格的最后一列已有了5,所以在右上角的3×3方块中,5就不能在6的右边。每次解决一行、一列或一个方块,将为剩下的部分提供更多的数字线索。随着你填入一组数字1~9,然后再填写另一组数字,整个网格很快就会被填满。
图0-1 一个新的数独问题(左边)及其答案(右边)。尽管使用了数字,但数独并不需要太多数学知识
数独虽然使用了数字,但兄扮颤并不意味着必须精通数学才能求出答案。编程也是这样。就像解决数独问题一样,编程需要将一个问题分解为单个的、详细的步骤。类似地,在“调试”程序(即寻找和修复错误)时,你会耐心地观察程序在做什么,找出出现错误的原因。像所有技能一样,编写的程序越多,你掌握得就越好。
就本书来说,它不会让你变成一个职业软件开发者,就像学习几节吉他课程不会让你变成一名摇滚明星一样。但如果你是办公室职员、管理者羡败、学术研究者,或其他任何使用计算机来工作缺链或娱乐的人,通过本书,你将学到编程的基本知识,这样就能将下面这些简单的任务自动化。
㈢ java学习主要是学习什么呢
你好,学习java只要掌握好方式和方法,其实学起来并不是非常难。比如你可以自学也可以选择机构学。
java是目前主流的开发语言,程序员不论是大数据、云计算、web前端、后端开发等都需要从java学起,如果你想计入IT高薪行列,建议学java!
java学的内容主要有:
①JAVA编程基础(基础语法、面向对象、和谐特性等)
②WEB应用开发(静态网页制作、Oracle数据库、Java Web开发技术、Linux技术、网站性能与安全、软件工程开发流程、Java Web和谐等)
③企业级框架开发(数据结构与算法、SSH框架、JavaEE和谐等)
④项目实训
互联网行业目前还是最热门的行业之一,学习IT技能之后足够优秀是有机会进入腾讯、阿里、网易等互联网大厂高薪就业的,发展前景非常好,普通人也可以学习。
想要系统学习,你可以考察对比一下开设有相关专业的热门学校,好的学校拥有根据当下企业需求自主研发课程的能力,能够在校期间取得大专或本科学历,中博软件学院、南京课工场、南京北大青鸟等开设相关专业的学校都是不错的,建议实地考察对比一下。
祝你学有所成,望采纳。
㈣ 计算机课操作系统的作用是
用一个简单的案例分析我们我可以推断,操作系统的意义不是去教会学生怎样去编写他们自己的操作系统。第一,这里有些学生对操作系统有兴趣并能编写一个操作系统。他们不需要看课堂上的资料就能够很好地编写自己的操作系统。第二,我们也有一些学生没能力实现一个新的的操作系统或者对实现新的操作系统没兴趣的。他们同样不需要课堂上的资料。
那么,意义何在呢?
并发
编写并发代码并不容易,特别是使用线程共享内存和线程锁。然而,现在很多学习计算机科学的学生都会在他们以后的职业生涯的某些时候使用到(并发)。在OS课程以外的课程里学习并发问题已经成为了一种增长的趋势,但即便如此,操作系统是学生首次了解线程,竞争,死锁等等重要概念的传统课程。教材很难(实际上很简单的,但运用起来很难),在毕业前多看几次是很有帮助的。一个可靠的并发编程介绍对学习操作系统课程是有很大好处的。
资源管理
硬件层次上的资源通常是专用的。操作系统提供了这些资源的种类,它们可以是虚拟的(每个用户都有种错觉,自己拥有资源的一份备份)或者是仲裁的(一次只能有一个用户占有资源,但由操作系统来安排访问顺序)。允许多用户访问专用物理资源是一个很基本的策略并被运用到很多用户级别的程序中。通过详细地学习这些内容,学生学会了能够在许多其他场合重用的模式。
性能分析和冲突解决
正如“为什么#*$是我的机器分页?”。当资源被分享时,冲突通常也会随之而来。冲突问题可以使用多种方式来解决,比如使用队列,合理共享,或者使用优先级。在某些情况下,比如CPU调度,没有单一技术解决方案并且最后的解决方案是一些古怪的混合技术。有时,最令人感兴趣的是找出导致问题出现的主要原因是哪一类冲突。我花费了夏天的一大部分时间去找出所有Windows NT导致MP3跳过的原因。操作系统课是学习这些理论的完美课程,它的适用性比计算机科学更广泛。
隐藏复杂性的接口
一个具有良好设计的接口是一个美妙的东西。它更美妙的地方体现在把一个讨厌的低层次接口(调制解调器或者NE2000卡)转换为一个实用高效的高层次抽象接口(套接字流)。学生应该已经在教材里关于抽象数据类型那部分接触过这些想法了,给定的例子一般都是比较普通的,并且抽象化和隐藏复杂性的作用在那个层次里不够明显。我认为把像套接字(socket),文件系统和地址空间这些集合合到一个单一便利的包里可能是计算机科学10大贡献之一了。这是司空见惯的事,以至于很容易让人忽视它的迷人之处。
没什么神奇的
从用户模式(user mode)看,很容易发现OS是一个神奇的东西–它提供了流畅的多任务处理,高效擦储存管理等。–不好的–它会出现蓝屏,系统颠簸,安全问题和调度异常。对于一般用户来说,这个模式是好的。但在另一方面,如果你想去证明你是一个计算机科学家,你需要知道这些问题的幕后细节。你将会从那里发现什么?很多时候,这看起来都是一些令人忧愁的集合,比如单调的链表,狡猾的启发式资源和维护不当的设备驱动程序。好的OS课程应该教会学生这些:
在内核的代码很优秀,你只需要知道到哪里找到它们。当你第一次看到它们时,你不一定会理解它们。但你理解了它们,你就会学得更多。
通常,内核代码都是很普通的代码。任何人都可以编写它,对比于用户模式代码(user-mode code),内核代码(kernel code)仅需要多一点对细节的关心和注意,因为内核代码中的bug造成的结果更严重。
处理大型软件
这是毫无疑问的,陷入别人的几百万行代码库中去是一个噩梦。错误零散的文档,残旧和广泛的接口,糟糕的交互,和费解的错误信息。不过,欢迎回到现实世界,我们不能因为这些糟糕的问题就经常重新开始。作为一个学生,如果你能够开始制定一个系统的方法去学习你需要用代码修复的大软件的相关部分,那么你以后的生活就会轻松很多。你可以讨厌Linux内核但它比你以后的职业生涯会遇到的软件好多了。
计算机系统设计
设计任何的工程系统,包括软件系统,都是一个权衡的过程。是要侧重于可靠性?性能?消耗还是维护性?因为操作系统是很庞大的,性能至关重要的程序,它一般都要维护几十年,所以它们是学生学习这类权衡的很好的地方。拥有一双发现合适设计点的锐利眼睛的学生在工业上是很需要的。这些人更像一个艺术家而不是一个科学家,你需要看大量的代码,理解这些问题,和学会自己独立考虑这些问题。
总结
我已经尝试去说清楚,一门OS课程不仅仅是关于操作系统和给UNIX/Windows/MacOS的使用者提供知识。优秀的OS课程教会你对广泛使用的操作系统的思考技巧和方式,即使你从没接触过一行的内核代码。实际上,在我的大学里获取学位的CS学生不要求一定要上OS课程,但我觉得,所有真正的计算机科学家要么是已经学这么课,要么已经用其他方式学会了这方面的技巧和直接
㈤ Pthread 并发编程(三)——深入理解线程取消机制
本文深入剖析了Pthread并发编程中的线程取消机制,这是一种用于终止线程执行的功能,仅在共享内存的多线程环境中有效。下面通过实例来呈现其工作原理。
在示例程序中,主线程调用pthread_cancel取消运行中的线程,如函数task,结果显示出线程在打印"step1"后被中断,证明了线程被成功取消。
深入分析指出,当线程被正常取消后,pthread_join用来获取线程退出状态,若返回值为PTHREAD_CANCELED,证明了取消机制的正确性。我们还研究了pthread_cancel的函数签名和可能的返回值,例如ESRCH的错误情况。
线程取消机制的执行涉及到线程的状态和取消类型,分为两种状态和两种类型。当线程设置为不接受取消请求时,取消无效。通过pthread_setcancelstate函数可以控制线程的取消状态。
clean-up handlers在线程被取消时执行,如通过pthread_cleanup_push添加清理函数。函数func中的clean-up handler演示了这一过程。线程退出时,pthread_exit和pthread_cancel都会按照特定顺序执行clean-up handlers。
此外,文章还涉及了线程私有数据的概念,通过pthread_key_create和pthread_setspecific来管理线程独有的数据,这些数据在线程结束时会被正确析构,释放内存。
总结来说,线程取消机制的核心流程包括发送取消请求、响应取消、执行清理操作和析构线程私有数据。理解这些细节有助于在实际编程中更有效地控制和管理线程行为。后续文章将探讨更多并发编程主题,如线程调度和同步。
想要获取更多深入内容,可访问项目github.com/Chang-LeHung...,或关注公众号“一无是处的研究僧”,探索更多计算机技术知识。