Python笔记(十一):多线程
(二)和(三)不感兴趣的可以跳过,这里参考了《深入理解计算机系统》第一章和《Python核心编程》第四章
(一) 多线程编程
一个程序包含多个子任务,并且子任务之间相互独立,让这些子任务同时运行就是多线程编程。
(二) 进程
进程是操作系统对一个正在运行的程序的一种抽象(或者说进程指的就是运行中的程序)。无论是在单核还是多核系统中,一个CPU看上去都是在并发执行多个进程,实际上这是通过处理器在进程间的切换来实现的,这种切换称为上下文切换。(下面只讨论一个CPU单处理器的情况)
要运行一个新进程时:操作系统总是 1、保存当前进程的上下文。2、恢复新进程的上下文3、将控制权传递给新进程,然后新进程开始执行。
这里说明下上下文的概念:
操作系统保持跟踪进程运行所需的所有状态信息。这种状态,也就是上下文,包括许多信息,比如PC和寄存器文件的当前值,以及主存的内容。
举个例子(解释上下文的概念):你正在和张三谈话,这时一个电话打过来,可以说你暂停了和张三谈话的进程,然后切入新的进程(打电话),等电话打完后,你和张三从刚才停止的地方继续交流。
这时想想能在刚刚的基础上继续交流的前提是什么,我想应该是你还记得刚刚谈话的内容,刚刚说到了什么地方,然后才能在这个基础上继续交谈下去。(计算机也是这样,一个进程暂停的时候,会记住交谈的内容、谈到了什么地方(计算机记住的这些东西就称为上下文(就是当时进程运行时需要的所有状态信息))。计算机恢复一个进程的时候,就是先恢复进程的上下文(就像你要继续交流就要先想起刚刚的谈话一样),所以进程间的切换称为上下文切换)
(三) 线程
在进程执行的过程中,可能有多个分支或多个步骤,例如执行程序A,可能有三个步骤A1、A2、A3,执行A1、A2、A3的过程就是线程。
例如:用户向服务器发出请求-服务器接收请求-服务器处理请求-服务器返回资源。这时就可以有:
线程1:负责接收用户的请求,放到一个队列中。
线程2:处理请求,并提供输出
线程3:负责返回资源
所以,一个进程实际上是由多个称为线程的执行单元组成的,每个线程都运行在进程的上下文中。
关于进程和线程,可以将进程理解为1个完整的任务,线程就是一个个子任务。
子任务1+子任务2+子任务3…组成了一个进程。
(四) Python中多线程
有2个标准库可以实现多线程,_thread和threading,threading更加先进,有更好的线程支持,推荐使用threading,下面也只对threading进行说明。
threading模块对象
对象 |
说明 |
Thread |
表示一个执行线程的对象 |
Lock |
锁原语对象 |
Semaphore |
为线程间共享的有限资源创建一个计数器,计数器值为0时,线程阻塞 |
BoundedSemaphore |
和Semaphore类似,不过它不允许计数器的值超过初始值 |
Thread类
方法 |
说明 |
|
实例化一个线程对象,这里只说明这3个参数的意思,target指函数名,args和kwargs都指要传给函数的参数(args传元组,kwargs传字典),只要指定一个就行了
|
|
开始执行线程 |
|
定义线程功能的方法(一般在继承threading.Thread的子类中重写该方法) |
|
等待直到线程终止 |
接下来:
第五节: 举个不使用多线程的例子。
第六、七节:说明使用threading.Tread创建多线程的2种方式
第八、九、十:分别说明为什么要做线程同步、线程同步方式(锁示例)、线程同步方式(信号量示例)
第十一:说明队列queue模块(该模块提供线程间通信机制,从而让线程间可以分享数据)
(五) 不使用多线程时的情况(接下来注意不使用多线程和使用多线程执行时间的区别)
- import time2 def loop1(name,t):
- 3 print(name+'开始时间' + time.ctime())
- 4 time.sleep(t)
- 5 print(name+'结束时间' + time.ctime())
- 6 loop1('第一次',2)
- 7 loop1('第二次',5)
接下来对使用Tread创建多线程的2种方式进行说明:
1、创建Tread实例,传函数。(六)
2、继承threading.Thread创建子类,并创建子类的实例。(七)
(六) 创建Tread实例(传函数),然后调用star启动线程
target指函数名,args指要传的参数
- import threading
- import time
- def loop1(name,t):
- print(name+'开始时间' + time.ctime())
- time.sleep(t)
- print(name+'结束时间' + time.ctime())
- #创建新线程
- t = threading.Thread(target=loop1,args=('线程1',2))
- t1 = threading.Thread(target=loop1,args=('线程2',5))
- #启动线程
- t.start()
- t1.start()
(七) 继承threading.Thread创建子类,实例化后调用star启动线程
- import threading
- import time
- class test1(threading.Thread):
- def __init__(self,name,t):
- threading.Thread.__init__(self)
- self.name = name
- self.t = t
- def run(self):
- loop1(self.name,self.t)
- def loop1(name,t):
- print(name+'开始时间' + time.ctime())
- time.sleep(t)
- print(name+'结束时间' + time.ctime())
- #创建新线程
- t = test1('线程1',2)
- t1 = test1('线程2',5)
- #启动线程
- t.start()
- t1.start()
(八)线程同步(为什么需要同步)
- import threading
- import time
- class test1(threading.Thread):
- def __init__(self,name,t):
- threading.Thread.__init__(self)
- self.name = name
- self.t = t
- def run(self):
- print('线程1开始修改列表'+time.ctime())
- #[i for i in range(100)]创建一个[0,1,2...99]的列表
- loop1([i for i in range(100)])
- print('线程1结束修改列表'+time.ctime())
- class test2(threading.Thread):
- def __init__(self,name,t):
- threading.Thread.__init__(self)
- self.name = name
- self.t = t
- def run(self):
- print('线程2打印列表:')
- printList()
- the_list = []
- def loop1(num):
- for i in num:
- the_list.append(i)
- if i ==10:
- time.sleep(1)
- def printList():
- print(the_list)
- #创建新线程
- t = test1('线程1',2)
- t1 = test2('线程2',5)
- #启动线程
- t.start()
- t1.start()
看上面这段代码,线程1调用loop1()函数修改列表the_list,线程2调用pringtList()打印the_list.如果不同步,那么可能出现loop1()函数修改到一半的时候,线程2就把the_list打印出来了,如下图所示。
(九)线程同步(锁示例)
接下来说明2种同步原语,锁和信号量。
通过acquire()获取锁,release()释放锁。
- import threading
- import time
- class test1(threading.Thread):
- def __init__(self,name,t):
- threading.Thread.__init__(self)
- self.name = name
- self.t = t
- def run(self):
- #获取锁,用于线程同步
- threadLock.acquire()
- print('开始修改列表'+time.ctime())
- #[i for i in range(100)]创建一个[0,1,2...99]的列表
- loop1([i for i in range(100)])
- print('结束修改列表'+time.ctime())
- #释放锁,开始下一个线程
- threadLock.release()
- class test2(threading.Thread):
- def __init__(self,name,t):
- threading.Thread.__init__(self)
- self.name = name
- self.t = t
- def run(self):
- # 获取锁,用于线程同步
- threadLock.acquire()
- print('打印列表:')
- printList()
- # 释放锁,开始下一个线程
- threadLock.release()
- threadLock = threading.Lock()
- the_list = []
- def loop1(num):
- for i in num:
- the_list.append(i)
- if i ==10:
- time.sleep(1)
- def printList():
- print(the_list)
- #创建新线程
- t = test1('线程1',2)
- t1 = test2('线程2',5)
- #启动线程
- t.start()
- t1.start()
用锁同步就不会存在上面的情况了,输出如下图所示:
也可以使用with,将上面test1的代码修改成下面的,执行with里面的代码时,会先调用acquire(),执行结束后,调用release()自动释放锁。
- def run(self):
- with threadLock:
- print('开始修改列表' + time.ctime())
- # [i for i in range(100)]创建一个[0,1,2...99]的列表
- loop1([i for i in range(100)])
- print('结束修改列表' + time.ctime())
(十)线程同步(信号量示例)
对操作系统PV操作有了解的,应该很容易理解。不知道也没关系,举个例子,有2台打印机,有4个线程要调用这2台打印机进行打印。
1、有2台打印机,这时可用资源 =2,代码中设置一个计数器(值为2)
2、线程1 、线程2分别调用不同的打印机进行打印(占用资源2,计数器值=2-2),此时线程3和4因为没有资源,处于阻塞状态。
3、线程1打印完后,释放资源(计数器值=0+1)
4、此时线程3或线程4,调用打印机进行打印(计数器值=1-1)
5、。。。等所有线程打印完后,计数器值就恢复初始值(2),表示2台打印机都处于空闲状态。
简单的说,这种方式就是
1、设置一个计数器,指定初始值
2、线程开始的时候调用acquire() (占用资源,计数器的值-1)
3、线程结束的时候调用release() (释放资源,计数器值+1)
(计数器的值为0时,线程是处于阻塞状态的)
可以在IDE上运行下面的代码,看下输出。
- import threading
- import time
- #添加计数器(设置打印机数量为2)
- #BoundedSemaphore还有一个作用:计数器的值永远不会超过初始值
- printerNum = threading.BoundedSemaphore(2)
- class test1(threading.Thread):
- def __init__(self, name, t):
- threading.Thread.__init__(self)
- self.name = name
- self.t = t
- def run(self):
- # 计数器获取锁,计数器值-1
- printerNum.acquire()
- print(self.name+':开始执行' + time.ctime())
- loop1(self.t)
- print(self.name+':结束执行' + time.ctime())
- # 计数器释放锁,计数器值+1
- printerNum.release()
- def loop1(t):
- time.sleep(t)
- threadName = ['线程1','线程2','线程3','线程4']
- threadList = []
- #创建新线程
- for tname in threadName:
- thread = test1(tname,2)
- if tname == '线程2':
- thread = test1(tname, 5)
- #将线程添加到列表
- threadList.append(thread)
- #启动线程
- for t in threadList:
- t.start()
- #等待所有线程完成
- for t in threadList:
- t.join()
- print('线程执行完成')
(十一)队列queue
三种类型:FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
方法 |
说明 |
qsize() |
返回队列大小 |
empty() |
如果队列为空,返回True,反之False |
full() |
如果队列满了,返回True,反之False |
put(item, block=True,timeout=None) |
将item放进队列中。block和timeout的作用和下面的get一样,不过这边的判断条件是队列有空间 |
put_nowait(item) |
和put(item,False)相同 |
get(block=True,timeout=None) |
block和timeout使用默认值:队列中没有元素时,阻塞到有可用元素为止 block:设置为Fasle,表示没元素时不等待,报Empty异常。 timeout:设置一个超时时间,超时队列还没元素,报Empty异常 (block为Fasle时,timeout是不生效的) |
get_nowait() |
和get(False)相同 |
task_done() |
表示队列中某个元素已经执行完毕,该方法会被下面的join()调用 |
join() |
所有元素执行完毕并调用上面的task_done()信号之前,保持阻塞 |
- import threading
- import time
- import queue
- from random import randint
- #从队列取数据
- class test1(threading.Thread):
- def __init__(self, name, workQueue):
- threading.Thread.__init__(self)
- self.name = name
- self.workQueue = workQueue
- def run(self):
- loop1(self.name,self.workQueue)
- #往队列写数据
- class test2(threading.Thread):
- def __init__(self, name, nameList):
- threading.Thread.__init__(self)
- self.name = name
- self.nameList = nameList
- def run(self):
- loop2(self.name,self.nameList)
- def loop1(name,workQueue):
- #从队列获取数据
- data = workQueue.get()
- print(name + "从队列获取数据:" + data + time.ctime())
- time.sleep(randint(2, 5))
- def loop2(name,nameList):
- #将数据存入队列
- for n in nameList:
- workQueue.put(n)
- print(name + "将数据存入队列:" + n+time.ctime())
- time.sleep(randint(2, 3))
- threadName = ['线程1','线程2','线程3','线程4']
- nameList = ['手机1','手机2','手机3','手机4','手机5']
- #创建一个先入先出的队列,最大值为10
- workQueue = queue.Queue(10)
- threadList = []
- #创建新线程
- for tname in threadName:
- thread = test1(tname,workQueue)
- if tname == '线程2':
- thread = test2(tname, nameList)
- #将线程添加到列表
- threadList.append(thread)
- #启动线程
- for t in threadList:
- t.start()
- #等待所有线程完成
- for t in threadList:
- t.join()
- print('线程执行完成')
Python笔记(十一):多线程的更多相关文章
- guxh的python笔记十一:异常处理
1,抓错方法 name = [0, 1, 2] try: name[3] except IndexError as exc: # 抓单个错误,打印错误信息e print(exc) except (In ...
- python笔记12-python多线程之事件(Event)
前言 小伙伴a,b,c围着吃火锅,当菜上齐了,请客的主人说:开吃!,于是小伙伴一起动筷子,这种场景如何实现 Event(事件) Event(事件):事件处理的机制:全局定义了一个内置标志Flag,如果 ...
- Python笔记十一(迭代器)
这里我们要学会Iterable和Iterator. 一类是集合数据类型,如list.tuple.dict.set.str等: 一类是generator,包括生成器和带yield的generator f ...
- Python笔记(十一)_匿名函数与map()、filter()
匿名函数 无需显式定义函数名,和函数过程,使代码更精简的lambda表达式 函数没有命名,不用担心函数名的冲突 冒号前面代表函数的参数,后面表示计算过程 >>>func=lambda ...
- Python Web学习笔记之多线程编程
本次给大家介绍Python的多线程编程,标题如下: Python多线程简介 Python多线程之threading模块 Python多线程之Lock线程锁 Python多线程之Python的GIL锁 ...
- python学习笔记(十三): 多线程多进程
一.线程&进程 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程, ...
- python笔记9 : 多线程
基础: 什么是进程(process)? 每一个程序的内存是独立的,例如:world不能访问QQ. 进程:QQ是以一个整体的形式暴露给操作系统管理,里面包含了各种资源的调用(内存管理.网络接口调用等). ...
- 流畅的python笔记
鸭子类型协议不完全总结序列:len,getitem切片:getitemv[0]分量的取值和写值:getitem和setitemv.x属性的取值和写值:getattr和setattr迭代:1)iter, ...
- python3.4学习笔记(十一) 列表、数组实例
python3.4学习笔记(十一) 列表.数组实例 #python列表,数组类型要相同,python不需要指定数据类型,可以把各种类型打包进去#python列表可以包含整数,浮点数,字符串,对象#创建 ...
- Go语言学习笔记十一: 切片(slice)
Go语言学习笔记十一: 切片(slice) 切片这个概念我是从python语言中学到的,当时感觉这个东西真的比较好用.不像java语言写起来就比较繁琐.不过我觉得未来java语法也会支持的. 定义切片 ...
随机推荐
- JAVA_SE基础——6.标识符&关键字
学会写helloworld之后, 我们就开始来认识标识符&关键字 一.标识符 标识符是指可被用来为类.变量或方法等命名的字符序列,换言之,标识符就是用户自定义的名称来标识类.变量或方法等.更 ...
- jquery 实时监听输入框值变化方法
$('.offers-number').bind('input propertychange', function (a, b) { var value = $(this).val() if (!va ...
- java基础总结(1)安装jdk
卸载java java -version yum remove java yum groupjava java 安装java tar -zxvf jdk-8u60-linu ...
- Mac里安装Jmeter
前提是需要安装jdk,参见http://www.cnblogs.com/fun0623/p/4703456.html 1.解压包 (双击apache-jmeter-2.13) 2.进去到解压后的bin ...
- HTML mate标签
META标签分两大部分:HTTP标题信息(http-equiv)和页面描述信息(name). http-equiv http-equiv类似于HTTP的头部协议,它回应给浏览器一些有用的信息,以帮助正 ...
- 我对let和const理解
let和const是es6新出的两种变量声明的方式,接下来我来分别针对这两个,聊一聊. let let它的出现,我认为主要是解决了块级作用域的需求.因为js以前本身是没有什么块级作用域的概念的(顶 ...
- CSS属性操作
CSS属性操作 1 属性选择器 Elenment(元素) E[att] 匹配所有具有att属性的E元素,不考虑它的值.(注意:E在此处可以省略)(推荐使用) 例如:[po]{ font-size: 5 ...
- Java基础语法<五> 大数值BigInteger BigDecimal
笔记整理 来源于<Java核心技术卷 I > <Java编程思想> 如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有平有用的类:BigIn ...
- MySQL高可用架构之MHA 原理与实践
MHA简介 关于MHA MHA(Master HA)是一款开源的MySQL的高可用程序,它为MySQL主从复制架构提供了automating master failover 功能.MHA在监控到mas ...
- 开源纯C#工控网关+组态软件(九)定制Visual Studio
一. 引子 因为最近很忙(lan),很久没发博了.不少朋友对那个右键弹出菜单和连线的功能很感兴趣,因为VS本身是不包含这种功能的. 大家想这是什么鬼,怎么我的设计器没有,其实这是一个微软黑科技 ...