38线程1-Thread-local-Timer

目录

并发和并行:... 1

parallel,并行:... 1

concurrency,并发:... 2

并发的解决:... 2

1、队列、缓冲区:... 2

2、争抢:... 3

3、预处理:... 3

4、并行:... 3

5、提速:... 3

6、消息中间件:... 3

进程和线程:... 4

线程的状态:... 5

py的线程开发:... 5

threading.Thread类:... 6

线程启动:... 6

线程退出:... 7

线程的传参:... 8

threading的属性和方法:... 8

Thread实例的属性和方法:... 9

t.start()t.run()... 9

线程安全:... 11

daemonnon-daemon... 11

t.join() 14

threading.local类:... 15

threading.Timer类:... 18

并发和并行:

parallel,并行:

同时做某些事,可互不干扰的同一时刻做几件事;

例:

高速公路的车道,所有车辆(数据)可互不干扰的在自己的车道上奔跑(传输);在同一时刻,每条车道上可能同时有车辆在跑,是同时发生的概念;

在一定程度上,并行可解决并发问题;并行并不是解决并发的最佳方案;

concurrency,并发:

也是同时做某些事,但是强调,同一时段做了几件事;

例:

乡村公路一条车道,半幅路面出现了坑,交警指挥车辆走另外半幅路面,一个方向放行3min,换另一个方向,发生了车辆同时要通过路面的事件,这就是并发;

并发是在一时间点有很多事要做,并发太大,如何做?怎么做并不知道;

队列、缓冲区:

乡村公路窄,车辆就会排队,所以排队是一种天然解决并发的办法,车辆排队的缓冲地带就是缓冲区;

并行:

乡村公路窄,拓宽成双向四车道,可同时双向车道通行,通行车辆增多,能满足一定的通行需求;

并发的解决:

食堂打饭模型:

中午12点,开饭,都涌向食堂,这就是并发,如果人很多,就是高并发;

1、队列、缓冲区:

假设只有一个窗口,陆续涌入食堂的,排队打菜是比较好的方式;

排队就是把人排成队列,先进先出,解决了资源使用的问题;

排成的队列,其实就是一个缓冲地带,就是缓冲区

假设女生优先,那么这个窗口就得是两队,只要有女生就先打饭,男生排队等着,女生队伍就是一个优先队列

2、争抢:

只开一个窗口,有可能没有秩序,也就是谁挤进去就给谁打饭;

挤到窗口的人占据窗口,直到打到饭菜离开;

其他人继续争抢,会有一个人正占据窗口,可以视为锁定窗口,窗口就不能为其他人提供了服务了,这是一种锁机制

抢到资源就上锁,排他性锁,其他人只能等候;

争抢也是一种高并发解决方案,但不好,因为有可能有人很长时间抢不到;

3、预处理:

如果排长队的原因,是由于每个人打菜等候时间长,因为要吃的菜没有,需要现做,没打着饭不走开,锁定着窗口;

可以提前统计大多数最爱吃的菜品,最爱吃的80%的热门菜提前做好,保证供应,20%的冷门菜现做;

这样大多数人,就算锁定窗口,也很快就释放窗口了;

一种提前加载用户需要的数据的思路,预处理思想,缓存常用;

4、并行:

成百上千人同时来吃饭,一个队伍搞不定的,多开打饭窗口形成多个队列,如同开多个车道一样,并行打菜;

开窗口就得扩大食堂,得雇人在每一个窗口提供服务,造成成本上升;

日常可通过购买更多服务器,或多开进程实现并行处理,来解决并发问题;

这些都是水平扩展思想;

5、提速:

提高单个窗口的打饭速度;

打饭人员提高工作技能,或为单个窗口配备更多的服务人员,都是提速的办法;

这是一种垂直扩展思想;

6、消息中间件:

北京地铁站外九曲回肠走廊,缓冲人流,进去之后再多口安检进站;

rabbitmqactivemq(apache)rocketmq(ali apache)kafka(apache)等;

以上是最常用的解决方案,一般来说不同的并发场景用不同的策略,而策略可能是多种方式的优化组合;

如,多开食堂(多地),可把食堂建设到宿舍生活区(就近),技术来源于生活;

注:

如果线程在单cpu上处理,就不是并行了;

一般server都是多cpu的,服务的部署往往是多机的、分布式的,这都是并行处理;

