1 线程同步

        线程同步,线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作后。不同的操作系统有多种实现方式。比如临界区(Critical Section)、互斥锁(Mutex)、信号量(Semaphore)、事件(Event)等。

1.1 Event

        Event是线程间通讯机制最简单的实现,使用一个内部标记flag,主要提供了三个方法wait、clear、set,通过操作flag来控制线程的执行。

  • clear():将'Flag'设置为False。
  • set():将'Flag'设置为True。
  • wait(timeout=None):等待'Flag'为True后,继续执行(timeout为超时时间,否则永远等待)。
  • is_set(): 判断'Flag'是否为

1.1.1 什么是Flag?

        Event对象在全局定义了一个'Flag',如果'Flag'值为 False,那么当程序执行 Event对象的wait方法时就会阻塞,如果'Flag'值为True,那已经阻塞的wait方法会继续执行。

1.1.2 Event原理

        在使用threading.Event 实现线程间通信时:使用threading.Event可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False。一旦该线程通过wait()方法进入等待状态,直到另一个线程调用该Event的set()方法将内置标志设置为True时,该Event会通知所有等待状态的线程恢复运行。

1.1.3 吃包子

有下面代码,大欣负责吃包子,厨师负责做包子,只有厨师做好了,大欣才能开始吃。

  1. import time
  2. import random
  3. event = threading.Event()
  4. def consumer():
  5. current = threading.current_thread()
  6. print('{} 等着吃包子...'.format(current.name))
  7. event.wait()
  8. print('包子来了,我正在吃')
  9. for i in range(1,5):
  10. time.sleep(random.randrange(1,3))
  11. print('{} 吃 包子-{}。'.format(current.name,i))
  12. def chef():
  13. current = threading.current_thread()
  14. print('{} 正在做包子'.format(current.name))
  15. time.sleep(3)
  16. print('{} 包子做好了,下班'.format(current.name))
  17. event.set()
  18. chef = threading.Thread(target=chef,name='大厨师')
  19. chef.start()
  20. consumer = threading.Thread(target=consumer,name='大欣')
  21. consumer.start()

运行consumer时,包子还没做,所以只能等着,等chef做完了以后,设置了event为True,这时consumer就开始吃了。wait还可以指定等待时间,比如chef做的太慢了,consumer不吃了。

  1. import time
  2. import random
  3. event = threading.Event()
  4. def consumer():
  5. current = threading.current_thread()
  6. print('{} 等着吃包子...'.format(current.name))
  7. if not event.wait(2):
  8. print('太慢了,不吃了')
  9. else:
  10. print('包子来了,我正在吃')
  11. for i in range(1,5):
  12. time.sleep(random.randrange(1,3))
  13. print('{} 吃 包子-{}。'.format(current.name,i))
  14. def chef():
  15. current = threading.current_thread()
  16. print('{} 正在做包子'.format(current.name))
  17. time.sleep(8)
  18. print('{} 包子做好了,下班'.format(current.name))
  19. event.set()
  20. chef = threading.Thread(target=chef,name='大厨师')
  21. chef.start()
  22. consumer = threading.Thread(target=consumer,name='大欣')
  23. consumer.start()

当Event被set后,wait的返回值就是True,如果wait(2),在2秒内,Event没有被set,那么返回值是False。

1.2 Lock

        由于线程间的数据是共享的,当我们多个线程操作一个相同的用户的数据时,有可能造成混乱,如下例子:

  1. import threading
  2. import time
  3. n = 10
  4. def work():
  5. global n
  6. while n > 0:
  7. time.sleep(0.01)
  8. n -= 1
  9. if __name__ == '__main__':
  10. t_l = []
  11. for i in range(10):
  12. t = threading.Thread(target=work)
  13. t_l.append(t)
  14. t.start()
  15. for t in t_l:
  16. t.join()
  17. print(n)

  我们认为结果应该是0,但是结果可能不如人意,因为进程间共享数据的问题,多个进程同时修改共享数据时,由于GIL的存在同一时刻只有1个线程在运行。当n的值为1的时候,10个子线程很有可能同时判断成功,再要修改的时候被挂起(时间片用完),等到真正回来修改的时候,n已经被其他线程改过来!所以如果要保持数据的正确性,那么就需要牺牲性能,即使用锁机制。

  1. 创建一个锁,由于进程内的线程共享进程数据,那么不需要传递,就可以直接调用
  1. import threading
  2. mutex = threading.Lock()
  1. 加锁解锁

    Lock内部实现了__enter__和__exit__的方法,所以我们可以使用两种方式来加锁或者解锁。

  1. # 调用方式一
  2. mutex.acquire() # 加锁
  3. '''code'''
  4. mutex.release() # 解锁
  5. # 调用方式二
  6. with mutex:
  7. '''code''' # 离开wit代码段,自动解锁

