【Python之旅】第六篇(四):Python多线程锁

 

python lock 多线程 多线程使用方法 多线程锁

摘要:   在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。 1.给线程加锁的原因     我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一...

在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。

1.给线程加锁的原因

我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一内存空间中的某些数据(只是调用,没有做修改)。

试想一下,在某一进程中,内存空间中存有一个变量对象的值为num=8,假如某一时刻有多个线程需要同时使用这个对象,出于这些线程要实现不同功能的需要,线程A需要将num减1后再使用,线程B需要将num加1后再使用,而线程C则是需要使用num原来的值8。由于这三个线程都是共享存储num值的内存空间的,并且这三个线程是可以同时并发执行的,当三个线程同时对num操作时,因为num只有一个,所以肯定会存在不同的操作顺序,想象一下下面这样操作过程:

1
2
3
4
5
第一步:线程A修改了num的值为7
第二步:线程C不知道num的值已经发生了改变,直接调用了num的值7
第三步:线程B对num值加1,此时num值变为8
第四步:线程B使用了num值8
第五步:线程A使用了num值8

因为num只有一个,而三个操作都针对一个num进行,所以上面的操作过程是完全有可能的,而原来线程A、B、C想要使用的num值应该分别为:7、9、8,这里却变成了:8、8、7。试想一下,如果这三个线程的操作对整个程序的执行是至关重要的,会造成什么样的后果?

因此出于程序稳定运行的考虑,对于线程需要调用内存中的共享数据时,我们就需要为线程加锁。

2.Python多线程锁

(1)

先看下面一个未给线程加锁的程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threading
import time
 
number = 0
 
def run(num):
    global number
    number += 1
    print number
    time.sleep(1)
     
for in range(20):
    t = threading.Thread(target=run, args=(i,))
    t.start()

程序执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面是多个线程同时抢占同一内存空间的例子,但从执行结果中可以看到,程序依然顺序地输出1-19,而没有出现上面说的情况,那是仅仅是因为量少的原因,虽然执行正常,没有出错,但是并不代表不会出错。

(2)

看下面给线程加锁的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
import time
 
number = 0
 
lock = threading.RLock()    #调用threading模块中的RLock()
 
def run(num):
    lock.acquire()      #开始给线程加锁
    global number
    number += 1
    lock.release()      #给线程解锁
    print number
    time.sleep(1)
 
for in range(20):
    t = threading.Thread(target=run, args=(i,))
    t.start()

程序执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

程序的执行结果肯定是会正常的,而在没有给线程加锁之前,则有可能是正常,注意这是两种完全不同的概念。

分析一下上面的程序:在某一线程修改num的值时,即给该线程加锁,该线程加锁后,只要是该线程需要调用的代码以及涉及的内存空间,都会立即被锁上,比如这里的"number+=1",其它线程虽然也在并发同时执行,但是不能执行"number+=1"这行代码的,即不能够去访问或修改num这一个共享内存空间的数据,只能等待该线程解锁后才能执行;当该线程解锁后,另一个线程马上加锁再来修改number的值,同时也不允许其它线程占用,如此类推,直到所有线程执行完毕。

根据上面的分析,为线程加锁就可以解决前面讲的线程安全问题。

(3)

为了更好的理解线程加锁的一个过程,把上面的代码修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
import time
 
number = 0
 
lock = threading.RLock()
 
def run(num):
    lock.acquire()
    global number
    number += 1
    print number
    time.sleep(1)    #把time.sleep(1)也锁在线程中
    lock.release()
     
for in range(20):
    t = threading.Thread(target=run, args=(i,))
    t.start()

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

程序的执行结果跟上面是完全一样,但是程序的执行过程却大不一样,这里说一下修改代码后程序的执行过程:每输出一个数字,sleep 1秒后再输出下一个数字,如此类推。

为了更好的说明,我们可以看一下执行完此程序所花的时间:

1
2
3
4
5
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ time python thread_clock6.py | grep 'real'
 
real    0m20.073s
user    0m0.024s
sys 0m0.008s

由执行时间可以更好的说明上面的执行过程,但为什么会这样呢?下面来分析一下:由(2)的分析可知,虽然20个线程都是在同时并发执行run这一个函数,这里与(2)不同在于,(2)只加锁了涉及修改number的程序代码,而这里是加锁了整一个函数!所以在20个线程同时开始并发执行这个函数时,由于每一个线程的执行都要加锁,并且加锁的是整一个执行的函数,因此其它线程就无法调用该函数中的程序代码,只能等待一个线程执行完毕后再调用该函数的程序代码,如此一来,一个线程的执行需要sleep(1)一次,则20个线程的执行就需要sleep(1)20次,并且该过程是串行的,因此我们才看到如上面所说的程序执行过程,也可以清晰的知道为什么程序的执行需要20s了。

