我正在尝试使用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
请帮助我理解为什么锁定方式更快!
答案 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的同时访问。