常用用法

t.is_alive()

Python中线程会在一个单独的系统级别线程中执行(比如一个POSIX线程或者一个Windows线程)

这些线程将由操作系统来全权管理。线程一旦启动,将独立执行直到目标函数返回。可以通过查询

一个线程对象的状态,看它是否还在执行t.is_alive()

t.join()

可以把一个线程加入到当前线程,并等待它终止

Python 解释器在所有线程都终止后才继续执行代码剩余的部分

daemon

对于需要长时间运行的线程或者需要一直运行的后台任务,可以用后台线程(也称为守护线程)

例:

t = Thread(target = func, args(1,), daemon = True)

t.start()

后台线程无法等待,这些线程会在主线程终止时自动销毁

小结:

后台线程无法等待,不过,这些线程会在主线程终止时自动销毁。你无法结束一个线程,无法给它发送信

号,无法调整它的调度,也无法执行其他高级操作。如果需要这些特性,你需要自己添加。比如说,

如果你需要终止线程,那么这个线程必须通过编程在某个特定点轮询来退出

如果线程执行一些像 I/O 这样的阻塞操作,那么通过轮询来终止线程将使得线程之间的协调变得非常棘手。

比如,如果一个线程一直阻塞在一个 I/O 操作上,它就永远无法返回,也就无法检查自己是否已经被结束了。

要正确处理这些问题,需要利用超时循环来小心操作线程。

线程间通信

queue

一个线程向另外一个线程发送数据最安全的方式应该就是queue库中的队列

先看一下使用例子,这里是一个简单的生产者和消费者模型:

1 from queue importQueue2 from threading importThread3 importrandom4 importtime5

6

7 _sentinel =object()8

9

10 defproducer(out_q):11 n = 10

12 whilen:13 time.sleep(1)14 data = random.randint(0, 10)15 out_q.put(data)16 print("生产者生产了数据{0}".format(data))17 n -= 1

18 out_q.put(_sentinel)19

20

21 defconsumer(in_q):22 whileTrue:23 data =in_q.get()24 print("消费者消费了{0}".format(data))25 if data is_sentinel:26 in_q.put(_sentinel)27 break

28

29

30 q =Queue()31 t1 = Thread(target=consumer, args=(q,))32 t2 = Thread(target=producer, args=(q,))33

34 t1.start()35 t2.start()

上述代码中设置了一个特殊值_sentinel用于当获取到这个值的时候终止执行

关于queue的功能有个需要注意的地方:

Queue对象虽然已经包含了必要的锁,主要有q.put和q.get

而q.size(),q.full(),q.empty()等方法不是线程安全的

使用队列进行线程通信是一个单向、不确定的过程。通常情况下,是没有办法知道接收数据的线程是什么时候接收到的数据并开始工作的。但是队列提供了一些基本的特性:q.task_done()和q.join()

如果一个线程需要在另外一个线程处理完特定的数据任务后立即得到通知,可以把要发送的数据和一个Event放到一起使用

关于线程中的Event

线程有一个非常关键的特性:每个线程都是独立运行的,且状态不可预测

如果程序中的其他线程需要通过判断每个线程的状态来确定自己下一步的操作,这时线程同步问题就会比较麻烦。

解决方法:

使用threading库中的Event

Event对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。

在初始化状态下,event对象中的信号标志被设置为假。

如果有线程等待一个event对象,而这个event的标志为假,这个线程将一直被阻塞知道该标志为真。

一个线程如果把event对象的标志设置为真,就会唤醒所有等待这个event对象的线程。

通过一个代码例子理解:

1 from threading importThread, Event2 importtime3

4

5 defcountdown(n, started_evt):6 print("countdown starting")7 #set将event的标识设置为True

8 started_evt.set()9 while n >0:10 print("T-mins", n)11 n -= 1

12 time.sleep(2)13

14 #初始化的started_evt为False

15 started_evt =Event()16 print("Launching countdown")17 t = Thread(target=countdown, args=(10, started_evt,))18 t.start()19 #会一直等待直到event的标志为True的时候

20 started_evt.wait()21 print("countdown is running")

