每日一问:python中的线程与进程,用进程来拷贝文件

本文深入探讨了线程与进程的基础概念,详细讲解了线程的创建、管理和同步机制,包括锁、条件锁、信号量和事件等。同时,文章还介绍了进程的管理,包括进程池、数据交换和进程同步,以及如何避免死锁和僵尸进程。

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

线程:

1.线程简介

线程是cpu运行的最小单元。
共享内存之间可以并发执行多任务。
每一个线程都可以共享一个进程的资源。
每个进程中最少包含一个线程,如何调度进程和线程完全由操作系统来决定。
所有变量可以被线程共享。
线程之间共享数据最大的危险在于:如果有多个线程同时启动处理同一个变量,会导致数据紊乱。

2.创建线程

通过模块_thread(偏向底层的低级模块,操作起来比较难)
threading (对_thread模块进行封装 ,高级模块)
在任何一个进程启动的时候会默认启动一个线程,这个就是主线程。主线程中间可以启动子线程。
方式一:创建一个threading.Thread对象,在其初始化函数中将可调用对象作为参数传入:
import threading
def handle(sid):
   print("Thread %d run"%sid)

#创建线程方法1
for i in range(1, 11):
    t = threading.Thread(target=handle, args=(i,))
    t.start()
print("main thread")

方式二:通过继承Thread类,重写它的run()方法
import threading
def handle(sid):
   print("Thread %d run"%sid)
class MyThread(threading.Thread):
    def __init__(self,sid):
        threading.Thread.__init__(self)
        self.sid = sid
    def run(self):
        handle(self.sid)

for i in range(1,11):
    t = MyThread(i)
    t.start()
print("main thread")

3.threading.Thread对象:

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
group :这个应该为空,是个保留位,是为了以后ThreadGroup(线程组)这个类实现而预留的  
target:是个由run()方法执行调用的可调用对象,也就是线程要执行的函数,默认为空,意味着没有东西可以被调用  
name: 这个参数表示线程的名称,默认情况下,是一个类似于Thread-N形式的名字,这里的N是小的十进制失踪  
args: 这是个参数元组,是用来给target调用的,默认为一个空的元组  
kwargs={} : 这是个关键字参数组成的字典,是用来给target调用的,默认为一个空的字典  
daemon 这个bool值表明这个线程是否是守护进程,当为True时,就是个守护进程,否则为False,就不是守护进程  
这个必须在start()之前被设置,否则就会抛出RuntimeError异常,它的初始值继承于创建它的线程,如果主进程不是守护进程,
那么由主进程创建的子进程默认都不是守护进程

一般用到的是 target=函数 name=“名字”,args=(元组,单个元素加逗号) args=(aa,)

4.start方法与run方法

启动线程是调用start方法,而不调用run方法,这里两者有什么区别呢?

首先线程的状态有五种:创建、就绪、运行、阻塞和死亡
当调用start函数,线程的状态就处于就绪状态,处于就绪状态后,还需要等待CPU分配时间片
如果分到时间片,就进入运行状态,执行run函数

start() --> 启动线程,这个函数只能在一个线程中调用一次,如果超过一次则会抛出RuntimeError异常,它安排run()方法在另一个线程中调用

run() --> 用以表示线程活动的方法,你可以在子线程中覆盖此方法
在自己构造线程类时候可以重写run方法,此时调用的是当前线程类的run
而不是父类身上的run

import threading
class MyThread(threading.Thread):
    def __init__(self,sid):
        threading.Thread.__init__(self)
        self.sid = sid
    def run(self):
        currentTreadname = threading.currentThread()
        print("running in", currentTreadname)

t = MyThread(1)
t.start()
t.run()

可以看到调用start方法是在创建的子线程中调用,而直接调用run方法则是在主线程中调用

5.线程方法

daemon 是否为守护线程 默认False 主线程根本不等你子线程
getName 获取线程名字
ident 线程的标识符(线程的id)
isAlive is_alive 是否为活动线程
isDaemon 是否为守护线程
name 线程名字
start 线程运行
join([timeout]) 阻塞主线程),timeout为最长等待时间
run 方法

6.锁 lock

使用公共资源的时候,可以上个锁,其他线程就不能使用了。解决所线程导致数据紊乱的问题。多线程同步时如果需要获得多个锁才能进入临界区的话,可能会发生死锁,在多线程编程时一定要注意并认真检查和避免这种情况。死锁可以理解为嵌套锁。

  • acquire() 获得锁
  • release() 释放锁
死锁
# lock.acquire()      # 锁上了,底下的代码在等待它的释放;将lock 状态调为locked
# lock.acquire()      # lock 阻塞等待 lock 被释放;发生死锁
# lock.release()      # 这里根本释放不了
# lock.release()      # 没有上锁,就不能释放。获取锁和释放锁要一一对应。RuntimeError
Rlock