1.2.1 lock方法

名称 含义
acquire(blocking=True,timeout=-1) 获取锁并加锁
blocking:表示是否阻塞,默认为True表示阻塞。
timeout:表示阻塞超时时间。
当blocking为非阻塞时,timeout不能进行设置
release() 释放锁,可以从任何线程上调用释放.
已上锁,调用时会释放锁,即重置为unlocked。
未上锁,调用时会抛出RuntimeError异常

所以,上面的例子可以有如下修改:

  1. import threading
  2. import time
  3. mutex = threading.Lock()
  4. n = 10
  5. def work():
  6. global n
  7. while True:
  8. mutex.acquire()
  9. if n > 0:
  10. time.sleep(1)
  11. n -= 1
  12. mutex.release()
  13. else:
  14. mutex.release()
  15. break
  16. if __name__ == '__main__':
  17. t_l = []
  18. for i in range(10):
  19. t = threading.Thread(target=work)
  20. t_l.append(t)
  21. t.start()
  22. for t in t_l:
  23. t.join()
  24. print(n)

在判断的时候就开始加锁,在修改完毕的时候解锁。这样加锁的情况下我们发现运行时间变长了,那是因为只有抢到锁的线程才可以工作(穿行执行),

1.2.2 计数器

有下面一个计数器类,来看如何加锁

  1. import threading
  2. class Counter:
  3. def __init__(self):
  4. self._value = 0
  5. @property
  6. def value(self):
  7. return self._value
  8. def inc(self):
  9. self._value += 1
  10. def dec(self):
  11. self._value -= 1
  12. def calc(c:Counter):
  13. for _ in range(1000):
  14. for i in range(-50,50):
  15. if i < 0:
  16. c.dec()
  17. else:
  18. c.inc()
  19. c = Counter()
  20. lst = []
  21. for i in range(10):
  22. t = threading.Thread(target=calc, args=(c,))
  23. lst.append(t)
  24. t.start()
  25. for t in lst:
  26. t.join()
  27. print(c.value)

        在需要调用和修改的地方加锁,修改完毕后解锁,是锁使用的基本原则,一般来说,加锁就要解锁,但是加锁和解锁之间会有一些代码要执行,如果出现异常,那么锁是无法释放的,但是当前线程已经终止了,这种情况一般称为死锁,可以添加异常处理,来确保锁一定被释放。

  1. import threading
  2. mutex = threading.Lock()
  3. class Counter:
  4. def __init__(self):
  5. self._value = 0
  6. @property
  7. def value(self):
  8. return self._value
  9. def inc(self):
  10. try: # 添加异常处理,即便时崩溃也可以释放锁
  11. mutex.acquire()
  12. self._value += 1
  13. finally:
  14. mutex.release()
  15. def dec(self):
  16. with mutex: # 上下文管理写法
  17. self._value -= 1
  18. def calc(c:Counter):
  19. for _ in range(1000):
  20. for i in range(-50,50):
  21. if i < 0:
  22. c.dec()
  23. else:
  24. c.inc()
  25. c = Counter()
  26. lst = []
  27. for i in range(10):
  28. t = threading.Thread(target=calc, args=(c,))
  29. lst.append(t)
  30. t.start()
  31. for t in lst:
  32. t.join()

当然这里也可以为每一个计数器实例对象初始化一个自己的锁,如果用全局锁,那么不同的计数器实例,会相互影响(因为多个实例,共享一把锁),因为不同实例的结果是不同的,所以建议为每个实例构建一个自己的锁。

  1. class Counter:
  2. def __init__(self):
  3. self._value = 0
  4. self._lock = threading.Lock()
  5. @property
  6. def value(self):
  7. return self._value
  8. def inc(self):
  9. try:
  10. self._lock.acquire()
  11. self._value += 1
  12. finally:
  13. self._lock.release()
  14. def dec(self):
  15. with self._lock:
  16. self._value -= 1

1.2.3 非阻塞锁

