zoukankan      html  css  js  c++  java
  • 多线程--vthread

    vthread中包含两个类:

    vthread.vthread.pool

    vthread.vthread.thread

    其中class pool的原型如下:

    class pool(builtins.object)
            pool(pool_num=None, gqueue=0, join=False, log=True, monitor=True)
    

    class thread的原型如下:

    class thread(builtins.object)
            thread(num=1, join=False, log=True)

    init:

    __init__(self, pool_num=None, gqueue=0, join=False, log=True, monitor=True)
        #     :pool_num  伺服线程数量
        #     :gqueue    全局队列表的index,默认0,建议用数字标识
        #     :join      多线程是否join
        #     :log       print函数的输出时是否加入线程名作前缀

    其他:

    Class methods defined here:
        change_thread_num(num, gqueue=0) from builtins.type
            #通过组名字,用来修改线程数量的函数,默认修改gqueue=0的组
            # 是静态函数,你可以直接用 vthread.self.change_thread_num(3)修改
            # 就是简单的多退少补,用来动态修改伺服线程数量的。
            # 因为原理是向线程队列注入停止标记,线程执行和线程接收停止信号是互斥安全的
            # 也是在设计初对任务执行完整性的一种考虑
        
        close_all() from builtins.type
            # 关闭所有伺服线程
    
        close_by_gqueue(gqueue=0) from builtins.type
            # 通过组名关闭该组所有的伺服线程
            # 默认关闭gqueue=0组的所有伺服线程
    
        main_monitor() from builtins.type
            # 对主线程进行监视的函数
            # 一旦主线程执行完毕就会向所有线程池函数队列尾注入停止标记
            # 使所有的线程在执行完任务后都停止下来
            # 对于命令行使用的 python 脚本尤为重要
            # 因为如果所有线程不停止的话,控制权就不会交还给命令窗口;
            # 在任意被含有该函数的装饰类装饰的情况下,这个是默认被打开的
            # 可以在装饰时通过设置 monitor 参数是否打开,默认以第一个装饰器设置为准
    
        
            show() from builtins.type
                # 简单的打印一下当前的线程池的组数
                # 以及打印每一组线程池的线程数量
    
    
    __dict__
         dictionary for instance variables (if defined)
    __weakref__
        list of weak references to the object (if defined)
    __call__(self, func)
        # 类装饰器入口
    
    atom(func)
        # 对任意函数进行原子包装(加锁)
    
    patch_print()
        # print 补丁函数
        # monkey patch 式的修改
        # 对python内建 print 函数进行加锁
        # 使其在调试阶段能更方便使用
    
    toggle(toggle=False, name='thread')
        # 开关显示方式,目前提供修改的参数有三个:
        # 1. "thread"  # 是否在print时在最左显示线程名字
        # 2. "error"   # 是否显示error信息
    
    unpatch_all(can_be_repatch=False)
        # 去补丁函数
        # :can_be_repatch=False
        #   因为设计是在每次装饰时就会默认patch一次
        #   卸载后不可被重新patch的参数添加就是为了
        #   可以使得在头部执行这个函数后后面的装饰都不会再patch

    example1:多线程

     1 import vthread
     2 import time
     3 # vthread.thread
     4 #========#
     5 # 多线程 #
     6 #========#
     7 # eg.1
     8 #普通的多线程装饰器
     9 @vthread.thread(3) # 只要这一行就能让函数变成开3个线程执行同个函数
    10 def foolfunc(num): #将foolfunc变成动态开启3个线程执行的函数
    11     time.sleep(1)
    12     print(f"foolstring, test1 foolnumb: {num} @",time.time())
    13 #默认参数:join=False;log=True
    14 foolfunc(123) # 加入装饰器后,这个函数就变成了开3个线程执行的函数了

    执行的结果如下所示:

    [   Thread-3  ] foolstring, test1 foolnumb: 123 @ 1585216798.0833788
    [   Thread-2  ] foolstring, test1 foolnumb: 123 @ 1585216798.0833788
    [   Thread-1  ] foolstring, test1 foolnumb: 123 @ 1585216798.0833788

    example2:多线程

     1 import vthread
     2 import time
     3 # eg.2
     4 #为了和pool的使用方法共通(一次函数执行只是一次函数单独执行的效果)
     5 # 为了使函数执行更独立可以用 vthread.thread(1) 来装饰
     6 # 但是为了使用更为简便 这里的 vthread.thread 等同于 vthread.thread(1)
     7 
     8 #通过装饰函数,将foolfunc变成开启新线程执行的函数
     9 @vthread.thread
    10 def foolfunc(num):
    11     time.sleep(1)
    12     print(f"foolstring, test1 foolnumb: {num} @",time.time())
    13 
    14 for i in range(5):
    15     foolfunc(123) # 执行与数量分离,可以使得参数传递更为动态
    16                   # 每次执行都会开启新线程,默认不join。
    17 # 注意:
    18 # 这种本身就用于简单测试的方法不要将带参数和不带参数的thread装饰器混用!
    19 # 可能会造成装饰出现问题。

    执行的结果如下:

    [   Thread-1  ] foolstring, test1 foolnumb: 123 @ 1585216957.6751187
    [   Thread-5  ] foolstring, test1 foolnumb: 123 @ 1585216957.6761105
    [   Thread-4  ] foolstring, test1 foolnumb: 123 @ 1585216957.6761105
    [   Thread-3  ] foolstring, test1 foolnumb: 123 @ 1585216957.6761105
    [   Thread-2  ] foolstring, test1 foolnumb: 123 @ 1585216957.6761105

    example3:线程池

     1 import vthread
     2 import time
     3 # vthread.pool
     4 #========#
     5 # 线程池 #
     6 #========#
     7 # 线程池的多线程装饰,对代码入侵较小
     8 
     9 @vthread.pool(3) # 只用加这一行就能实现3条线程池的包装
    10 def foolfunc(num):
    11     time.sleep(1)
    12     print(f"foolstring, test2 foolnumb: {num} @",time.time())
    13 # 默认参数:pool_num=None,join=False,log=True,gqueue=0
    14 # pool_num不选时就自动选 cpu 核心数
    15 # 就是说,装饰方法还可以更简化为 @vthread.pool()
    16 # join参数不建议在主线程内打开。
    17 
    18 for i in range(5):
    19     foolfunc(i) # 加入装饰器后,这个函数变成往伺服线程队列里塞原函数的函数了
    20 
    21 # 这里的函数执行都是放在伺服线程中执行。
    22 # 如果不指定 gqueue 参数,默认是共用0号队列
    23 # 不指定 gqueue 参数给多个函数装饰的情况下,用的都是一组伺服线程
    24 #可以尝试用gqueue的参数来实现不同函数不同作用域,开启多组伺服线程
    25 
    26 # 不加装饰就是普通的单线程
    27 # 只用加一行就能不破坏原来的结构直接实现线程池操作,能进行参数传递

    执行的结果如下所示:

    [  Thread-1_0 ] foolstring, test2 foolnumb: 1 @ 1585217724.2185104
    [  Thread-2_0 ] foolstring, test2 foolnumb: 2 @ 1585217724.2187068
    [  Thread-3_0 ] foolstring, test2 foolnumb: 0 @ 1585217724.2187068
    [  Thread-1_0 ] foolstring, test2 foolnumb: 3 @ 1585217725.219437
    [  Thread-2_0 ] foolstring, test2 foolnumb: 4 @ 1585217725.2204554

    可以看出,多线程并发控制的实现可以通过vthread.pool线程池实现

    example4:多组线程池

     1 import vthread
     2 import time
     3 # vthread.pool
     4 #==============#
     5 # 多组的线程池 #
     6 #==============#
     7 pool_1 = vthread.pool(2,gqueue=1) # 开2个伺服线程,组名为1
     8 pool_2 = vthread.pool(2,gqueue=2) # 开2个伺服线程,组名为2
     9 
    10 @pool_1
    11 def foolfunc1(num):
    12     time.sleep(1)
    13     print(f"foolstring1, test3 foolnumb1:{num} @",time.time())
    14 
    15 @pool_2 # foolfunc2 和 foolfunc3 用gqueue=2的线程池
    16 def foolfunc2(num):
    17     time.sleep(1)
    18     print(f"foolstring2, test3 foolnumb2:{num} @",time.time())
    19 @pool_2 # foolfunc2 和 foolfunc3 用gqueue=2的线程池
    20 def foolfunc3(num):
    21     time.sleep(1)
    22     print(f"foolstring3, test3 foolnumb3:{num} @",time.time())
    23 
    24 for i in range(3): foolfunc1(i)
    25 for i in range(2): foolfunc2(i)
    26 for i in range(1): foolfunc3(i)
    27 # 额外开启线程池组的话最好不要用gqueue=0
    28 # 因为gqueue=0就是默认参数

    执行的结果如下所示:

    [  Thread-4_2 ] foolstring2, test3 foolnumb2:1 @ 1585218328.066928
    [  Thread-1_1 ] foolstring1, test3 foolnumb1:0 @ 1585218328.066928
    [  Thread-3_2 ] foolstring2, test3 foolnumb2:0 @ 1585218328.066928
    [  Thread-2_1 ] foolstring1, test3 foolnumb1:1 @ 1585218328.066928
    [  Thread-4_2 ] foolstring3, test3 foolnumb3:0 @ 1585218329.0687575
    [  Thread-1_1 ] foolstring1, test3 foolnumb1:2 @ 1585218329.0688848

    example5:加装封装,将某些操作进行原子化

     1 import time
     2 import vthread
     3 # 有时候你需要把某些操作进行原子化
     4 # 可以把你要原子化的操作写成函数,用vthread.atom装饰就行
     5 #==========#
     6 # 加锁封装 #
     7 #==========#
     8 @vthread.thread(2)
     9 def foolfunc_():
    10 
    11     @vthread.atom # 将函数加锁封装
    12     def do_some_fool_thing1():
    13 #        pass # do_something
    14         print("do some fool thing1 @",time.time())
    15     @vthread.atom # 将函数加锁封装
    16     def do_some_fool_thing2():
    17 #        pass # do_something
    18         print("do some fool thing2 @", time.time())
    19 
    20     # 执行时就会实现原子操作
    21     do_some_fool_thing1()
    22     do_some_fool_thing2()
    23 
    24 foolfunc_()

    执行的结果如下所示:

    [   Thread-1  ] do some fool thing1 @ 1585223133.8172073
    [   Thread-1  ] do some fool thing2 @ 1585223133.8172073
    [   Thread-2  ] do some fool thing1 @ 1585223133.8172073
    [   Thread-2  ] do some fool thing2 @ 1585223133.8172073

    原子操作是指独立而不可分割的操作,可以理解为一组不可分割操作集。

    其他说明:

    # 另外:
    # 为了便于调试函数在任意第一次装饰过后会对 print 打猴子补丁
    # 自带的 print 函数变成带锁的函数了,还加了些打印线程名字的操作
    # 可以通过 vthread.toggle 函数对这些或其他一些功能进行关闭
    # 也可以用 vthread.unpatch_all() 直接将 print 还原成系统默认函数
    # 更多详细内容可以 help(vthread)
    
    # 额外细节:
    # 如果想将自己的某些函数进行原子操作的封装可以考虑用 @vthread.atom 装饰那个函数
    # 如果你想用原函数的话,你可以用 vthread.orig_func["foolfunc1"] 获得原函数地址
    # vthread.orig_func 就是一个包装【原函数名字】对应【原函数地址】的字典。
    # 虽然 vthread.atom 可以实现原子操作
    # 这里仍然将 lock 暴露出去,用 vthread.lock 就可以拿到这个唯一的线程锁实体
    
    # 为了不用使用者收尾:
    # 当使用者装饰任意数量的线程池的时候,都会默认只开一个不计入数量的线程MainMonitor
    # 就是监视主线程执行情况,一旦主线程执行完就向线程队列注入相应数量的停止标记
    # 因为该线程池的原理就是让主线程变成派发函数的进程,执行到尾部自然就代表
    # 分配的任务已经分配完了,这时就可以注入停止标记让线程执行完就赶紧结束掉
    # 因为是队列操作不会影响线程效率,只是为了防止在命令行下控制权不交还的情况。
    # 当然在之前设计的时候是可以用人为在所有代码最后执行一次 vthread.pool_close_all() 即可解决。
    # 但是为了更易用,为了不让使用者在代码最后添加这一句话,就设计了这个监控线程
  • 相关阅读:
    如何在Ubuntu 18.04上安装Memcached
    ubuntu安装mysql添加密码
    Django学习---快速搭建搜索引擎(haystack + whoosh + jieba)
    django3.x 使用haystack 报错:ImportError: cannot import name 'six' from 'django.utils'
    spring boot2之Jackson和ObjectMapper
    python之装饰器强制函数上的类型检查
    python之*args和**kwargs的区别
    Python之@property
    python基础语法之and,or,not
    小案例
  • 原文地址:https://www.cnblogs.com/zhiminyu/p/12576537.html
Copyright © 2011-2022 走看看