重复锁,递归锁,可以锁很多次,但是解锁要一一对应
也是局部锁,在一个线程中可以实现重复获取锁,并且不需要释放锁,代码依旧执行(其他线程局部不调用此锁时),如果在多个线程中使用RLock,必须在线程局部获取锁和释放锁一一对应,否则其他线程会出现阻塞。

condition 条件锁
方法:
  • wait():
    wait方法会释放锁,并阻塞当前线程直到超时或其他线程针对同一个Condition对象调用了notify/notify_all方法,被唤醒之后当前线程会重新尝试获取锁并在成功获取锁之后结束wait方法,然后继续执行。
  • wait_for(predicate=fun,timeout=None)方法
    阻塞当前线程,直到超时或者制定条件得到满足
import time
print("主线程开始")
def fun():
    time.sleep(2)
    return True
con.wait_for(fun)       # 阻塞,直到predicate 返回TRUE
print("主线程结束..")
  • notify(n=1)
    唤醒等待该Condition对象的一个或多个线程,该方法并不负责释放锁,n=1唤醒一个锁
  • notify_all
    会唤醒等待该Condition对象的所有线程

线程同步

信号量对象 semaphore(value)
Semaphore ,是一种带计数的线程同步机制,当调用release时,增加计数,当acquire时,减少计数,当计数为0的时候,自动阻塞,等待被释放。

sem = Semaphore(10)     # 班级   信号量锁,指定锁的次数,只能锁到10次,保证只有10个线程在运行,其余的都在等待。

Event对象

Event是线程间通信最简单的机制之一:一个线程发送一个event信号,其他的线程则等待这个信号。用于主线程控制其他线程的执行。Event管理一个flag,这个flag可以使用set()设置成True或者使用clear()重置为False,wait()则用于阻塞,在flag为True之前。Event内置了一个初始化为False的标志(flag)。

		# Event  直行红灯
from threading import Event,Thread
import time