真正的好系统,是串行、并行共存的;

串行、并行是解决并发的手段之一,不可厚此薄彼;

串行、并行哪个快?问题本身有问题,类似1车道快还是4车道快;

进程和线程:

在实现了线程的OS中,线程是OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个程序的执行实例就是一个进程;

process,进程:

是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是OS结构的基础;

进程和程序的关系:

程序是源代码编译后的文件,而这些文件存放在disk上,当程序被OS加载到内存中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器;

linux进程有父进程、子进程;

win的进程是平等关系;

thread,线程:

有时被称为light weight processLWP,轻量级进程,线程是程序执行流的最小单元

一个标准的线程由线程ID、当前指令指针PC、寄存器集合、堆栈组成;

processthread理解:

现代OS提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源

进程就是独立的王国,进程间不可以随便的共享数据;

进程是国,线程是省

线程就是省份,同一个进程内的线程可共享进程的资源,每一个线程拥有自己独立的堆栈;

进程是容器,进程内的线程是干活的;

线程是程序执行流的最小单元,基本单元是进程;

py中的进程和线程:

进程会启动一个解释器进程,线程共享一个解释器进程;

线程的状态:

ready,就绪态,线程能够运行,但在等待被调度,可能线程刚刚创建启动,或刚刚从阻塞中恢复,或被其它线程抢占;

running,运行态,线程正在运行;

blocked,阻塞态,线程等待外部事件发生而无法运行,如IO操作;

terminated,终止,线程完成、或退出、或被取消;


py的线程开发:

使用标准库threading

threading.Thread类:

def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)

target,线程调用的对象,就是目标函数;

name,为线程起个名,可重名,区分是靠线程ID

args,为目标函数传递实参,元组;

kwargs,为目标函数传递关键字参数,字典;

py的线程没有优先级,没有线程组的概念,也不能被销毁、停止、挂起,也就没有恢复、中断了;

线程启动:

例:

import threading

import time

def worker():

    for _ in range(5):

        time.sleep(1)

        print('welcome magedu')

    print('thread over')

def worker1():

    for _ in range(5):

        time.sleep(1)

        print('welcome magedu...')

    print('thread over...')

t = threading.Thread(target=worker, name='w1')   #实例化一个线程对象

t.start()   #线程启动,每个线程必须且只能执行该方法1

# print('~'*30)

# t.start()   #RuntimeError: threads can only be started oncet.start()只能一次,如果非要再写一次必须先实例化t=threading.Thread(...)再启动t.start()

t1 = threading.Thread(target=worker1, name='w2')   #对于1cpuserver,多线程是假并行

t1.start()

输出:

welcome magedu...

welcome magedu

welcome magedu...

welcome magedu

welcome magedu...

welcome magedu

welcome magedu...

welcome magedu

welcome magedu...

welcome magedu

thread over

thread over...

线程退出:

py没有提供线程退出的方法,线程在如下情况时退出:

1、线程函数内语句执行完毕;

2、线程函数中抛出未处理的异常;

例:

import threading

import time

def worker2():

    count = 0

    while True:   #通过在循环中加标记,当某一变量为指定值时退出循环

        time.sleep(1)

        print('thread quit')

        count += 1

        if count > 3:

            raise Exception('new exception')

t2 = threading.Thread(target=worker2, name='t2')

t2.start()

输出:

thread quit

thread quit

thread quit

thread quit

Exception in thread t2:

Traceback (most recent call last):

  File "D:\Python\Python35\lib\threading.py", line 914, in _bootstrap_inner

    self.run()

  File "D:\Python\Python35\lib\threading.py", line 862, in run

    self._target(*self._args, **self._kwargs)

  File "E:/git_practice/cmdb/example_threading.py", line 23, in worker2

    raise Exception('new exception')

Exception: new exception

线程的传参:

本质上就是函数传参,线程传参和函数传参没什么区别;

例:

import threading

import time

def worker3(n):

    for _ in range(n):

        print('thread arguments')

# t3 = threading.Thread(target=worker3, name='t3', args=(3,))

t3 = threading.Thread(target=worker3, name='t3', kwargs={'n':3})

t3.start()

输出:

thread arguments

thread arguments

thread arguments

threading的属性和方法:

current_thread(),返回当前线程对象;

