欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python多线程中阻塞(join)与锁(Lock)的使用方式

程序员文章站 2022-05-02 12:55:20
...

关于阻塞主线程
join的错误用法
Thread.join() 作用为阻塞主线程,即在子线程未返回的时候,主线程等待其返回然后再继续执行.

join不能与start在循环里连用
以下为错误代码,代码创建了5个线程,然后用一个循环**线程,**之后令其阻塞主线程.

threads = [Thread() for i in range(5)]
for thread in threads:
    thread.start()
    thread.join()

执行过程:

  1. 第一次循环中,主线程通过start函数**线程1,线程1进行计算.
  2. 由于start函数不阻塞主线程,在线程1进行运算的同时,主线程向下执行join函数.
  3. 执行join之后,主线程被线程1阻塞,在线程1返回结果之前,主线程无法执行下一轮循环.
  4. 线程1计算完成之后,解除对主线程的阻塞.
  5. 主线程进入下一轮循环,**线程2并被其阻塞…

如此往复,可以看出,本来应该并发的五个线程,在这里变成了顺序队列,效率和单线程无异.
join的正确用法
使用两个循环分别处理start和join函数.即可实现并发.

threads = [Thread() for i in range(5)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join(

关于线程锁(threading.Lock)
单核CPU+PIL是否还需要锁?

非原子操作 count = count + 1 理论上是线程不安全的.
使用3个线程同时执行上述操作改变全局变量count的值,并查看程序执行结果.
如果结果正确,则表示未出现线程冲突.
使用以下代码测试

# -*- coding: utf-8 -*-

import threading
import time

count = 0


class Counter(threading.Thread):
    def __init__(self, name):
        self.thread_name = name
        super(Counter, self).__init__(name=name)

    def run(self):
        global count
        for i in range(100000):
            count = count + 1


counters = [Counter('thread:%s' % i) for i in range(5)]

t1 = time.time()
for counter in counters:
    counter.start()

print('count=%s' % count, "consume time:{}".format(time.time() - t1))

执行结果:

count=491648 consume time:0.030451297760009766

可以看到执行结果并不准确,并且每次执行结果都不一样

事实上每次运行结果都不相同且不正确,这证明单核CPU+PIL仍无法保证线程安全,需要加锁.

加锁后的正确代码:
第一种用法

# -*- coding: utf-8 -*-

import threading
import time

count = 0
lock = threading.Lock()  


class Counter(threading.Thread):
    def __init__(self, name):
        self.thread_name = name
        self.lock = threading.Lock()  
        super(Counter, self).__init__(name=name)

    def run(self):
        global count
        # global lock
        for i in range(100000):
            lock.acquire()
            count = count + 1
            lock.release()

counters = [Counter('thread:%s' % i) for i in range(5)]
t1 = time.time()
for counter in counters:
    counter.start()
    counter.join()  # 添加join使线程执行完

print('count=%s' % count, "consume time:{}".format(time.time() - t1))

执行结果:

count=500000 consume time:0.10671520233154297

第二种写法

# -*- coding: utf-8 -*-

import threading
import time

count = 0
lock = threading.Lock()  # 正确的位置


class Counter(threading.Thread):
    def __init__(self, name):
        self.thread_name = name
        self.lock = threading.Lock()  # 此种写法锁不能在此处声明
        super(Counter, self).__init__(name=name)

    def run(self):
        global count
        for i in range(100000):
            lock.acquire()
            count = count + 1
            lock.release()


counters = [Counter('thread:%s' % i) for i in range(5)]
t1 = time.time()
mythread = []
for i in range(5):
    t = Counter('thread:%s' % i)
    mythread.append(t)
    t.start()

for counter in mythread:
    counter.join()

# time.sleep(1)
print('count=%s' % count, "consume time:{}".format(time.time() - t1))

执行结果

count=500000 consume time:0.7899613380432129

可以明显看出第二种写法比第一种消耗的时间要多

第三种写法:使用with 上下文的方式自动获取锁和释放锁

# -*- coding: utf-8 -*-

import threading
import time

count = 0
lock = threading.Lock() 


class Counter(threading.Thread):
    def __init__(self, name):
        self.thread_name = name
        self.lock = threading.Lock()  
        super(Counter, self).__init__(name=name)

    def run(self):
        global count
        # global lock
        for i in range(100000):
            with self.lock:
                count = count + 1


counters = [Counter('thread:%s' % i) for i in range(5)]
t1 = time.time()
for counter in counters:
    counter.start()
    counter.join()  # 添加join使线程执行完

print('count=%s' % count, "consume time:{}".format(time.time() - t1))

执行结果:

count=500000 consume time:0.12868905067443848

可以看出执行结果和第一种写法几乎是一样的