这个人的系列文章值得一读:http://blog.51cto.com/suhaozhi/category3.html/p2,不过这个系列总共15偏,Python并发入门,有很多文字描述错误,有些道理也是错的,特别是进程那块,竟然说和线程等同,筛选着看就行

你需要对多线程程序中的临界区加锁以避免竞争条件。

要在多线程程序中安全使用可变对象,你需要使用 threading 库中的 Lock 对象,就像下边这个例子这样:

import threading

class SharedCounter:
'''
A counter object that can be shared by multiple threads.
'''
def __init__(self, initial_value = ):
self._value = initial_value
self._value_lock = threading.Lock() def incr(self,delta=):
'''
Increment the counter with locking
'''
with self._value_lock:
self._value += delta def decr(self,delta=):
'''
Decrement the counter with locking
'''
with self._value_lock:
self._value -= delta

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

 

多线程与互斥锁

一、锁的概念。

  锁,通常被用来实现共享数据的访问,为每一个共享的数据,创建一个Lock对象(一把锁),当需要访问这个共享的资源时,可以调用acquire方法来获取一个锁的对象,当共享资源访问结束后,在调用release方法去解锁;最好结合with使用,因为with可以自动释放锁,也会处理异常导致释放锁失败的问题。

二、python中的互斥锁。

  在介绍互斥锁之前,先来一起看一个例子。(每个线程对num实现一次-1的操作)

import threading
import time num = #每个线程都共享这个变量。
tread_list = [] def add_num():
global num #每个线程都去获取这个全局变量。
temp_num = num
time.sleep(0.1) #执行sleep,相当于执行IO操作.
num = temp_num - #对公共的变量做一个-1的操作。
for i in range(): #同时开启200个线程
t = threading.Thread(target=add_num)
t.start() # 子线程准备就绪,等待获取CPU时间片执行线程任务add_num
tread_list.append(t)
for t in tread_list:
t.join() # 表示主线程会等待所有子线程执行结束后继续往下执行下面的代码语句,即执行:print "ending....num = %s" %(num)
print "ending....num = %s" %(num)

最后的结果就是:ending....num = 199

结果并不是我们想要的。来分析下为何会出现这种现象。

  200个线程现在想统一修改一个全局变量,由于python解释器的GIL(全局解释锁)锁的限制,每次只能有一个线程在cpu上运行,在执行到sleep时,就相当于一次I/O操作,这时就会切到其他的线程,在执行sleep之前,当前运行的这个线程,这个线程取到的全局变量的值是200(temp_num = 200),还没来得及做修改,就被切换到其他线程了,其他的线程也是一样的道理,取到temp_num = 200这个值后,还没来得及计算,执行到sleep触发一次IO操作后,又切到了其他的线程,第2个第3个直到最后一个线程都拿到了temp_num=200这个变量后,后面的计算操作才会开始运行!(不要忘记一个概念,线程在切换之前,是会保存当前执行状态的)当所有线程都拿到了emp_num=200这个变量后,每个线程都会自己执行一遍

num = temp_num - 1这也就导致了每个线程执行的都是200-1 所以,最后的结果就等于199.

下面执行的结果却是200,为什么?

因为主线程没有使用join,所以主线程没有等待子线程结束就先结束了,这个时候,所有的子线程还没有来得及做减一操作,所以num的值还是200。

此时的200个子线程就成了孤儿线程,会有系统的初始进程init进行托管,等子线程执行结束后,init把子线程的资源进行回收。

最后我们再来查看num的值,依然是199,这是因为子线程被遗弃后成为孤儿线程,但是依然长大成人,完成了自己的任务,然后被init回收。

    

  还拿刚刚写的那个减法程序举例,我们把sleep的时间缩短为0.001秒看看会出现什么效果?还是上一段代码,只不过把add_num函数的time.sleep(0.1)改为time.sleep(0.001)看看出现了什么效果。这个结果完全出乎意料,最终num变成了115或者其他的值,每个系统值会有不同。

  接着来分析下造成这种结果的原因。当sleep时间较短的时候,在线程切换的过程中,之前运行的线程的sleep就已经执行结束了,就会重新参与竞争cpu资源,在切的过程中,之前的线程sleep结束,就有了被切回去的可能,继续执行后面的num = temp_num - 1 所以就会导致这种情况。

