进程和线程

来源:互联网 发布:playclub捏脸数据论坛 编辑:程序博客网 时间:2024/05/17 00:18

概念

  1. 进程:程序运行的状态就是进程。进程包括三个部分:程序(一堆代码)、数据集(程序在执行过程中的所有数据的集合)、进程控制块PCB(寄存器保存状态,OS利用它来管理进程)
  2. 线程:在现代操作系统中,进程相当于一个容器,线程是其中的执行单位。线程具有三种状态:运行态,阻塞态,就绪态。
  3. 二者的关系:
    1. 一个线程只能属于一个进程,一个进程至少有一个线程。
    2. 进程都是最小的资源管理单元,线程是最小的执行单元:即操作系统分配资源给进程,同一进程的所有线程共享进程的资源;操作系统分配线程给CPU执行。例如:运行一个.py文件就是一个进程(只不过只有一个线程)。这个文件是由python解释器执行的,所以在任务管理器中看到的是python.exe (通过sleep或input阻塞让其保持运行才能查看)
  4. 串行、并行、并发:
    1. 串行:执行完一个线程,再执行下一个。串行并不意味着效率低:纯计算的任务,串行执行并没有效率问题;如果是IO密集型任务,串行执行效率极低。
    2. 并行:同时执行多个进程/线程,需要多核CPU,由系统调配。
    3. 并发:同一时间段内,多个线程在同一CPU上切换运行。比如:比如,如果有2个线程,两个cpu, 那么就是并行。如果有4个线程,两个cpu, 那么就是并行加并发。
    4. 并发/并行的问题:开进程和线程有资源开销,不能无限制开启。解决方案:进程池/线程池,将进程或线程控制在一定数量内(计算机可承受的范围),但是池的大小需要根据任务规模的增大而调高,如果任务数过多,池的效率也有问题。
  5. 进程/线程的切换原则:由OS控制的
    1. 时间片
    2. 遇到I/O操作:比如sleep, input, socket.recv, socket.accept
    3. 优先级切换
  6. 任务的调用方式:同步和异步:
    1. 同步:一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么该进程将会一直等下去,直到收到返回信息才继续执行下去。
    2. 异步:异步常和回调函数捆绑在一起。主进程提交完任务后,不需要等待子进程返回结果,而是继续执行下面的操作,一旦子进程返回了结果,系统会通知主进程进行处理(触发回调函数的执行),这样提高执行的效率。

GIL和python中的线程

Python代码的执行由python虚拟机(CPython)来控制。CPython中的GIL全局解释器锁,控制同一时刻同一进程中只能有一个线程被执行。
这里写图片描述
无法实现一个进程内多线程的并行,浪费了多核的优势。
因此同一进程中,Python的多线程是并发的。并发的切换机制,决定了python的多线程适合I/O密集型的程序,而对于计算密集型的程序,反而可能会降低程序性能。举个栗子:
I/O密集型:

import threadingimport timeprint('主线程开始:',time.ctime())def foo(n):    print('>>>>> run foo',n)    time.sleep(3)    print('end foo',n)ts = []for i in range(3):    t= threading.Thread(target=foo, args=(i,)) #实例化线程对象    ts.append(t)  #添加线程列表中for t in ts:    t.start()   # 执行子线程for i in ts:    t.join()    # 主线程等待子线程结束再执行print('主线程结束:',time.ctime())'''执行结果一共是3秒。如果不利用多线程,结果将是9秒。主线程开始: Mon Jul 17 15:53:56 2017>>>>> run foo 0>>>>> run foo 1>>>>> run foo 2end foo 1end foo 0end foo 2主线程结束: Mon Jul 17 15:53:59 2017'''

计算密集型开多线程,python一次只能运行一个线程,由于没有I/O阻塞,时间片到了后,就会切换线程,这样反复切换反而比单线程模式花费了更多时间。

threading模块

产生子线程对象(子线程可以再开子线程)
t = threading.Tread(target=func, args=(元组))
threading.active_count() 当前进程中活动线程的数量(如果一个线程t t.join() 结束后,那么t不计入数量。)
threading.enumerate() 查看正在运行的线程的清单

Thread()对象的函数

t.start() # 运行子线程
t.run() # 定义线程的功能的函数,一般会被子类重写。
t.join(timeout=None) # 主程序挂起,等待子线程结束
t.setDaemon(True) # 设置为守护线程。在threading中,主线程执行完后,会等待子线程结束,然后退出。设置了Daemeon后(相当于线程不重要),主进程执行完后,无论子线程是否结束,全部结束,程序退出。子进程的daemon值默认继承创建该线程的值。一般主线程的daemon值默认是False.

通过派生Thread类来实例化化线程线程

import threadingimport time, randomclass MyThread(threading.Thread):   # 继承Thread类    def __init__(self,name):    # 重用父进程的构造器        super().__init__()        self.name=name  # 这个要写在父类的构造器方法下下面,        # 否则父类的self.name=MyThread-1(会覆盖自己 的self.name属性。    def run(self):  # 重写run方法        '''具体的功能函数'''        print('[%s] start to runing'% self.name)        time.sleep(random.random())        print('[%s] end'% self.name)t = MyThread('test') #实例化线程对象t.start()'''[test] start to runing[test] end'''

重写的类就将函数封装进去了,实例化线程对象obj,通过obj.start()就会调用run方法。
但是大师说了,更推荐用传统的方式,具体原因不明(· - ·):
def func():
pass
t = threading.Thread(target=func, args=(元组)),然后t.start()

原创粉丝点击