被执行的代码比未执行的代码要花费更多的时间

时间:2018-11-28 18:12:11

标签: python multithreading python-2.7 locking

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

在ThreadUnsafeClass中,在递增和递减之前我没有使用锁。

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

我的假设是,由于锁定将迫使某些线程等待,因此在ThreadSafeClass的情况下应该花费更多时间。

结果表明ThreadSafeClass更快。

这是我的代码(python 2.7)

import threading
import time


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):
    time.sleep(0.1)
    for i in xrange(0, self.count_tot):
        ThreadUnsafeClass.ref += 1

def dec_ref(self):
    time.sleep(0.1)
    for i in xrange(0, self.count_tot):
        ThreadUnsafeClass.ref -= 1


def compute_ref_value(self):
    start_time = time.time()
    for i in xrange(0, 50):
        t1 = threading.Thread(target=self.inc_ref, args=())
        t2 = threading.Thread(target=self.dec_ref, args=())
        t1.start()
        t2.start()
        self.all_threads.append(t1)
        self.all_threads.append(t2)

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

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

    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):
    time.sleep(0.1)
    self.lock.acquire()
    for i in xrange(0, self.count_tot):
        ThreadUnsafeClass.ref += 1
    self.lock.release()

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


def compute_ref_value(self):
    start_time = time.time()
    for i in xrange(0, 50):
        t1 = threading.Thread(target=self.inc_ref, args=())
        t2 = threading.Thread(target=self.dec_ref, args=())
        t1.start()
        t2.start()
        self.all_threads.append(t1)
        self.all_threads.append(t2)

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

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

    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()

这是我的结果:

来自不安全线程的值3.54868483543-> 30653

安全线程的值2.28372502327-> 0

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

1 个答案:

答案 0 :(得分:1)

我相信答案是,通过锁定代码的方式,您实际上避免了线程和缓存的混乱,从而避免了线程和缓存的混乱,因为每个线程的循环可以完成而无需任何其他硬件资源争用。这实际上不是一个苹果对苹果的比较,而是通过将锁移到循环中而不是在循环之外进行的:

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的同时访问。