注意!!这里面的sleep是用来模拟程序中的I/O操作!

  从第二个例子中,我们可以看到一个全局资源被抢占的现象,没有控制多个线程对一个全局资源的访问控制,造成全局资源的损坏(这里的损坏是指得到了我们不想要的结果)使我们无法预测程序最后执行的结果,如果想避免这种问题,就需要用到“互斥锁”。

  “互斥锁”最主要的作用就是,保证在操作共享数据时,共享数据的完整性。

  互斥锁实现的方式,就是为每个共享的资源创建一个Lock对象,当需要访问这个共享资源的时候,调用这个锁的acquire方法来获取锁的对象,资源访问结束后,在调用release方法去解锁。

  我们对上面的程序进行整改,为此我们需要添加一个互斥锁变量t_lock = threading.Lock(),然后在争夺资源的时候之前我们会先抢占这把锁t_lock.acquire(),对资源使用完成之后我们在释放这把锁t_lock.release().

# -*- coding:utf- -*-

import threading
import time num =
tread_list = [] t_lock = threading.RLock() #创建一个锁的对象。 或者写成:t_lock = threading.Lock() def add_num(): global num,temp_num if t_lock.acquire(): #加锁 使用with: with t_lock:
temp_num = num                                  temp_num=num
time.sleep(0.001) #执行sleep,相当于执行IO操作.                 time.sleep(0.001)
num = temp_num - 1                                num = temp_num -1 
t_lock.release() #公共资源访问和操作结束后,解锁。             for i in range():
t = threading.Thread(target=add_num)
t.start()
tread_list.append(t)
for t in tread_list:
t.join() print "ending....num = %s" %(num)

最后看下输出结果:ending....num = 800。之前的资源抢占现象得到了解决。

当一个线程去调用一个Lock对象的acquire()方法去得到一个锁时,这把锁就会进入一个“locked”锁定的状态,在锁定时,每次只有一个线程可以获得锁,如果有第二个线程试图去获得锁(去访问操作共享资源时),去操作共享的数据时,第二个线程就会进入阻塞状态,直到线程1对共享数据资源操作结束后,调用了这个lock对象的release方法后(此时的锁已经变为“unlocked”状态),线程二才可以去操作共享资源。

大概的加锁思路就是这样的:

import threading

R=threading.Lock()  #创建一个锁的对象

R.acquire() #加锁

'''

对公共数据的操作    #执行了对公共数据的操作后

'''

R.release() #解锁

最后补充~

写到这里,可能会有人觉得,互斥锁和join没什么区别!!事实并非如此!

互斥锁可以做到,只有对公共数据进行访问或者操作的时候是串行模式!

如果使用了join,那么两个线程中所有执行的操作都会变为串行模式!!

这两个还是有很大区别的!

linux会根据线程的优先级、线程的闲置情况、I/O操作、紧要程度等对线程进行切换,进程CPU时间片的分配。更详细的可以参考《Linux内核设计与实现》

refer to:

1、https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431928972981094a382e5584413fa040b46d46cce48e000

2、https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p04_locking_critical_sections.html#id3

3、http://blog.51cto.com/suhaozhi/1924938