main_thread(),返回主线程对象;

active_count(),当前处于alive状态(一旦被cpu调度才是活动状态ready<->running)的线程个数;

enumerate(),返回所有活着的线程的列表,不包括已终止的线程和未开始的线程;

get_ident(),返回当前线程的ID,非0整数;

例:

def worker(n=3):

    print('current_thread: {}'.format(threading.current_thread()))

    print('current_thread().name: {}'.format(threading.current_thread().name))

    print('currrent_thread().is_alive(): {}'.format(threading.current_thread().is_alive()))

    print('main_thread: {}'.format(threading.main_thread()))

    print('main_thread().is_alive(): {}'.format(threading.main_thread().is_alive()))

    print('active_count: {}'.format(threading.active_count()))

    print('enumerate: {}'.format(threading.enumerate()))

    for _ in range(n):

        time.sleep(0.5)

        print('welcome to magedu')

    print('thread over')

print('current_thread again: {}'.format(threading.current_thread()))

print('current_thread().is_alive() again: {}'.format(threading.current_thread().is_alive()))

t = threading.Thread(target=worker,name='w1')

t.start()

# t1 = threading.Thread(target=worker,name='w2')   #多线程无法确定谁前谁后,每个都有机会被执行到

# t1.start()

输出:

current_thread again: <_MainThread(MainThread, started 18324)>

current_thread().is_alive() again: True

current_thread: <Thread(w1, started 14036)>

current_thread().name: w1

currrent_thread().is_alive(): True

main_thread: <_MainThread(MainThread, started 18324)>

main_thread().is_alive(): True

active_count: 2

enumerate: [<Thread(w1, started 14036)>, <_MainThread(MainThread, started 18324)>]

welcome to magedu

welcome to magedu

welcome to magedu

thread over

Thread实例的属性和方法:

t.name,只是个名字,是个标识,可重名,getName()获取,setName()设置;

t.ident,线程ID,是非0整数,线程启动后才会有ID,否则为None;线程退出,此ID依旧可访问,此ID可重复使用;t.name可重复,t.ident必须唯一但可在线程退出后再利用;

t.is_alive(),返回线程是否活着,True|False

t.start()t.run()

t.start()用于多线程场景,一执行到t1.start()则开启一个新的工作线程,threading.current_thread()<MyThread(w1, started 17760)>,主线程1个工作线程多个;

t.run(),运行线程函数,仅是普通函数调用,不会开启新的线程,在当前线程中顺序执行,threading.current_thrad()<_MainThread(MainThread, started 15384)>,先执行t1.run()再执行t2.run()

注:多线程:

一个进程中有多个线程,实现并发;

一个进程至少有一个主线程,其它线程为工作线程;

一个进程至少有一个线程,作为程序入口,这个线程就是主线程;

多线程争用同一个资源(打饭时很多人争一个窗口),解决:加锁;

父线程不管理子线程,父、子只是为了分清谁启动的谁;

父线程消亡,子线程仍可继续运行;

主线程结束时,可杀掉工作线程(子线程);

例:

class MyThread(threading.Thread):

    def run(self):

        print('run')

        super().run()

    def start(self):

        print('start')

        return super().start()

def worker(n=3):

    print(threading.current_thread())

    for _ in range(n):

        time.sleep(1)

        print('welcome to magedu')

    print('thread over')

t1 = MyThread(target=worker,name='w1')

t1.start()

# t1.run()

# t2 = MyThread(target=worker,name='w2')

# t2.start()

# t2.run()

输出:

start

run

<MyThread(w1, started 18324)>

welcome to magedu

welcome to magedu

welcome to magedu

thread over

线程安全:

多线程的执行结果,有时是意想不到的,即线程不安全,在各种高级语言中都必须面对;

如下例在pycharm中和ipython中分别执行,pycharm中整整齐齐,而ipython中不规整,ipython中运行的效果就是线程不安全;

其它语言中有:

线程安全函数;

线程不安全函数(要加锁);

线程安全类;

线程不安全类(加各种锁);

在多线程或特殊场景下应用,能不加锁就不加,否则效率极差;

例:

def worker():

    for _ in range(100):

        print('{} is running'.format(threading.current_thread()))

for _ in range(2):

    threading.Thread(target=worker,name='worker-{}'.format('view')).start()

解决:

def worker():

    for _ in range(100):

        print('{} is running\n'.format(threading.current_thread()),end='')

for _ in range(2):

    threading.Thread(target=worker,name='worker-{}'.format('view')).start()

daemonnon-daemon

linuxdaemon守护进程不一样linux是进程级别,此处是线程;

进程靠线程执行代码,至少有一个主线程,其它线程是工作线程;

主线程是第一个启动的线程;

父线程:如果线程A启动了线程BAB的父线程;

子线程:BA的子线程;

查看Thread类源码:

    def __init__(self, group=None, target=None, name=None,

                 args=(), kwargs=None, *, daemon=None):

注:

daemon,没有主线程之说,都是当前线程;

py构造线程时,可设置daemon属性,这个属性必须在t.start()方法前设置好;

线程有daemon属性(仅py有),可显式设置为TrueFalse;也可不设置取默认值None,如果不设置daemon就取当前线程的daemon来设置它;

主线程是non-daemon线程(铁律),即daemon=False,从主线程创建的所有线程不设置daemon属性,则默认都是daemon=Falsenon-daemon线程;

py程序在没有活着的non-daemon线程运行时退出(主线程kill掉所有活着的daemon线程,主线程退出),也就是剩下的只能是daemon线程,主线程才能退出,否则主线程只能等待(只有在有non-daemon线程,主线程一直等待);

daemon属性,表示线程是否是daemon线程,这个值必须在t.start()前设置,否则引发RuntimeError异常;

isDaemon(),是否是daemon线程;

setDaemon(),设置为daemon线程,必须在t.start()之前设置;

简单来说,本来并没有daemon thread,为简化程序员工作,让他们不用去记录和管理那些后台进程,创建了daemon thread概念,这个概念唯一的作用是,当你把一个线程设为daemon,它会随主线程的退出而退出

daemon线程简化了程序员手动关闭线程的工作;

daemon线程适用场景:

1、后台任务,如:发送心跳包、监控等场景居多;

2、主线程工作才有用的线程,如主线程中维护着公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没意义了,一起退出最合适;

3、随时可被终止的线程;

如果主线程退出,想其它工作线程一起退出,就使用daemon=True来创建工作线程;

如,开启一个线程,定时判断web服务是否正常工作,主线程退出,工作线程也没必要存在了,应随主线程一起退出,这种daemon线程一旦创建,就可忘记它了,只用关心主线程什么时候退出就行;

如果在non-daemon线程(A)中,对另一个daemon线程(B)使用了join方法,这个线程B设置daemon就没意义了,因为Anon-daemon)总是要等待B的;

如果在一个daemon线程C中,对另一个daemon线程D使用了join方法,只能说明C要等待D,主线程退出,CD不管是否结束,也不管它们谁等谁,都要被杀掉;

例:

import threading

import logging

logging.basicConfig(level=logging.INFO)

def worker():

    for _ in range(100):

        msg = '{} is running'.format(threading.current_thread())

        logging.info(msg)   #logging.info(msg)print()的内容不规律的交替出现,原因:多线程,谁前谁后不可预知,要看cpu运行调度情况

        print('inner threading.enumerate(): {}'.format(threading.enumerate()))

threading.Thread(target=worker,name='worker-{}'.format('view')).start()   #工作线程未设置daemon则用主线程的daemon=False(铁律,主线程是non-daemon),主线程一直等待子线程执行完才退出

print('end')

print('main threading.enumerate(): {}'.format(threading.enumerate()))   #返回主线程+其它活着的工作线程

输出:

……

INFO:root:<Thread(worker-view, started 16204)> is running

INFO:root:<Thread(worker-view, started 16204)> is running

end

inner threading.enumerate(): [<_MainThread(MainThread, started 17456)>, <Thread(worker-view, started 16204)>]

main threading.enumerate(): [<_MainThread(MainThread, started 17456)>, <Thread(worker-view, started 16204)>]

……

例:

# threading.Thread(target=worker,name='worker-{}'.format('view')).start()

threading.Thread(target=worker,name='worker-{}'.format('view'),daemon=True).start()   #daemon线程,主线程不会等待,直接杀掉daemon线程退出

print('end')

print('main threading.enumerate(): {}'.format(threading.enumerate()))

例:

logging.basicConfig(level=logging.INFO)