而结果,我们也可以看出当线程执行了set之后,才打印running

实际用event对象最好是单次使用,创建一个event对象,让某个线程等待这个对象,一旦对象被设置为Tru,就应该丢弃它,我们虽然可以通过clear()方法重置event对象,但是这个没法确保安全的清理event对象并对它进行重新的赋值。会发生错过事件,死锁等各种问题。

event对象的一个重要特点是它被设置为True时会唤醒所有等待它的线程,如果唤醒单个线程的最好用Condition或信号量Semaphore

和event功能类似的线程中还有一个Condition

关于线程中的Condition

关于Condition官网的一段话:

A condition variable is always associated with some kind of lock; this can be passed in or one will be created by default. Passing one in is useful when several condition variables must share the same lock. The lock is part of the condition object: you don’t have to track it separately.

Other methods must be called with the associated lock held. The wait() method releases the lock, and then blocks until another thread awakens it by calling notify() or notify_all(). Once awakened, wait() re-acquires the lock and returns. It is also possible to specify a timeout.

但是需要注意的是:notify() and notify_all()这两个方法,不会释放锁,这意味着线程或者被唤醒的线程不会立刻执行wait()

我们可以通过Conditon对象实现一个周期定时器的功能,每当定时器超时的时候,其他线程都可以检测到,代码例子如下:

1 importthreading2 importtime3

4

5 classPeriodicTimer:6 """

7 这里做了一个定时器8 """

9

10 def __init__(self, interval):11 self._interval =interval12 self._flag =013 self._cv =threading.Condition()14

15 defstart(self):16 t = threading.Thread(target=self.run)17 t.daemon =True18 t.start()19

20 defrun(self):21 whileTrue:22 time.sleep(self._interval)23 with self._cv:24 #这个点还是非常有意思的^=

25 self._flag ^= 1

26 self._cv.notify_all()27

28 defwait_for_tick(self):29 with self._cv:30 last_flag =self._flag31

32 while last_flag ==self._flag:33 self._cv.wait()34

35

36 #下面两个分别为两个需要定时执行的任务

37 defcountdown(nticks):38 while nticks >0:39 ptimer.wait_for_tick()40 print('T-minus', nticks)41 nticks -= 1

42

43

44 defcountup(last):45 n =046 while n <47 ptimer.wait_for_tick print n>

50

51

52 ptimer = PeriodicTimer(5)53 ptimer.start()54

55 threading.Thread(target=countdown, args=(10,)).start()56 threading.Thread(target=countup, args=(5,)).start()

关于线程中锁的使用

要在多线程中安全使用可变对象,需要使用threading库中的Lock对象

先看一个关于锁的基本使用:

1 importthreading2

3

4 classSharedCounter:5

6 def __init__(self, initial_value=0):7 self._value =initial_value8 self._value_lock =threading.Lock()9

10

11 def incr(self,delta = 1):12 with self._value_lock:13 self._value +=delta14

15 def decr(self, delta=1):16 with self._value_lock:17 self._value -= delta

Lock对象和with语句块一起使用可以保证互斥执行,这样每次就只有一个线程可以执行with语句包含的代码块。with语句会在这个代码快执行前自动获取锁,在执行结束后自动释放所。

线程的调度本质上是不确定的,因此,在多线程程序中错误的使用锁机制可能会导致随机数据

损坏或者其他异常错误,我们称之为竞争条件

你可能看到有些“老python程序员”

还是通过_value_lock.acquire() 和_value_lock.release(),明显看来

还是with更加方便,不容易出错,毕竟你无法保证那次就忘记释放锁了

为了避免死锁,使用锁机制的程序应该设定每个线程一次只能获取一个锁

threading库中还提供了其他的同步原语:RLock,Semaphore对象。但是这两个使用场景相对来说比较特殊

RLock(可重入锁)可以被同一个线程多次获取,主要用来实现基于检测对象模式的锁定和同步。在使用这种锁的时候,当锁被持有时,只有一个线程可以使用完整的函数或者类中的方法,例子如下:

1 importthreading2

3

4 classSharedCounter:5

