进程和线程

本文详细阐述了进程与线程的概念及其区别,介绍了线程的三种状态及串行、并行与并发的区别,并探讨了Python中线程的具体实现及其限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念

  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 threading
import time

print('主线程开始:',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 2
end foo 1
end foo 0
end 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 threading
import time, random

class 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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值