def worker():

    threading.Thread(target=worker1,name='worker1-{}'.format('view')).start()   #取当前线程的daemon,即daemon=True

    for _ in range(100):

        msg = '{} is running'.format(threading.current_thread())

        logging.info(msg)

def worker1():

    for _ in range(100):

        msg = '$$$ {} is running'.format(threading.current_thread())

        logging.info(msg)

# threading.Thread(target=worker,name='worker-{}'.format('view')).start()

threading.Thread(target=worker,name='worker-{}'.format('view'),daemon=True).start()

# threading.Thread(target=worker,name='worker-{}'.format('view'),daemon=False).start()   #一旦该线程执行到了,则主线程一直等待;如果没有执行到(当前试验没有执行不到的情况)主线程直接退出了,如何改?加上time.sleep(0.0005)

# time.sleep(0.0005)

print('ending')

print(threading.enumerate())

t.join()

t.join(timeout=None)

join()是线程的标准方法之一;

一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止;

一个线程可被join多次;

调用谁的join方法,就是join谁,就要等谁;

timeout参数很少用,指定调用者等待多久,没有设置就一直等到被调用线程结束;

如果在non-daemon线程(A)中,对另一个daemon线程(B)使用了join方法,这个线程B设置daemon就没意义了,因为Anon-daemon)总是要等待B的;

如果在一个daemon线程C中,对另一个daemon线程D使用了join方法,只能说明C要等待D,主线程退出,CD不管是否结束,也不管它们谁等谁,都要被杀掉;

例:

def worker():

    for _ in range(50):

        msg = '{} is running'.format(threading.current_thread())

        logging.info(msg)

        t = threading.Thread(target=worker1,name='worker1-{}'.format('view'),daemon=True)

        t.start()

        # t.join()   #如果有此句打印行数为5050

def worker1():

    for _ in range(100):

        msg = '$$$ {} is running'.format(threading.current_thread())

        logging.info(msg)

t = threading.Thread(target=worker,name='worker-{}'.format('view'),daemon=True)

t.start()

t.join()   #可理解为join(t),当前线程是主线程,主线程和t联合起来,主线程要等t执行完才退出

例:

def bar():

    while True:

        time.sleep(1)

        print('bar')

def foo():

    print('t1 is daemon= {}'.format(threading.current_thread().isDaemon()))

    t2 = threading.Thread(target=bar)

    t2.start()

    print('t2 is daemon= {}'.format(t2.isDaemon()))

    t2.join()

t1 = threading.Thread(target=foo,daemon=True)

t1.start()

# t1.join()

time.sleep(2)

print('main thread exiting')

注:

等价于t1t2都为daemon=Falsenon-daemon

threading.local类:

threading.local学习自java

查看threading.local源码:

class local:   #注意local首字母为小写

    __slots__ = '_local__impl', '__dict__'

         ……

    def __getattribute__(self, name):   #对象所有属性均拦截,重新构建字典

class _localimpl:

    def __init__(self):

        # The key used in the Thread objects' attribute dicts.

        # We keep it a string for speed but make it unlikely to clash with

        # a "real" attribute.

        self.key = '_threading_local._localimpl.' + str(id(self))

        # { id(Thread) -> (ref(Thread), thread-local dict) }

        self.dicts = {}

threading.local类构建了一个大字典,即{ id(Thread) -> (ref(Thread), thread-local dict) },其元素的key是线程实例的内存地址,其元素的value是二元组,二元组中的元素为(线程对象引用+每个线程的字典)

使用时:

d[key][0].name

d[key][1][key]

通过threading.local()实例就可在不同线程中,安全的使用线程独有的数据,做到了线程间数据隔离,如同本地变量一样安全;

多线程中,能不共享就不共享,用局部变量,局部变量与threading.local()比,局部变量用得多些;例如,外面传进来的参数,直接赋给本地变量,但对于引用类型来说不行(编程中经常遇到外面传来的是对象,该对象通常是类型);

例:

def worker():

    x = 0

    for _ in range(100):

        time.sleep(0.0001)

        x += 1

    print(threading.current_thread(),x)

for _ in range(5):

    threading.Thread(target=worker).start()   #线程有自己的栈,每个线程压栈的x为函数内局部作用域(局部变量),因此多线程里看到最终x值均为100

输出:

<Thread(Thread-1, started 11216)> 100

<Thread(Thread-5, started 17792)> 100

<Thread(Thread-3, started 15924)> 100

<Thread(Thread-4, started 3248)> 100

<Thread(Thread-2, started 17804)> 100

例:

class A:

    def __init__(self,x):

        self.x = x

a = A(0)   #解决此问题(使用全局变量,还能保持每个线程使用不同的数据)

def worker():

    a.x = 0

    for _ in range(100):

        time.sleep(0.0001)

        a.x += 1

    print(threading.current_thread(),a.x)

    print(a.__dict__)   #每个线程有自己的字典,是隔离的

for _ in range(5):

    threading.Thread(target=worker).start()   #乱,不可预知,使用了全局对象,线程之间互相干扰,导致是错误的结果

输出:

<Thread(Thread-1, started 18216)> 483

<Thread(Thread-3, started 15560)> 493

<Thread(Thread-5, started 17464)> 496

<Thread(Thread-2, started 18268)> 497

<Thread(Thread-4, started 4676)> 498

例:

a = threading.local()

def worker():

    a.x = 0

    for _ in range(100):

        time.sleep(0.0001)

        a.x += 1

    print(threading.current_thread(),a.x)

         print(a.__dict__)   #每个线程有自己的字典,是隔离的

for _ in range(5):

    threading.Thread(target=worker).start()   #结果与使用局部变量效果一样

输出:

<Thread(Thread-4, started 11792)> 100

<Thread(Thread-2, started 12848)> 100

<Thread(Thread-1, started 17764)> 100

<Thread(Thread-3, started 16464)> 100

<Thread(Thread-5, started 16704)> 100

例:

X = 'abc'

ctx = threading.local()

ctx.x = 123

print(ctx,type(ctx),ctx.x)

def worker():

    print(X)

    print(ctx)

    print(ctx.x)

    print('good job')

worker()

print('~'*30)

threading.Thread(target=worker).start()   #ctx.x出错,AttributeError,要在worker中重新定义ctx.x=567

输出:

<_thread._local object at 0x000000000125DA40> <class '_thread._local'> 123

abc

<_thread._local object at 0x000000000125DA40>

123

good job

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

abc

<_thread._local object at 0x000000000125DA40>

Exception in thread Thread-1:

Traceback (most recent call last):

  File "D:\Python\Python35\lib\threading.py", line 914, in _bootstrap_inner

    self.run()

  File "D:\Python\Python35\lib\threading.py", line 862, in run

    self._target(*self._args, **self._kwargs)

  File "E:/git_practice/cmdb/example_threading.py", line 215, in worker

    print(ctx.x)

AttributeError: '_thread._local' object has no attribute 'x'

threading.Timer类:

定时器,延迟执行,定义多久执行一个函数,继承自threading.Thread

查看源码:

    def __init__(self, interval, function, args=None, kwargs=None):

t.start()方法执行后,Timer()对象处于等待状态,等待interval后,开始执行function,实际是延迟执行该线程而不是函数;

non-daemon,继承Thread

TimerThread的子类,就是线程类,具有线程的能力和特征;它的实例是能够延时执行目标函数的线程,在真正执行目标函数之前都可cancel它;

t.cancel()

如果在执行函数之前的等待阶段使用了t.cancel()方法,就会跳过,函数不会执行;

并不是非要在t.start()之后,只要在延迟时间内取消都可以;

查看源码,通过event实现t.cancel()

例:

# def add():

#     print(4 + 5)

#     print(threading.enumerate())

# t = threading.Timer(3,add)

# t.setName('w1')

# t.start()

def add(x,y):

    print(x + y)

    print(threading.enumerate())

t1 = threading.Timer(2,add,args=(4,4))

t1.setName('w2')

t1.start()

# t1.cancel()   #此句可不在t.start()之后,只要在延迟时间内取消都可以;如果线程中add函数已经开始执行了,cancel就没任何效果了

print(threading.enumerate())

time.sleep(3)

t1.cancel()   #此时取消无效,3>2add函数已经执行

输出:

[<_MainThread(MainThread, started 14560)>, <Timer(w2, started 17696)>]

8

[<_MainThread(MainThread, started 14560)>, <Timer(w2, started 17696)>]


猜你喜欢

转载自blog.51cto.com/jowin/2394439