event = Event()
event.clear()       # 将Event 开关设置成False
"""
event.set()        将Event 开关设置成True
event.isSet()       查看开关是否为True
event.wait()        等待
"""
num = 4
class RGB(Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        while True:
           global num, flag
           time.sleep(2)
           num -= 1
           print("倒计时:%s"%num)
           if num == 0:
               event.set()
               time.sleep(2)
               event.clear()
           if num<=0:
               num = 4

class Car(Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        while True:
            if event.isSet():
                time.sleep(2)
                print("%s:直行通过红绿灯"%self.name)
            else:
                event.wait()

t1 = RGB()
t1.start()

for i in range(5):
    t = Car()
    t.start()
  • 方法
    Event.wait(timeout) :堵塞线程,直到Event对象内部标识符被设为True或者超时
    Event.set():将标识符设为Ture
    Event.clear():将标识符设为False
    Event.isSet():判断标识符是否为Ture
栅栏 Barrier

栅栏提供了一个简单的同步原语,用于应对固定数量的线程需要彼此相互等待的情况,线程调用wait()方法后将阻塞,直到所有线程都用了wait()方法,此时所有线程将被同时释放。

class threading.Barrier(parties,action=None,timeout=None)
		parties 栅栏数
action=None 一个函数
timeout 超时数
	方法
		wait()  等待
parties  栅栏打、开所需线程的数量(属性)
n_waiting  当前正在栅栏中阻塞的线程
rest()  重置栅栏        # 避免死锁会用一下的
abort()  使栅栏破损,冲出栅栏所需要的线程数量
broken  一个布尔值,值为True表示栅栏受损
			田径比赛
from threading import Thread,Barrier
import time,random
def fun():
    print("<比赛开始>")

bar = Barrier(10,action=fun)

class Per(Thread):      # 创建人,线程
    def __init__(self):
        super().__init__()
    def run(self):
        time.sleep(1)
        print("%s已经准备好了"%self.name)
        bar.wait()
        time.sleep(random.randint(2,5))
        print("%s跑完了全程"%self.name)

for i in range(10):     # 19  前10个人已经跑完了,后边不够10个人,线程阻塞了
    p = Per()
    p.start()
with语法
Lock 、 RLock 、 Condition 、 Semaphore 和 BoundedSemaphore 对象可以用作 with 语句的上下文管理器。

进程

线程具体占用的时间是多少,优先级是多少,就得看进程了。

多线程适合IO密集型,每一个耗时比较长
跑起来的软件、程序 就是进程。
运算密集型用进程,CPU就是用来做运算的。

概述

进程是正在执行中的应用程序(编完编程就是系统里的一个进程)。一个进程是一个执行中的文件使用资源的总和,包括虚拟地址空间、代码、数据、对象句柄、环境变量和执行单元等。一个应用程序同时打开并执行多次,会创建多个进程。
python 标志库 multiprocessing 支持使用类似于threading 的用法来创建于管理进程,并且避免了GIL(Global Interpreter Lock)问题,可以有效利用CPU。

总结:对于操作系统而言,一个任务就是一个进程,是系统中程序执行和资源分配的基本单元。每个进程都有自己的数据段,堆栈段。
全局变量在多个进程中不能共享,子进程和主进程和兄弟进程操作的不是同一个变量。在操作的时候,每一个进程都会引用一个新的全局变量。或者说,在创建子进程时,自动备份了一个去全局变量。
父进程不能影响结束子进程。
可以用进程池来管理进程。
全局解释锁,解释器的锁。GIL(Global Interpreter Lock)
历史原因:
硬件厂商,CPU的速度技术上解决不了。做了一个多核,开始多核运算。核是处理线程的,4个核就可以运行4个线程。但是核多了,会有弊端,这4个核同时运行的期间,数据是不共享的。要解决数据共享的问题,就得上锁。
硬件厂商已经想好了数据进行共享的问题,而python也想解决这个问题,因此全局解释锁就是使python自己的线程可以进行数据共享。
每一版本python都在解决这个问题。
每一个核里都有GIL,只有一个能获取锁,只是占用了内存,没有起作用。
全局解释锁非常不高效,直接取消的话,之前很多包都不能用了。只能慢慢想对策去改,怎么样改就是开发的问题。
python是一个开源的,没有人维护,全部是靠赞助,效率不高。
一个解释器有一个GIL,开两个解释器就有两个锁。
采用多进程的方法解决

创建进程(两种类似线程)

一种是通过构造函数继承的方式,
一种直接通过mulitprocessing实例化一个进程对象。

僵尸进程

守护进程的时候,主进程不考虑子进程有没有完
子进程不考虑主进程

上下文和启动进程方法

  • spawn
    父进程启动一个新的python解释器进程。子进程将只继承运行流程对象run() 方法所需的资源。特别是,来自父进程的不必要的文件描述符合句柄不会被继承。与使用fork或forkserver相比,使用这种方法启动进程要慢得多。
    可在Unix和Windows上使用,Windows上的默认值
  • fork(复制)
    父进程使用os.fork() 派生python解释器。子进程开始时实际上与父进程相同。父进程的所有资源都由子进程继承。注意,安全地分叉多线程进程是有问题的。
    只能在Unix是上使用,Unix上的默认值。
  • forkserver
    当程序启动并选择forkserver start方法时,将启动一个服务器进程。从那时起,每当需要一个新进程时,父进程就连接到服务器,并请求它派生一个新进程,fork服务器进程是单线程的,因此使用os.fork() 是安全的,没有继承不必要的资源。
  • 创建方法 在windows里只有spawn这种方式

multiprocessing

		multiprocessing.active_children()  返回主进程中所有子进程
		multiprocessing.cpu_count()     返回系统中的CPU数量
		multiprocessing.current_process()     返回系统当前数量
		multiprocessing.get_all_start_methods()   返回支持的start方法列表,第一个是默认方法
		multiprocessing.get_context(method=None)   获得上下文  如果method=None,则返回默认的上下文  method = fork,spawn,forkserver
		multiprocessing.get_start_method()  返回用于启动进程的start方法的名称
	进程对象方法
		run()
		start()  运行的时候运行run文件
		join()
		name
		is_alive()
		daemon
		pid  返回进程ID
		authkey  进程的身份验证密钥  
		exitcode  进程终止码,没有终止则为None
		terminate()  立刻终止进程运行,极易产生僵尸进程:主进程已经结束了,子进程还在运行。
		kill()  使用SIGKILL信号相同
		close()  关闭

数据交换

  • Queue
  • Pipe

进程同步

  • Manager 对象
    控制一个拥有list,dict,Lock,Semaphore,BoundedSemaphore,Queue,Event,Barrier,Value,Array。

进程池

利用pool对象进行数据并行处理
创建进程池
		from multiprocessing import Pool
		p = Pool(num)  num为进程池中进进程的数量
常用方法
  • apply(func[,args,kwds])
    调用func函数,并传递参数args和kwds,同时阻塞当前进程直至函数返回
  • apply_async(func,callback,error_callback)
    返回AsyncResult对象
    get()获得返回值
    wait()等到结果可用
    ready()返回调用是否完成
    succful()返回结果是否在没有引发异常的情况下完成
  • map(func,iterable)
    内置函数map()模块的并行版本,但只能接受一个可迭代对象作为参数,该方法会阻塞当前进程直至结果可用。该方法会把迭代对象iterable切分成多个块再作为独立的任务提交给进程池,块的大小可通过参数chunksize(默认值为1)来设置。
  • close()
    不允许再次向进程池提交任务,当所有已提交的任务完成后工作进程会退出
  • terminate()
    立即结束工作进程,当进程池对象被回收时会自动调用该方法
  • join()
    等待工作进程退出,在此之前必须先调用close()或terminate()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值