首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Python的Socket知识6:线程、线程锁、线程池、上下文管理

1、进程、线程

进程(process)是cpu资源分配的最小单位,线程(thread)是cpu调度的最小单位。多线程和多进程的应用目的是为了提高并发。一个应用程序可以包含多个进程,而一个进程又可以包含多个线程。默认一个应用程序是单进程、单线程。

1)什么是进程(process)

进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。进程空间的大小只与处理机的位数有关,一个 16 位长处理机的进程空间大小为 216 ,而 32 位处理机的进程空间大小为 232 。进程至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。比如打开一个QQ算是一个进程。

2)什么是线程(thread)

线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻量级进程,也是CPU 调度的一个基本单位。

3)进程和线程的区别:

进程是资源分配和拥有的单位,拥有独立的地址空间,同一个进程内的线程共享进程的资源,但线程并不拥有资源,只是使用他们

线程是处理器调度的基本单位,但进程不是。每个进程在创建时,至少需要同时为该进程创建一个线程。即进程中至少要有一个或一个以上的线程,否则该进程无法被调度执行。线程是进程内的一个相对独立的可执行的单元。

由于共享资源【包括数据和文件】,所以线程间需要通信和同步机制,且需要时线程可以创建其他线程,但线程间不存在父子关系。

4)python中关于线程和进程的使用经验:

因为IO操作不占用CPU,因此,IO操作可以使用多线程提高并发。

计算性操作,占用CPU,可以使用多进程提高并发。

5)创建线程

threading提供了一个比thread模块更高层的API来提供线程的并发性

创建线程的基本方法有两种:

1)利用threading.Thread创建对象,在它的初始化函数(__init__)中将可调用对象作为参数传入。

案例1:创建一个子线程,但子线程什么时候被执行,需要服从系统的调用。

虽然代码解释到了t.start(),但实际执行时间取决于系统。

importtime

#定义一个主线程

deff1(i):

time.sleep(3)# 需要3秒执行完毕

print(i)

#创建的子线程

importthreading

#写完下面一句,就代表创建线程完毕

#target是需要执行的函数,args是传入函数的参数

t=threading.Thread(target=f1,args=(123,))

#执行子线程

t.start()#执行时间待定

#执行主线程

f1(456)

print('end')

执行结果:

案例2:使用setDaemon(True)控制主线程不等子线程。

setDaemon()方法。当参数为True时,把主线程设置为守护线程,主线程A执行结束了,就不管子线程是否完成,一并和主线程退出,也即主线程不等子线程。此方法必须在start() 方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。

importtime

#定义一个主线程

deff1(i):

time.sleep(3)# 需要3秒执行完毕

print(i)

#创建的子线程

importthreading

#写完下面一句,就代表创建线程完毕

#target是需要执行的函数,args是传入函数的参数

t=threading.Thread(target=f1,args=(123,))

#True表示主线程不等子线程,主线程结束则程序结束。

t.setDaemon(True)

#执行子线程

t.start()#执行时间待定

#执行主线程

f1(456)

print('end')

效果:

案例3:join的参数使用,控制主线程等待子线程多长时间

当一个线程操作需要等待另一个线程执行完毕之后才能继续进行时,使用Join()方法。Join方法会等到使用该方法的线程结束后再执行下面的代码。

importtime

#定义一个主线程

deff1(i):

time.sleep(3)# 需要3秒执行完毕

print(i)

#创建的子线程

importthreading

#写完下面一句,就代表创建线程完毕

#target是需要执行的函数,args是传入函数的参数

t=threading.Thread(target=f1,args=(123,))

#True表示主线程不等子线程,主线程结束则程序结束。

t.setDaemon(True)

#执行子线程

t.start()#执行时间待定

#主线程执行到join时,等待,直到子线程执行完毕,再继续执行join后的程序

t.join(2)#2表示最多等2秒

print('end')

print('end')

效果:

