Python线程进程

本文详细解读了Python中的threading模块,包括Thread.join()的作用,守护线程(setDaemon)、GIL的理解,以及进程间通信、进程池和线程池的使用。通过实例演示了如何控制线程执行顺序和理解守护进程行为。
摘要由CSDN通过智能技术生成


主要围绕遇到的python threading包的一些知识点

OMP_NUM_THREADS

Thread.join()

参考------------------->Python thread join()
主要作用就是让线程有优先执行权,即一定要执行完才进行下面指令,类似于同步的概念

import threading
def action(*add):
    for arc in add:
        #调用 getName() 方法获取当前执行该程序的线程名
        print(threading.current_thread().getName() +" "+ arc)
#定义为线程方法传入的参数
my_tuple = ("http://c.biancheng.net/python/",\
            "http://c.biancheng.net/shell/",\
            "http://c.biancheng.net/java/")
#创建线程
thread = threading.Thread(target = action,args =my_tuple)
#启动线程
thread.start()
#指定 thread 线程优先执行完毕
# thread.join()
#主线程执行如下语句
for i in range(5):
    print(threading.current_thread().getName())

此时结果为

Thread-1 http://c.biancheng.net/python/
MainThread
Thread-1 http://c.biancheng.net/shell/
MainThread
Thread-1 http://c.biancheng.net/java/
MainThread
MainThread
MainThread

如果将**thread.join()**注释去掉,则输出为

Thread-1 http://c.biancheng.net/python/
Thread-1 http://c.biancheng.net/shell/
Thread-1 http://c.biancheng.net/java/
MainThread
MainThread
MainThread
MainThread
MainThread

setDaemon

守护进程的概念,正好最近听龙少说linux也有守护进程的概念,就像在命令行shell命令如果是父进程则在命令行中的fork的其他进程为子进程,父进程结束是不是子进程也会结束?
在python setDaemon就有这样的效果
具体参考--------------------->Python为什么要有setDeamon这里面讲的比较深入,在python中表现得最明显的就是下面例子

import time
import threading

def test():
    while True:
        print threading.currentThread()
        time.sleep(1)

if __name__ == '__main__':
    t1 = threading.Thread(target=test)
    t2 = threading.Thread(target=test)
    t1.start()
    t2.start()

输出结果为

^C<Thread(Thread-2, started 123145414086656)>
<Thread(Thread-1, started 123145409880064)>
CCCCCC<Thread(Thread-2, started 123145414086656)> # ctrl-c 多次都无法中断
<Thread(Thread-1, started 123145409880064)>
^C<Thread(Thread-1, started 123145409880064)>
<Thread(Thread-2, started 123145414086656)>
<Thread(Thread-1, started 123145409880064)>
<Thread(Thread-2, started 123145414086656)>
<Thread(Thread-2, started 123145414086656)><Thread(Thread-1, started 123145409880064)>
…(两个线程竞相打印)

这是作者的结果,但是我使用的python3可能有些改动,使用ctrl-c直接退出了,但是这个死循环确实是在一直存在。
如果将t1和t2进程都设置成守护进程,则结果为

python2.7 1.py
<Thread(Thread-1, started daemon 123145439883264)>
<Thread(Thread-2, started daemon 123145444089856)>
(直接退出了)
注意设置守护进程时要在线程启动之前,不然会报错
Traceback (most recent call last):
File “thread_join.py”, line 39, in
t1.setDaemon(True)
File “/home/amax/anaconda3/envs/torch/lib/python3.8/threading.py”, line 1122, in setDaemon
self.daemon = daemonic
File “/home/amax/anaconda3/envs/torch/lib/python3.8/threading.py”, line 1115, in daemon
raise RuntimeError(“cannot set daemon status of active thread”)
RuntimeError: cannot set daemon status of active thread
这里面展示的源码在上面blog👆中有说到,具体可参考

time.sleep

这里面还有一个有意思的函数time.sleep(),它的作用就是挂起,但是和sys.stdout.flush()结合起来就有一些其他神奇效果

