Python 执行被锁定的代码应该比执行未锁定的代码花费更多的时间

Python 执行被锁定的代码应该比执行未锁定的代码花费更多的时间,python,multithreading,python-2.7,locking,Python,Multithreading,Python 2.7,Locking,我正在尝试python线程和锁定。因此,我创建了两个类。这两个类都使用线程递增和递减类级变量“ref” 在ThreadUnsaceClass中,我在递增和递减之前不使用lock 在ThreadSafeClass中,我在递增和递减之前使用锁 我的假设是,由于锁将强制一些线程等待,所以在ThreadSafeClass的情况下应该需要更多的时间 结果表明ThreadSafeClass的速度更快 这是我的代码(python 2.7) 以下是我的结果: 非安全线程的值3.54868483543->3065

我正在尝试python线程和锁定。因此,我创建了两个类。这两个类都使用线程递增和递减类级变量“ref”

在ThreadUnsaceClass中,我在递增和递减之前不使用lock

在ThreadSafeClass中,我在递增和递减之前使用锁

我的假设是,由于锁将强制一些线程等待,所以在ThreadSafeClass的情况下应该需要更多的时间

结果表明ThreadSafeClass的速度更快

这是我的代码(python 2.7)

以下是我的结果:

非安全线程的值3.54868483543->30653

来自安全线程的值2.28372502327->0


请帮助我理解为什么锁定方式更快

我相信答案是,通过锁定代码的方式,实际上可以避免线程和缓存抖动,从而加快速度,因为每个线程的循环都可以在没有任何其他硬件资源争用的情况下完成。这实际上不是一个苹果对苹果的比较,而是通过将锁移入循环而不是移出循环:

def inc_ref(self):
    time.sleep(0.1)
    for i in xrange(0, self.count_tot):
        self.lock.acquire()
        ThreadUnsafeClass.ref += 1
        self.lock.release()

def dec_ref(self):
    time.sleep(0.1)
    for i in xrange(0, self.count_tot):
        self.lock.acquire()
        ThreadUnsafeClass.ref -= 1
        self.lock.release()
我发现执行时间显著增加(如您所预期的)

为了进一步测试这一理论,我使用了您的代码,并添加了一些更详细的计时,以准确捕获递增/递减操作与锁定所花费的时间:

import threading
import time
import operator

class ThreadUnsafeClass(object):
    ref = 0

    def __init__(self, count_tot=10000):
        self.all_threads = []
        self.count_tot = count_tot
        ThreadUnsafeClass.ref = 0

    def inc_ref(self, ndx):
        time.sleep(0.1)
        ref_time = 0
        for i in xrange(0, self.count_tot):
            op_start = time.time()
            ThreadUnsafeClass.ref += 1
            ref_time += time.time() - op_start
        self.op_times[ndx] = ref_time

    def dec_ref(self, ndx):
        time.sleep(0.1)
        ref_time = 0
        for i in xrange(0, self.count_tot):
            op_start = time.time()
            ThreadUnsafeClass.ref -= 1
            ref_time += time.time() - op_start
        self.op_times[ndx] = ref_time


    def compute_ref_value(self):
        start_time = time.time()
        self.op_times = [0]*100
        for i in xrange(0, 50):
            t1 = threading.Thread(target=self.inc_ref, args=(i*2,))
            t2 = threading.Thread(target=self.dec_ref, args=(i*2+1,))
            t1.start()
            t2.start()
            self.all_threads.append(t1)
            self.all_threads.append(t2)

        for t in self.all_threads:
            t.join()

        op_total = reduce(operator.add, self.op_times)

        print time.time() - start_time, op_total, " -> ",

        return ThreadUnsafeClass.ref

class ThreadSafeClass(object):
    ref = 0

    def __init__(self, count_tot=10000):
        self.all_threads = []
        self.count_tot = count_tot
        ThreadUnsafeClass.ref = 0
        self.lock = threading.Lock()

    def inc_ref(self, ndx):
        time.sleep(0.1)
        lock_start = time.time()
        self.lock.acquire()
        lock_time = time.time() - lock_start
        ref_time = 0
        for i in xrange(0, self.count_tot):
            op_start = time.time()
            ThreadUnsafeClass.ref += 1
            ref_time += time.time() - op_start
        self.lock.release()
        self.op_times[ndx] = ref_time
        self.lock_times[ndx] = lock_time

    def dec_ref(self, ndx):
        time.sleep(0.1)
        lock_start = time.time()
        self.lock.acquire()
        lock_time = time.time() - lock_start
        ref_time = 0
        for i in xrange(0, self.count_tot):
            op_start = time.time()
            ThreadUnsafeClass.ref -= 1
            ref_time += time.time() - op_start
        self.lock.release()
        self.op_times[ndx] = ref_time
        self.lock_times[ndx] = lock_time


    def compute_ref_value(self):
        start_time = time.time()
        self.op_times = [0]*100
        self.lock_times = [0]*100
        for i in xrange(0, 50):
            t1 = threading.Thread(target=self.inc_ref, args=(i*2,))
            t2 = threading.Thread(target=self.dec_ref, args=(i*2+1,))
            t1.start()
            t2.start()
            self.all_threads.append(t1)
            self.all_threads.append(t2)

        for t in self.all_threads:
            t.join()

        op_total = reduce(operator.add, self.op_times)
        lock_total = reduce(operator.add, self.lock_times)

        print time.time() - start_time, op_total, lock_total, " -> ",

        return ThreadUnsafeClass.ref

thread_unsafe_class = ThreadUnsafeClass(100000)
print "Value from un-safe threading ", thread_unsafe_class.compute_ref_value()

thread_safe_class = ThreadSafeClass(100000)
print "Value from safe threading ", thread_safe_class.compute_ref_value()
结果是:

Value from un-safe threading  6.93944501877 297.449399471  ->  13057
Value from safe threading  4.08318996429 2.6313662529 197.359120131  ->  0
显示了在没有锁定的情况下,仅增量和减量的累积时间(在所有线程中)几乎为300秒,而在锁定的情况下则不到3秒。锁定案例确实花费了将近200(累计)秒来获取所有线程的锁定,但在这种情况下,锁定和增量/减量的总时间仍然较少

发生抖动的原因是,当多个CPU上运行的多个线程正在访问共享内存时(现在几乎每个系统都是这样),硬件必须在每个CPU之间协调对该共享内存的访问,并且当对同一内存(或同一缓存线内的内存)进行多次重复访问时同时,来自不同来源的CPU会花费大量的时间等待对方


当你引入锁时,你要花时间等待锁,但是在锁中,每个线程/CPU都以独占方式访问共享内存,因此没有额外的开销来协调来自多个CPU的同时访问。

您能否详细说明带锁代码如何避免线程和缓存抖动?答案中添加了更多细节。
Value from un-safe threading  6.93944501877 297.449399471  ->  13057
Value from safe threading  4.08318996429 2.6313662529 197.359120131  ->  0