threading.local()、多线程里全局变量锁的更多相关文章

  1. threading.local和高级

    threading.local特点 ①为每个线程开辟空间,让你进行存取值(根据线程ID来固定某个值) ②flask中没有threading.local,但是flask中的上下文管理的思想是借鉴的thr ...

  2. 网络编程 多线程/socketserver模块/ threading.local

    线程:进程中负责程序执行的执行单元. 多线程:在1个进程中存在多个线程. 进程只是用来把资源集中在一起,而线程才是cpu上的执行单位. 每个进程都会默认有一个控制线程也叫作主线程. 进程之间是竞争关系 ...

  3. 多线程多进程学习threading,queue线程安全队列,线程间数据状态读取。threading.local() threading.RLock()

    http://www.cnblogs.com/alex3714/articles/5230609.html python的多线程是通过上下文切换实现的,只能利用一核CPU,不适合CPU密集操作型任务, ...

  4. [Python 多线程] threading.local类 (六)

    在使用threading.local()之前,先了解一下局部变量和全局变量. 局部变量: import threading import time def worker(): x = 0 for i ...

  5. 线程锁、threading.local(flask源码中用的到)、线程池、生产者消费者模型

    一.线程锁 线程安全,多线程操作时,内部会让所有线程排队处理.如:list/dict/Queue 线程不安全 + 人(锁) => 排队处理 1.RLock/Lock:一次放一个 a.创建10个线 ...

  6. 锁丶threading.local丶线程池丶生产者消费者模型

    一丶锁 线程安全: 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取. import threading v = ...

  7. 锁、threading.local、线程池

    一.锁 Lock(1次放1个) 什么时候用到锁: 线程安全,多线程操作时,内部会让所有线程排队处理.如:list.dict.queue 线程不安全, import threading import t ...

  8. python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型

    线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...

  9. 锁,threading local,以及生产者和消费者模型

    1.锁:Lock(一次放行一个) 线程安全,多线程操作时,内部会让所有的线程排队处理. 线程不安全:+人=>排队处理 以后锁代码块 v=[] lock=threading.Lock()#声明锁 ...

随机推荐

  1. day4递归原理及实现

    递归 特定: 递归算法是一种直接或者间接地调用自身算法的过程.在计算机编写程序中,递归算法对解决一大类问题十分有效,它往往是算法的描述简洁而且易于理解. 递归算法解决问题的特点: (1)递归就是在过程 ...

  2. 05 java 基础:运算符、程序结构

    赋值运算符 : = 三元运算符 : ? 算术运算符 : +.- .*./.% 自增自减运算符: ++.-- 关系运算符:>.<.==.>=.<=.!= 逻辑运算符 :& ...

  3. Asp.net vNext 学习之路(二)

    View component(视图组件)应该是MVC6 新加的一个东西,类似于分部视图.本文将演示在mvc 6中 怎么添加视图组件以及怎么在视图中注入一个服务. 本文包括以下内容: 1,创建一个新的a ...

  4. LoadRunner项目结合抓包工具

    LoadRunner项目结合抓包工具 常见的抓包工具包括:     1. Http协议   报文分为"请求","应答"两大类. 请求: 方法-URL-协议/版本 ...

  5. Java 中如何计算两个字符串时间之间的时间差?(单位为分钟)

    Java 中如何计算两个字符串时间之间的时间差?(单位为分钟) import java.text.DateFormat; import java.text.ParseException; import ...

  6. Java多线程编程——wait()和notify()、notifyAll()

    1.源码 wait() notify() notifyAll()都是Object类中方法.源码如下所示: public final native void notify(); public final ...

  7. Python开发基础-Day30多线程锁机制

    GIL(全局解释器锁) GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL 在Cpython解释器 ...

  8. FastReport.Net使用:[14]文本控件使用

    文本控件(Text)是FastReport中最常用的控件了,它可以是一行\多行文本.数据源的列.报表参数.汇总值.表达式,它还可以是以上任何元素的组合. 如何使用文本编辑器 1.双击文本框进入文本编辑 ...

  9. 「POI2011 R1」Conspiracy

    「POI2011 R1」Conspiracy 解题思路 : 问题转化为,将点集分成两部分,其中一部分恰好组成一个团,其中另一部分恰好组成一个独立集. 观察发现,如果求出了一个解,那么答案最多可以在这个 ...

  10. [转]php-fpm - 启动参数及重要配置详解

    约定几个目录/usr/local/php/sbin/php-fpm/usr/local/php/etc/php-fpm.conf/usr/local/php/etc/php.ini 一,php-fpm ...