6 _lock =threading.RLock()7

8 def __init__(self,initial_value=0):9 self._value =initial_value10

11 def incr(self,delta=1):12

13 with SharedCounter._lock:14 self._value +=delta15

16 def decr(self,delta=1):17

18 with SharedCounter._lock:19 self.incr(-delta)

这个例子中的锁是一个类变量,也就是所有实例共享的类级锁,这样就保证了一次只有一个线程可以调用这个类的方法。与标准锁不同的是已经持有这个锁的方法再调用同样适用这个锁的方法时,无需再次获取锁,例如上面例子中的decr方法。

这种方法的特点是:无论这个类有多少实例都使用一个锁。因此在需要使用大量使用计数器的情况下内存效率更高。

缺点:在程序中使用大量线程并频繁更新计数器时会有竞争用锁的问题。

信号量对象是一个建立在共享计数器基础上的同步原语,如果计数器不为0,with语句讲计数器减1,

线程被允许执行。with语句执行结束后,计数器加1。如果计数器为0,线程将被阻塞,直到其他线程结束并将计数器加1。但是信号量不推荐使用,增加了复杂性,影响程序性能。

所以信号量更适用于哪些需要在线程之间引入信号或者限制的程序。例如限制一段代码的并发量

1 from threading importSemaphore2 importrequests3

4

5 _fetch_url_sema = Semaphore(5)6

7

8 deffetch_url(url):9 with _fetch_url_sema:10 return requests.get(url)

关于防止死锁的加锁机制

在多线程程序中,死锁问题很大一部分是由于多线程同时获取多个锁造成的。

举个例子:一个线程获取一个第一个锁,在获取第二个锁的时候发生阻塞,那么这个线程就可能阻塞其他线程执行,从而导致整个程序假死。

一种解决方法:为程序中每一个锁分配一个唯一的id,然后只允许按照升序规则来使用多个锁。

1 importthreading2 from contextlib importcontextmanager3

4 #存储已经请求锁的信息

5 _local =threading.local()6

7

8 @contextmanager9 def acquire(*locks):10 #把锁通过id进行排序

11 locks = sorted(locks, key=lambdax: id(x))12

13 acquired = getattr(_local, 'acquired', [])14

15 if acquired and max(id(lock) for lock in acquired) >=id(locks[0]):16 raise RuntimeError("Lock order Violation")17 acquired.extend(locks)18 _local.acquired =acquired19

20 try:21 for lock inlocks:22 lock.acquire()23 yield

24 finally:25 for lock inreversed(locks):26 lock.release()27 del acquired[-len(locks):]28

29

30 x_lock =threading.Lock()31 y_lock =threading.Lock()32

33

34 defthread_1():35 whileTrue:36 with acquire(x_lock,y_lock):37 print("Thread-1")38

39

40 defthread_2():41 whileTrue:42 with acquire(y_lock,x_lock):43 print("Thread-2")44

45

46 t1 = threading.Thread(target=thread_1)47 t1.daemon =True48 t1.start()49

50 t2 = threading.Thread(target=thread_2)51 t2.daemon =True52 t2.start()

通过排序,不管以什么样的顺序来请求锁,这些锁都会按照固定的顺序被获取。

这里也用了thread.local()来保存请求锁的信息

同样的这个东西也可以用来保存线程的信息,而这个线程对其他的线程是不可见的

47>