由上面的分析,我们不仅可以知道为什么要给线程加锁以及如何加锁,还可以比较清楚的知道线程加锁的一个过程了,以后在编写程序的时候,类似情况的,我们就应该要为线程加锁。

 

python lock 多线程 多线程使用方法 多线程锁

摘要:   在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。 1.给线程加锁的原因     我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一...

在多线程程序执行过程中,为什么需要给一些线程加锁以及如何加锁,下面就来说一说。

1.给线程加锁的原因

我们知道,不同进程之间的内存空间数据是不能够共享的,试想一下,如果可以随意共享,谈何安全?但是一个进程中的多个线程是可以共享这个进程的内存空间中的数据的,比如多个线程可以同时调用某一内存空间中的某些数据(只是调用,没有做修改)。

试想一下,在某一进程中,内存空间中存有一个变量对象的值为num=8,假如某一时刻有多个线程需要同时使用这个对象,出于这些线程要实现不同功能的需要,线程A需要将num减1后再使用,线程B需要将num加1后再使用,而线程C则是需要使用num原来的值8。由于这三个线程都是共享存储num值的内存空间的,并且这三个线程是可以同时并发执行的,当三个线程同时对num操作时,因为num只有一个,所以肯定会存在不同的操作顺序,想象一下下面这样操作过程:

1
2
3
4
5
第一步:线程A修改了num的值为7
第二步:线程C不知道num的值已经发生了改变,直接调用了num的值7
第三步:线程B对num值加1,此时num值变为8
第四步:线程B使用了num值8
第五步:线程A使用了num值8

因为num只有一个,而三个操作都针对一个num进行,所以上面的操作过程是完全有可能的,而原来线程A、B、C想要使用的num值应该分别为:7、9、8,这里却变成了:8、8、7。试想一下,如果这三个线程的操作对整个程序的执行是至关重要的,会造成什么样的后果?

因此出于程序稳定运行的考虑,对于线程需要调用内存中的共享数据时,我们就需要为线程加锁。

2.Python多线程锁

(1)

先看下面一个未给线程加锁的程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threading
import time
 
number = 0
 
def run(num):
    global number
    number += 1
    print number
    time.sleep(1)
     
for in range(20):
    t = threading.Thread(target=run, args=(i,))
    t.start()

程序执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面是多个线程同时抢占同一内存空间的例子,但从执行结果中可以看到,程序依然顺序地输出1-19,而没有出现上面说的情况,那是仅仅是因为量少的原因,虽然执行正常,没有出错,但是并不代表不会出错。

(2)

看下面给线程加锁的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
import time
 
number = 0
 
lock = threading.RLock()    #调用threading模块中的RLock()
 
def run(num):
    lock.acquire()      #开始给线程加锁
    global number
    number += 1
    lock.release()      #给线程解锁
    print number
    time.sleep(1)
 
for in range(20):
    t = threading.Thread(target=run, args=(i,))
    t.start()

程序执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

程序的执行结果肯定是会正常的,而在没有给线程加锁之前,则有可能是正常,注意这是两种完全不同的概念。

分析一下上面的程序:在某一线程修改num的值时,即给该线程加锁,该线程加锁后,只要是该线程需要调用的代码以及涉及的内存空间,都会立即被锁上,比如这里的"number+=1",其它线程虽然也在并发同时执行,但是不能执行"number+=1"这行代码的,即不能够去访问或修改num这一个共享内存空间的数据,只能等待该线程解锁后才能执行;当该线程解锁后,另一个线程马上加锁再来修改number的值,同时也不允许其它线程占用,如此类推,直到所有线程执行完毕。

根据上面的分析,为线程加锁就可以解决前面讲的线程安全问题。

(3)

为了更好的理解线程加锁的一个过程,把上面的代码修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import threading
import time
 
number = 0
 
lock = threading.RLock()
 
def run(num):
    lock.acquire()
    global number
    number += 1
    print number
    time.sleep(1)    #把time.sleep(1)也锁在线程中
    lock.release()
     
for in range(20):
    t = threading.Thread(target=run, args=(i,))
    t.start()

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ python thread_clock6.py 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

程序的执行结果跟上面是完全一样,但是程序的执行过程却大不一样,这里说一下修改代码后程序的执行过程:每输出一个数字,sleep 1秒后再输出下一个数字,如此类推。

为了更好的说明,我们可以看一下执行完此程序所花的时间:

1
2
3
4
5
xpleaf@xpleaf-machine:/mnt/hgfs/Python/day6$ time python thread_clock6.py | grep 'real'
 
real    0m20.073s
user    0m0.024s
sys 0m0.008s

