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(),但实际执行时间取决于系统。

import  time
#定义一个主线程
def f1(i):
    time.sleep(3)  # 需要3秒执行完毕
    print(i)
#创建的子线程
import threading
#写完下面一句,就代表创建线程完毕
#target是需要执行的函数,args是传入函数的参数
t=threading.Thread(target=f1,args=(123,))
#执行子线程
t.start()#执行时间待定
#执行主线程
f1(456)
print('end')

执行结果:

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

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

import  time
#定义一个主线程
def f1(i):
    time.sleep(3)  # 需要3秒执行完毕
    print(i)
#创建的子线程
import threading
#写完下面一句,就代表创建线程完毕
#target是需要执行的函数,args是传入函数的参数
t=threading.Thread(target=f1,args=(123,))
#True表示主线程不等子线程,主线程结束则程序结束。
t.setDaemon(True)
#执行子线程
t.start()#执行时间待定
#执行主线程
f1(456)
print('end')

效果:

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

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

import  time
#定义一个主线程
def f1(i):
    time.sleep(3)  # 需要3秒执行完毕
    print(i)
#创建的子线程
import threading
#写完下面一句,就代表创建线程完毕
#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方法
def f2(arg):
    print(arg)
#创建线程
import threading
class mythread(threading.Thread):#继承threading.Thread方法
    def __init__(self,func,args):
        self.func=func
        self.args=args
        # 执行父类的构造方法
        super(mythread,self).__init__()
    def run(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自动关闭

import contextlib
import socket
@contextlib.contextmanager
def context_socket(host,port):
    sk=socket.socket()
    sk.bind((host,port))
    sk.listen(5)
    try:
        yield sk#返回sk给with
    finally:
        sk.close()
#sk返回后,给到sock
with context_socket('127.0.0.1',8888) as sock:
    print('执行其他操作')

执行效果:

案例链接:https://pan.baidu.com/s/1mj2U6MG 密码:lxs2

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值