sys.stdout.flush()

栗子来自blog------------------>sys.stdout.flush()的作用

import time

for i in range(5):
	print(i)
    time.sleep(1)

结果很简单

0
1
2
3
4
并且是一个一个打印出现来的

当代码改成

import time

for i in range(5):
    print(i, end=' ')
    time.sleep(1)

结果为

0 1 2 3 4
且是等待5s后一次性全部蹦出来的

给人的感觉像是print是以行(即/n)为结束,遇到行结束就打印出来,具体原因在上面👆blog中有说
将代码改为

import time
import sys

for i in range(5):
    print(i, end=' ')
    sys.stdout.flush()
    time.sleep(1)

输出的最终结果与上面无异,但是效果和第一种很像,一个一个打印出来的
拓展:实现一个时钟,不停刷新,但是不换行

    while True:
        from time import strftime
        from datetime import datetime
        # print (strftime("%m/%d/%Y %H:%M:%S"), end="", flush=True)
        now = datetime.now()
        print ("%s/%s/%s %s:%s:%s" % (now.month,now.day,now.year,now.hour,now.minute,now.second),end="", flush=True)
        print("\r", end="", flush=True)
        time.sleep(1)

这里面的技巧主要在\r上,即光标在行首,参考-------------->python \r
将代码的print时间后面再加上一行
time.sleep即可看出端倪。

multiprocessing

由于这个包和threading很相似,所以放在一起
主要栗子来自multiprocessing python2.x

Process

和thread很像

pool

同时启动大量子进程的进程池