由执行时间可以更好的说明上面的执行过程,但为什么会这样呢?下面来分析一下:由(2)的分析可知,虽然20个线程都是在同时并发执行run这一个函数,这里与(2)不同在于,(2)只加锁了涉及修改number的程序代码,而这里是加锁了整一个函数!所以在20个线程同时开始并发执行这个函数时,由于每一个线程的执行都要加锁,并且加锁的是整一个执行的函数,因此其它线程就无法调用该函数中的程序代码,只能等待一个线程执行完毕后再调用该函数的程序代码,如此一来,一个线程的执行需要sleep(1)一次,则20个线程的执行就需要sleep(1)20次,并且该过程是串行的,因此我们才看到如上面所说的程序执行过程,也可以清晰的知道为什么程序的执行需要20s了。

由上面的分析,我们不仅可以知道为什么要给线程加锁以及如何加锁,还可以比较清楚的知道线程加锁的一个过程了,以后在编写程序的时候,类似情况的,我们就应该要为线程加锁。

Python多线程锁的更多相关文章

  1. python 多线程锁机制

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

  2. python多线程锁lock/Rlock/BoundedSemaphore/Condition/Event

    import time import threading lock = threading.RLock() n = 10 def task(arg): # 加锁,此区域的代码同一时刻只能有一个线程执行 ...

  3. 浅析Python多线程

    学习Python多线程的资料很多,吐槽Python多线程的博客也不少.本文主要介绍Python多线程实际应用,且假设读者已经了解多线程的基本概念.如果读者对进程线程概念不甚了解,可参见知名博主 阮一峰 ...

  4. Python多线程和Python的锁

    Python多线程 Python中实现多线程有两种方式,一种基于_thread模块(在Python2.x版本中为thread模块,没有下划线)的start_new_thread()函数,另一种基于th ...

  5. python多线程threading.Lock锁用法实例

    本文实例讲述了python多线程threading.Lock锁的用法实例,分享给大家供大家参考.具体分析如下: python的锁可以独立提取出来 mutex = threading.Lock() #锁 ...

  6. python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio

    摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...

  7. python多线程、线程锁

    1.python多线程 多线程可以把空闲时间利用起来 比如有两个进程函数 func1.func2,func1函数里使用sleep休眠一定时间,如果使用单线程调用这两个函数,那么会顺序执行这两个函数 也 ...

  8. 第十五章、Python多线程同步锁,死锁和递归锁

    目录 第十五章.Python多线程同步锁,死锁和递归锁 1. 引子: 2.同步锁 3.死锁 引子: 4.递归锁RLock 原理: 不多说,放代码 总结: 5. 大总结 第十五章.Python多线程同步 ...

  9. python多线程,event,互斥锁,死锁,递归锁,信号量

    Python多线程/event 多线程-threading python的thread模块是⽐较底层的模块, python的threading模块是对thread做了⼀些包装的, 可以更加⽅便的被使⽤ ...

随机推荐

  1. flask request

    请求对象要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:searchword = request.args.get('key', '')用户可能会改变 URL 导致 ...

  2. openerp report image

    webkit : 再mako 文件中插入以下代码, <% %>标签用于再mako中定义代码或者函数. 然后  ${ embed_image('图片类型', 图片字段 , 宽度,高度)  } ...

  3. I题 - A+B for Input-Output Practice (VIII)

      Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u   Description You ...

  4. 转:gpio_direction_output 与 gpio_set_value

    gpio_set_value(port_num,0/1) 一般只是在这个GPIO口的寄存器上写上某个值,至于这个端口是否设置为输出,它就管不了! 而gpio_direction_output (por ...

  5. mapreduce (六) MapReduce实现去重 NullWritable的使用

    习题来源:http://www.cnblogs.com/xia520pi/archive/2012/06/04/2534533.htmlfile1 2012-3-1 a 2012-3-2 b 2012 ...

  6. mapreduce (四) MapReduce实现Grep+sort

    1.txt dong xi cheng xi dong cheng wo ai beijing tian an men qiche dong dong dong 2.txt dong xi cheng ...

  7. ORACLE WIN7安装过程截图

    尽管 有点不正规,但还可以.

  8. 【Linux】鸟哥的Linux私房菜基础学习篇整理(十一)

    1. 直接将命令丢到后台中执行“&”,在命令最后加“&”.    将目前的工作丢到后台中暂停:[Ctrl]+z 2. jobs [-lrs]:查看目前的后台工作状态.参数:-l:除了列 ...

  9. P1894セチの祈り

    描述 在 Ninian 的花园里,有许多琼花,环绕着中间的凉亭.有 N 片琼花,组成一个环.Ninian 想在凉亭中发动 [セチの祈り] , 需要划分出三个区域的琼花,为了平均,要最大化面积最小的区域 ...

  10. 高级数据结构(树状数组套主席树):ZOJ 2112 Dynamic Rankings

    Dynamic Rankings Time Limit: 10 Seconds      Memory Limit: 32768 KB The Company Dynamic Rankings has ...