当lock.acquire(False)时,该锁就是非阻塞锁了,调用时,仅获取一次,如果获取到那么返回True,否则返回False。

  1. import time
  2. import threading
  3. import random
  4. import logging
  5. logging.basicConfig(level=logging.INFO, format='%(asctime)s %(threadName)s %(thread)s %(message)s')
  6. def worker(tasks:list):
  7. for task in tasks:
  8. if task.lock.acquire(False): # 抢到任务
  9. time.sleep(random.randrange(1,3)) # 模拟执行任务需要的时间
  10. logging.info('{} I am working for {}'.format(threading.current_thread().name, task))
  11. else: # 没有抢到任务
  12. logging.info('{} not get {}'.format(threading.current_thread().name,task))
  13. class Task:
  14. def __init__(self,name):
  15. self.task = name
  16. self.lock = threading.Lock()
  17. def __repr__(self):
  18. return self.task
  19. __str__ = __repr__
  20. if __name__ == '__main__':
  21. task_list = [Task('task{}'.format(x)) for x in range(1,11)] # 构建任务列表
  22. for _ in range(10):
  23. threading.Thread(target=worker,args=(task_list,)).start() # 开启多线程执行任务

10个任务,交给10个线程运行,谁抢到哪个线程,谁就运行。

1.2.4 锁应用场景

        锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候。但如果都是读取的话,就不需要了。使用锁的时候有一下几点需要特别注意:

  1. 少于锁,必要时用锁,使用了锁,多线程访问被被锁资源时,就成了穿行的了,要么排队,要么争抢执行。
  2. 加锁时间越短越好,不需要时立即释放锁。
  3. 一定要避免死锁。

33 - 并发编程-线程同步-Event-lock的更多相关文章

  1. Python并发编程-线程同步(线程安全)

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

  2. 11 并发编程-(线程)-信号量&Event&定时器

    1.信号量(本质也是一把锁)Semaphore模块 信号量也是一把锁,可以指定信号量为5,对比互斥锁同一时间只能有一个任务抢到锁去执行, 信号量同一时间可以有5个任务拿到锁去执行, 如果说互斥锁是合租 ...

  3. C#并行编程-线程同步原语

    菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...

  4. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  5. 【转】Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

  6. 8、Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  7. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  8. java并发编程 线程基础

    java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...

  9. java并发:线程同步机制之Lock

    一.初识Lock Lock是一个接口,提供了无条件的.可轮询的.定时的.可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock, ...

随机推荐

  1. 第189天:BOM属性方法

    一.BOM---location 1.访问页面 location.href = "http://www.baidu.com"; location.assign("http ...

  2. HDU4675_GCD of Sequence

    很有意思的一个数论题. 是这样的,给你一个数数组a[i],其中给出的每一个数字和要求的数字方位都是[1,m],现在问你根据a[]构造一个b[],且a和b中间的不相等的元素的个数恰好有k个. 现在问你g ...

  3. Codeforces 627D Preorder Test(二分+树形DP)

    题意:给出一棵无根树,每个节点有一个权值,现在要让dfs序的前k个结点的最小值最大,求出这个值. 考虑二分答案,把>=答案的点标记为1,<答案的点标记为0,现在的任务时使得dfs序的前k个 ...

  4. wp如何代码重启手机

    用过windows phone手机操作系统的人都知道,wp的系统设置界面很长一串,我们并不能快速进入想要的设置项,更受不了的是有些常用的设置项竟然在最下边.因为前段时间没事做,于是乎写了个wp的工具类 ...

  5. 元素定位:selenium消息框处理 (alert、confirm、prompt)

    基础普及 alert对话框 .细分三种,Alert,prompt,confirm 1. alert() 弹出个提示框 (确定) 警告消息框 alert 方法有一个参数,即希望对用户显示的文本字符串.该 ...

  6. HPP注入详解

      ###HPP参数污染的定义 HTTP Parameter Pollution简称HPP,所以有的人也称之为“HPP参数污染”,HPP是一种注入型的漏洞,攻击者通过在HTTP请求中插入特定的参数来发 ...

  7. [ACM][2018南京预赛]Sum

    一.题面 样例输入: 2 5 8 样例输出: 8 14 二.思路 关键词:线性筛 在Zed的帮助下知道了这是一道线性筛的比较裸的题了.考试过程中肝这道题的时间最久,费了心思找到递推式后,发现根本不是在 ...

  8. Linux之GDB调试介绍与应用20170601

    一.GDB调试命令   描述 backtrace(或bt) 查看各级函数调用及参数 finish 连续运行到当前函数返回为止,然后停下来等待命令 frame(或f) 帧编号 选择栈帧 info(或i) ...

  9. CSK & KCF(tracking)

    转自:http://blog.csdn.net/ben_ben_niao/article/details/51364323 上次介绍了SRDCF算法,发展历史轨迹为CSK=>>KCF/DC ...

  10. python学习(十三)进程和线程

    python多进程 from multiprocessing import Process import os def processFunc(name): print("child pro ...