python线程唤醒_Python 并发编程(一)之线程相关推荐

  1. python锁机制_Python并发编程之谈谈线程中的“锁机制”(三)

    大家好,并发编程 进入第三篇. 今天我们来讲讲,线程里的锁机制. 本文目录 何为Lock( 锁 )?如何使用Lock( 锁 )?为何要使用锁?可重入锁(RLock)防止死锁的加锁机制饱受争议的GIL( ...

  2. python 线程同步_Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

  3. python 消息机制_Python并发编程之线程消息通信机制任务协调(四)

    . 前言 前面我已经向大家介绍了,如何使用创建线程,启动线程.相信大家都会有这样一个想法,线程无非就是创建一下,然后再start()下,实在是太简单了. 可是要知道,在真实的项目中,实际场景可要我们举 ...

  4. python多线程调度_python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 进程.线程的调度策略介绍 linux中的进程主要有三种调度策略: 优先级调度:将进程分为普通进程和实时进程: 先进先出(队列)调度:实时进程先创建的先 ...

  5. python3 线程隔离_Python并发编程之线程中的信息隔离(五)

    大家好,并发编程 进入第三篇. 上班第一天,大家应该比较忙吧.小明也是呢,所以今天的内容也很少.只要几分钟就能学完. 昨天我们说,线程与线程之间要通过消息通信来控制程序的执行. 讲完了消息通信,今天就 ...

  6. python并发处理机制_Python并发编程—同步互斥

    同步互斥 线程间通信方法 1.通信方法:线程间使用全局变量进行通信 2.共享资源争夺 共享资源:多个进程或者线程都可以操作的资源称为共享资源.对共享资源的操作代码段称为临界区. 影响 : 对共享资源的 ...

  7. java 5 线程 睡眠,Java并发编程实例--5.线程睡眠

    有时候我们需要让线程在一段时间内不做任何事.例如某线程每个一小时检测一下传感器,剩余的时间不做任何事. 我们可以使用sleep()方法使线程睡眠,此期间不占用计算机资源. 这个方法接受一个整数表示睡眠 ...

  8. 学习笔记(33):Python网络编程并发编程-进程池线程池

    立即学习:https://edu.csdn.net/course/play/24458/296451?utm_source=blogtoedu 进程池与线程池: 一般应用在网站上,进程池或线程池最大的 ...

  9. Java并发编程 synchronized保证线程安全的原理

    文章转载致博客 blog.csdn.net/javazejian/- 自己稍加完善. 线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源 ...

最新文章

  1. 使用Jenkins来实现内部的持续集成流程(下)
  2. bat文件运行java的jar包不弹出dos窗口,开机自启jar包
  3. 又一波“打工人”财富自由!快手赴港上市,4000员工人均身家一夜涨至3000万...
  4. ArcGIS AddIn 图斑比例分割工具,调用捕捉功能
  5. MIT联手IBM发布超大数据集:100多万短视频,多维度标注
  6. HDOJ 1698 Just a Hook(线段树成段更新)
  7. 最近一段时间的手工作品
  8. 方便的管理苹果Mac上菜单栏图标软件推荐:Bartender
  9. NOIP2016D2T2 蚯蚓
  10. 微积分 --- 以e为底的指数函数(个人学习笔记)
  11. 速达软件各版本及产品ID
  12. python开发移动app_手机python开发
  13. 泽林主办前沿IT技术分享峰会隆重召开,深度探讨人工智能、大数据与物联网 的未来发展趋势
  14. 代码大全(第2版)_2021【公式大全3.0版】【(数一)第371页】【(数二)第283页】【(数三)第324页】【有关矩阵秩的重要结论】6)~...
  15. 编译ORB-SLAM2遇到的问题及解决方法
  16. zhengyang:全面了解风控决策引擎
  17. 零和博弈(Zero-Sum Games)与非零和博弈(Non-Zero-Sum Games)
  18. 初中英语语法(008)-动词不定式
  19. 1.2RK3288积累
  20. 鄢陵一高2021高考成绩查询单,鄢陵县第一高级中学2020年高考喜报

热门文章

  1. Lambda-函数式接口(1)
  2. 奇异值分解 VS 特征值分解
  3. 【已解决】linux redhat 6 如何打开防火墙中的某个端口?例如:5900端口
  4. 【简单易懂】c++中组合与聚合
  5. while(1); 作用
  6. 约数个数 (排列组合中的乘法原理)
  7. tomcat和idea都占用了8080_IDEA 启动tomcat 端口占用原因以及解决方法( 使用debug模式)...
  8. vue中引用js_从JS中的内存管理说起 —— JS中的弱引用
  9. woocommerce分类页面模板_怎样让wordpress网站的不同分类页面,调用不同的banner图片?...
  10. CPU和内存之间——地址映射(理解很重要)