从零开始的Python学习Episode 22——多线程
多线程
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程
程序的执行实例称为进程。
每个进程提供执行程序所需的资源。进程具有虚拟地址空间、可执行代码、系统对象的打开句柄、安全上下文、唯一进程标识符、环境变量、优先级类、最小和最大工作集大小以及至少一个执行线程。每个进程都是用一个线程(通常称为主线程)启动的,但是可以从它的任何线程创建额外的线程。
线程和进程的区别
线程共享创建它的进程的地址空间;进程有自己的地址空间。
线程可以直接访问其进程的数据段;进程有自己的父进程数据段副本。
线程可以直接与其进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。
新线程很容易创建;新进程需要父进程的重复。
线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
对主线程的更改(取消、优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。
线程的创建
import threading
def foo(num):
print('running on thread ',num)if __name__=='__main__':
t1 = threading.Thread(target=foo, args=(1,)) # 生成一个线程实例
t1.start() # 启动线程
通过继承的方式创建线程
import threading
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num def run(self): # 定义每个线程要运行的函数,将threading.Thread中的run方法进行了重载
print("running on number:%s" % self.num) if __name__ == '__main__':
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
并发
创建两个线程来同时并发。
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num def run(self): # 定义每个线程要运行的函数
print("running on number:%s" % self.num)
time.sleep(3) if __name__ == '__main__':
begin = time.time()
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
end = time.time()
print(end-begin)
这样输出的是
running on number:1
running on number:20.002995729446411133
期间只用了零点几秒,几乎是同时输出的,这样可以看出它们是同时进行的。要是是串行的就应该是3.XXXXX秒了。
join()方法
在子线程完成运行之前,这个子线程的父线程将一直被阻塞。即一个线程使用join()方法后,必须等该线程结束后才执行join()之后的代码。
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num def run(self): # 定义每个线程要运行的函数
print("running on number:%s" % self.num)
time.sleep(3) if __name__ == '__main__':
begin = time.time()
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print(end-begin)
这样输出的是
running on number:1
running on number:2
3.005915880203247
这样就相当于串行了。
守护线程setDaemon
将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当主线程完成时不需要某个子线程完全运行完就要退出程序,那么就可以将这个子线程设置为守护线程,setDaemon(True)。
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num def run(self): # 定义每个线程要运行的函数
print("running on number:%s" % self.num)
time.sleep(3) if __name__ == '__main__':
begin = time.time()
t1 = MyThread(1)
t2 = MyThread(2)
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
end = time.time()
print(end-begin)
这样当主线程完成了之后就会退出,不会等待子线程。
running on number:1
running on number:20.0020024776458740234
同步锁(Lock)
import time
import threading def addNum():
global num #在每个线程中都获取这个全局变量
# num-=1 temp=num
print('--get num:',num )
time.sleep(0.001)
num =temp-1 #对此公共变量进行-1操作 num = 100 #设定一个共享变量
thread_list = []
for i in range(100):
t = threading.Thread(target=addNum)
t.start()
thread_list.append(t) for t in thread_list: #等待所有线程执行完毕
t.join() print('final num:', num )
但这样最后获得的final num往往不是0,而是其他数,这是因为多个线程在time.sleep()的时候同时拿到了num,所以num是同一个数,而解决方法就是加锁。
死锁与递归锁(RLock)
死锁
import time
import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
class Mythread(threading.Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock1.acquire()
print('%s 拿到一号锁' %self.name)
lock2.acquire()
print('%s 拿到二号锁' % self.name)
time.sleep(2)
lock1.release()
lock2.release()
def f2(self):
lock2.acquire()
print('%s 拿到二号锁' %self.name)
lock1.acquire()
print('%s 拿到一号锁' % self.name)
lock2.release()
lock1.release()
if __name__ == '__main__':
for i in range(5):
t=Mythread()
t.start() #Thread-1 拿到一号锁
Thread-1 拿到二号锁
Thread-1 拿到二号锁Thread-2 拿到一号锁
这里开了5个线程,可是却阻塞住了,原因是在Thread1拿到二号锁,Thread2拿到一号锁时,f2中在等待一号锁,f1在等待二号锁,结果都等不到,所以产生了死锁。
RLock
import time
import threading
lock = threading.RLock()
class Mythread(threading.Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock.acquire()
print('%s 拿到一号锁' %self.name)
lock.acquire()
print('%s 拿到二号锁' % self.name)
time.sleep(2)
lock.release()
lock.release()
def f2(self):
lock.acquire()
print('%s 拿到二号锁' %self.name)
lock.acquire()
print('%s 拿到一号锁' % self.name)
lock.release()
lock.release()
if __name__ == '__main__':
for i in range(5):
t=Mythread()
t.start() '''
Thread-1 拿到一号锁
Thread-1 拿到二号锁
Thread-2 拿到一号锁
Thread-2 拿到二号锁
Thread-3 拿到一号锁
Thread-3 拿到二号锁
Thread-4 拿到一号锁
Thread-4 拿到二号锁
Thread-5 拿到一号锁
Thread-5 拿到二号锁
Thread-1 拿到二号锁
Thread-1 拿到一号锁
Thread-2 拿到二号锁
Thread-2 拿到一号锁
Thread-3 拿到二号锁
Thread-3 拿到一号锁
Thread-4 拿到二号锁
Thread-4 拿到一号锁
Thread-5 拿到二号锁
Thread-5 拿到一号锁
'''
为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
信号量
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
import time
import threading
s = threading.BoundedSemaphore(3)
class Mythread(threading.Thread):
def run(self):
s.acquire()
print(self.name)
time.sleep(2)
s.release()
if __name__ == '__main__':
t=[]
for i in range(10):
t.append(Mythread())
for i in t:
i.start() '''
Thread-1
Thread-2
Thread-3
Thread-4Thread-6Thread-5 Thread-7
Thread-8Thread-9 Thread-10
'''
可以看到几乎是三个三个同时输出的。
条件变量同步
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。
lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传入锁,对象自动创建一个RLock()。
wait():条件不满足时调用,线程会释放锁并进入等待阻塞;
notify():条件创造后调用,通知等待池激活一个线程;
notifyAll():条件创造后调用,通知等待池激活所有线程。
import time
import threading
import random
lock = threading.Condition()
class Producer(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self)
self.name = name
def run(self):
global product
while True:
if lock.acquire():
p = random.randint(1,10)
print('机器%s生产了%d件产品'%(self.name,p))
product+=p
lock.notify()
lock.release()
time.sleep(2) class Consumer(threading.Thread):
def __init__(self,name):
threading.Thread.__init__(self)
self.name = name
def run(self):
global product
while True:
if lock.acquire():
if product>=1:
print('客户%s购买了1件产品'%self.name)
product -=1
lock.notify()
lock.release()
time.sleep(2) if __name__=="__main__":
product = 0
t = []
for i in range(5):
t.append(Producer(i))
t.append((Consumer(1)))
t.append(Consumer(2))
for x in t:
x.start() '''
机器0生产了10件产品
机器1生产了4件产品
机器2生产了1件产品
机器3生产了2件产品
机器4生产了3件产品
客户1购买了1件产品
客户2购买了1件产品
机器0生产了8件产品
机器1生产了7件产品
机器2生产了5件产品
机器3生产了1件产品
机器4生产了4件产品
客户2购买了1件产品
客户1购买了1件产品
机器0生产了8件产品
机器1生产了4件产品
机器2生产了5件产品
机器3生产了1件产品
机器4生产了1件产品
客户1购买了1件产品
客户2购买了1件产品
机器0生产了5件产品
机器1生产了2件产品
'''
wait等待notify的通知,当接到通知后,会重新从if acquire()开始执行
同步条件(Event)
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度; event.clear():恢复event的状态值为False。
import time
import threading
import random
event1=threading.Event()
event2=threading.Event()
event3=threading.Event()
class Producer(threading.Thread):
def run(self):
event1.wait()
print('salesman:这个东西100块。')
print('salesman:你要吗?')
event1.clear()
event2.set()
event3.wait()
print('salesman:好的')
event3.clear()
class Consumer(threading.Thread):
def run(self):
print('customer:这个东西多少钱?')
event1.set()
event2.wait()
print('customer:嗯,帮我包起来')
event2.clear()
event3.set() if __name__=="__main__":
t = []
t.append(Producer())
t.append(Consumer())
for x in t:
x.start() '''
customer:这个东西多少钱?
salesman:这个东西100块。
salesman:你要吗?
customer:嗯,帮我包起来
salesman:好的
'''
队列queue
创建一个“队列”对象
import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。
将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize)
此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作
import threading,queue
from time import sleep
from random import randint
class Production(threading.Thread):
def run(self):
while True:
r=randint(0,100)
q.put(r)
print("生产出来%s号包子"%r)
sleep(1)
class Proces(threading.Thread):
def run(self):
while True:
re=q.get()
print("吃掉%s号包子"%re)
if __name__=="__main__":
q=queue.Queue(10)
threads=[Production(),Production(),Production(),Proces()]
for t in threads:
t.start() '''
生产出来55号包子
生产出来100号包子
生产出来67号包子
吃掉55号包子
吃掉100号包子
吃掉67号包子
生产出来23号包子生产出来97号包子生产出来41号包子吃掉23号包子 吃掉97号包子
吃掉41号包子
吃掉4号包子
生产出来4号包子
生产出来45号包子吃掉45号包子生产出来84号包子
'''
从零开始的Python学习Episode 22——多线程的更多相关文章
- 从零开始的Python学习Episode 23——进程
---恢复内容开始--- 进程 由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程.Python提供了非常好用 ...
- 从零开始的Python学习Episode 20——面向对象(3)
面向对象之封装 封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体. 隐藏 在python中用双下划线开 ...
- 从零开始的Python学习Episode 19——面向对象(2)
面向对象之继承 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称 为基类或超类,新建的类称为派生类或子类. 子类会“”遗传”父类的属性,从而解决代码重用问 ...
- 从零开始的Python学习Episode 17——序列化
序列化 我们把对象(变量)从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语 言中也被称之为serialization,marshalling,flattenin ...
- 从零开始的Python学习Episode 16——模块
一.模块 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相 ...
- 从零开始的Python学习Episode 15——正则表达式
正则表达式 正则表达式(或 RE)是一种小型的.高度专业化的编程语言,(在Python中)它内嵌在Python中,并通过 re 模块实现,所以使用时要导入re模块.正则表达式模式被编译成一系列的字节码 ...
- 从零开始的Python学习Episode 13——常用模块
模块 一.time模块 时间戳(timestamp) :时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量. 元组(struct_time) :struct_time元组共有9 ...
- 从零开始的Python学习Episode 12——迭代器&生成器
生成器 列表生成式 用于快速地生成一个列表 a = [x*x for x in range(1,9)] print(a) #输出[1, 4, 9, 16, 25, 36, 49, 64] 也可以用于生 ...
- 从零开始的Python学习Episode 11——装饰器
装饰器 装饰器是用来处理其他函数的函数,主要作用是在不修改原有函数的情况下添加新的功能,装饰器的返回值也是一个函数对象. 简单的装饰器 import time def show_time(f): de ...
随机推荐
- BZOJ5418:[NOI2018]屠龙勇士(exCRT,exgcd,set)
Description Input Output Sample Input 23 33 5 74 6 107 3 91 9 10003 23 5 64 8 71 1 11 1 Sample Outpu ...
- istio 配置解读
Istio在服务网络中统一提供了许多关键功能: 流量管理:控制服务之间的流量和API调用的流向,使得调用更可靠,并使网络在恶劣情况下更加健壮. 可观察性:了解服务之间的依赖关系,以及它们之间流量的本质 ...
- 封装一个统一返回json结果类JsonResult
import java.io.Serializable; public class JsonResult implements Serializable{ private static final l ...
- mysql root密码忘记重置及相关注意事项
1.使用mysqld_safe --skip-grant-tables跳过授权,进入mysql操作界面或者在配置文件mysqld 添加skip-grant-tables也行,找回后需要删除..恢复原样 ...
- c++getline()、get()等
1.cin 接受一个字符串,遇“空格”.“TAB”.“回车”都结束 2.cin.get() cin.get(字符变量名)可以用来接收字符 只能接收一个字符 cin.get(字符数组名,接收字符数目)用 ...
- Java面向对象之多态(来源于身边的案例)
2019年1月3日 星期四 Java面向对象之多态(来源于身边的案例) 1. 为什么要用多态? 1.1 多态是面向对象的三大特性之一 1.2 多态是基于接口设计的模型 1.3 多态具有横向扩展特性 1 ...
- Modelsim SE自动化仿真——如何将.do文件中自定义的库链接到testbench顶层模块
我们用Modelsim SE进行仿真时,为了方便,一般会编写.do文件来启动当前仿真.关于.do文件的编写,一般网上都有成型的模板,我们只要稍微改几个参数,就可以符合我们的仿真需求了.但是如果仿真时需 ...
- 大数据入门第七天——MapReduce详解(一)入门与简单示例
一.概述 1.map-reduce是什么 Hadoop MapReduce is a software framework for easily writing applications which ...
- 《Java 程序设计》课堂实践项目汇总链接
1.<Java 程序设计>课堂实践项目-命令行参数 2.<Java 程序设计>课堂实践项目-mini dc 3.<Java 程序设计>课堂实践项目-Arrays和S ...
- mysql数据库的左连接,右连接,内链接。
一般所说的左连接,外连接是指左外连接,右外连接.做个简单的测试你看吧.先说左外连接和右外连接:[TEST1@orcl#16-12月-11] SQL>select * from t1; ID NA ...