假设join中的秒数大一点,可能就会有子线程执行完毕

t.join(4)#2表示最多等4秒

2)第二种创建方式,自定义类,通过继承Thread类,重写它的run方法

案例4:自定义线程的使用

#自定义f2方法

deff2(arg):

print(arg)

#创建线程

importthreading

classmythread(threading.Thread):#继承threading.Thread方法

def__init__(self,func,args):

self.func=func

self.args=args

# 执行父类的构造方法

super(mythread,self).__init__()

defrun(self):

self.func(self.args)

#实例化线程

obj=mythread(f2,123)

#执行线程

obj.start()

threading.Thread类的使用方法:

在自己的线程类的__init__里调用threading.Thread.__init__(self, name = threadname)

Threadname为线程的名字

run(),通常需要重写,编写代码实现做需要的功能。

getName(),获得线程对象名称

setName(),设置线程对象名称

start(),启动线程

jion([timeout]),等待另一线程结束后再运行。

setDaemon(bool),设置子线程是否随主线程一起结束,必须在start()之前调用。默认为False,参数为True时表示主线程不等子线程结束

isDaemon(),判断线程是否随主线程一起结束。

isAlive(),检查线程是否在运行中。

2、线程锁,解决数据共享

同一时刻允许一个线程执行操作。由于线程调用随机,当多个线程都要去修改某一个共享数据的时候,需要对数据访问进行同步。

假设两个线程对象t1和t2都要对num=0进行增1运算,t1和t2都各对num修改10次,num的最终的结果应该为20。但是由于是多线程访问,有可能出现下面情况:在num=0时,t1取得num=0。系统此时把t1调度为”sleeping”状态,把t2转换为”running”状态,t2页获得num=0。然后t2对得到的值进行加1并赋给num,使得num=1。然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给num。这样,明明t1和t2都完成了1次加1工作,但结果仍然是num=1。

threading.Lock类对象,在run方法里,使用lock.acquire()获得了这个锁。此时,其他的线程就无法再获得该锁了,他们就会阻塞在“if lock.acquire()”这里,直到锁被另一个线程释放:lock.release()。

案例5:使用threading.Lock(),上一把锁。

执行结果如下:

案例6:使用threading.RLock(),上多把锁。

执行效果:

3、信号量(Semaphore):批量放行

互斥锁是同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有三个坑,最多只允许三个人同时上厕所,后面的人只能等里面有人出来才能再进去。

案例7:Semaphore批量放行,同时允许5个人同时修改。

执行效果:

5、事件(Event):全部放行或挡住。

python线程的事件用于主线程控制其他线程的执行。事件提供了三个方法set、wait、clear。事件处理的机制,全局定义了一个“Flag”值为False,那么当程序执行evevt.wait方法时,就会阻塞,如果“Flag”值为True,那么event.wait方法时便不再阻塞。

clear:将Flag设置为False,set:将Flag设置为True

案例8:事件event,全部挡住或放行,按照代码从上到下的执行原则,可以写出其执行顺序。

执行结果:

6、条件:使得线程等待,只有满足某条件时,才释放n个线程。

案例9:条件,直接条用condition。

案例10:自己写条件,使用wait_for获取

效果:

7、线程池。

Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码,但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。

线程池需要实现的功能:

设置容量

取一个少一个

无线程时等待

线程执行完毕,交还线程

案例11:使用队列自定义线程池,线程池中放的线程,但是线程不能重复利用

案例12:自定义线程池,在线程池中放任务,线程池的任务存放是用列表的形式,任务有两种类型,一种是实际的任务,一种是在尾部放几个空的任务,空的任务作用是用于识别任务执行结束。

执行结果:打印0-29

8、上下文管理

contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,而任何yield之后的操作都可以放在finally函数中。

案例13:上下文管理案例,看执行顺序

执行结果:

案例14:利用上下文管理,实现socket自动关闭

执行效果:

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180219G0P1QL00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券