import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print( 'Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
    print ('Parent process %s.' % os.getpid())
    p = Pool()
    for i in range(21):
        p.apply_async(long_time_task, args=(i,))
    print ('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print ('All subprocesses done.')

这里是21个进程同时启动,而默认为20,所以21号进程要等前面一个完成后才能执行,其中默认值就是CPU的核,具体解释在blog中

进程通信

也是使用blog中的例子
使用了multiprocessing中的queue

Bilibili lesson

来自b站的学习宝地

进程线程

比喻

  • 车间—进程
  • 工人—线程
  • 进程储存数据
  • 线程一行一行执行
  • python中,默认创建一个进程,进程又会创建一个线程,进程是资源分配单位而进程是真正工作单位
  • 创建进程资源消耗少?----->对!
  • 使用python threading创建线程以及start()之后,主线程会继续往下走不会等待
  • window主进程是spawn,linux为fork,mac默认为spawn也可set为fork(如果是fork就不用写到main里面)

线程

GIL锁

全局解释器锁

  • 把进程中的线程🔒住,一次只执行一次CPU
  • 发挥多CPU核优势------>多进程
  • 计算密集型->多进程
  • IO密集型->多线程(例如网络交换是通过网卡直接发送,与CPU关系不大)

多线程开发

  • 从在创建线程时,顺序为
    • 创建程序主进程
    • 主进程创建主线程执行code
    • 执行到创建线程时,由主进程创建子进程
    • 子进程创建子线程
    • 主线程执行到code底端,等待所有子线程执行完毕
    • end主线程进程
  • 线程常用方法
    • t.start()当前线程准备就绪,等待CPU调度,具体需要CPU来决定(与操作系统有关)
    • t.join()等待线程的任务执行完毕后再向下继续执行
      • Join()的案例,与CPU中断切换有关,可能计算没做完,直接切到其他进程中了,这样会导致数据混乱。例如多人抢票时票的数量
    • serDaemon:主线程不等子线程 为Ture时
    • 为线程命名,for debuging
      • setName
      • Threading.current_thread().getName()当前线程名
    • 自定义线程类(写成threading.Thread的子类),直接将线程需要做的事情写到run方法中,通过self._args调用原线程的args的元组

线程安全

切换CPU时造成的数据安全,通过加锁实现 threading.RLock()

  • 申请锁lock_object.acquire()
  • 释放锁lock_object.release()
  • 也可通过上下文管理即 with lock_object:
  • python给有些数据类型自动上锁,这些数据类型称为线程安全型

线程锁

lock和RLock

  • lock()效率较高,不支持多次锁
  • RLock()支持多次锁
  • 多次锁的情况:协同开发时锁的嵌套

死锁

抢锁或者一个线程拿两把锁时会出现(可以通过Rlock()解决)

线程池

  • pool=ThreadPoolExecutor(pool_size)

  • Pool.submit(func,arg1,arg2…)

  • Pool.shutdown(True)类似于join()

  • future=pool.submit(…)

    通过feature.result()拿到结果pool的结果

    future=add_done_callback(another func)

    任务分工

  • 闭包

单例模式

  • python在创建类对象的时候会先调用__new__方法来创建,然后再调用__init__方法来初始化(赋值)
    • 在类中要写一个instance变量,然后在__new__根据instance进行创建
    • __new__中变量铜鼓cls.xxx调用
  • 每次实例化对象调用的地址都是一样的

进程

进程与进程之间相互隔离,不共享资源(不同APP之间资源不共享)

创建进程

  • fork:拷贝所有资源
    • 但是是拷贝在创建进程时刻的资源(等于两份),创建之后不会同步
    • 支持文件对象/线程锁等传参
  • spawn:只是创建一个python解释器,就是最简单创建一个进程实现一个函数功能
    • 手动传递必备资源,传递资源通过拷贝生成
    • 需要主进程中的数值时要通过args参数传递
    • 不支持文件对象/线程锁传参
  • forkserver:只传run()方法的必备资源,CPU执行进程时其实只是执行run方法
    • 不支持文件对象/线程锁传参

进程模式

关于文件读写
  • 文件读写是会先写到内存中,也即进程中,在进程结束后再刷到硬盘里(也即文件里)
  • .flush()可以将内存中数据直接刷到硬盘中
  • fork会将主进程中的内存数据(也即即将写到文件的数据)拷贝出来
关于锁
  • 子进程中fork主进程中的锁,这把锁会被子进程的主线程申请走(如果在主线程中acquire了)

进程常见功能

  • start()

  • join()

  • setDeamon

  • 进程名

    • name
    • Multiprocessing.current_process().name
    • PID
      • os.getpid()获取进程id
      • os.getppid()获取父进程
      • Threading.enumerate()
  • 自定义进程类

    • 需要自定义run函数,和线程类似
  • 进程个数与CPU个数一致

    • Multiprocessing.cpu_count()

进程间数据的共享

共享shared memory
  • Value(c风code格)来实现数据共享,需要提前定义好变量类型
  • Array,不仅需要提前定义类型还不能动数组大小
  • manager,将字典dict()(key-value类型)和list()等python风格的数据类型集合加入
交换
  • 通过队列queue,queue=multiprocessing.Queue

  • 在这里插入图片描述

    • put
    • get
  • Pipe管道,parent_conn, child_conn=multiprocessing.Pipe

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6WJt6jSu-1641560154041)(/Users/xiaqiqi/Library/Application Support/typora-user-images/image-20220107155323952.png)]

  • 其他方式

    • 数据库
    • 文件
    • redis

进程锁

当多个程序公用资源时需要使用

lock=multiprocessing.Rlock() ###进程锁可以在进程间通过参数传递

使用spawn的进程并发

for i in range(num_process):
  p=multiprocessing.Process(target=task,args=(lock,))
  p.start()
  process_list.append(p)

for proc in process_list:
  proc.join()

进程池

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
pool = ProcessPoolExecutor(n_Process)
pool.shutdown(Ture)##使进程池与主进程进行同步

加入回调函数

fur = pool.submit(task, i)
fur.add_done_callback(done)#与线程最大的区别在于,回调的done函数会通过主进程来完成,可以通过multiprocessing.current_process().pid来验证

进程池与锁

不要用RLock要用Manager

协程

进程、线程在计算机中真实存在,而协程则是二次开发的,等同于微线程

实现代码切换

  • 遇到IO等待的时候可以自动切换,解决IO下载等待问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值