Python 线程锁-何时真正需要?

Python 线程锁-何时真正需要?,python,multithreading,thread-safety,locking,Python,Multithreading,Thread Safety,Locking,我一直在大量使用线程(并行处理)和锁(防止同时操纵共享对象)。由于我以并行线程的极高处理速度编写代码,接收数据并填充共享数据缓冲区,我想知道什么时候真的需要锁 写入共享对象 读取共享对象 更新内容中给定的共享对象 我知道第三种情况主要很关键(考虑到著名的线程和锁的“增量计数器”示例)。但是我应该在其他情况下也使用锁吗 在我的特殊情况下,它是关于一个用作数据缓冲区的数据帧。我想: 向其中添加新数据 从中获取数据 从中删除数据(创建循环缓冲区) 下面的最小工作示例(MWE)显示了这个带有线程

我一直在大量使用线程(并行处理)和锁(防止同时操纵共享对象)。由于我以并行线程的极高处理速度编写代码,接收数据并填充共享数据缓冲区,我想知道什么时候真的需要锁

  • 写入共享对象
  • 读取共享对象
  • 更新内容中给定的共享对象
我知道第三种情况主要很关键(考虑到著名的线程和锁的“增量计数器”示例)。但是我应该在其他情况下也使用锁吗

在我的特殊情况下,它是关于一个用作数据缓冲区的数据帧。我想:

  • 向其中添加新数据
  • 从中获取数据
  • 从中删除数据(创建循环缓冲区)
下面的最小工作示例(MWE)显示了这个带有线程的进程,但为了简单起见,这里按顺序处理,并在进程之间进行密集锁定。虽然这是一种非常谨慎的方法,但我想一些获取/释放锁定步骤可能会被放弃?但由于熊猫在附加它们时正在复制对象,所以我不能100%确定是否要删除这些锁

是否有人对此进行了深入测试,或者有人对此有任何经验


MWE:


输出:

**********1**********
钥匙
0  1.0
********** 2 **********
钥匙
0  1.0
1  2.0
**********提取**********
数据提取
钥匙
0  1.0
数据缓冲区
钥匙
1  2.0
********** 3 **********
钥匙
0  2.0
1  3.0
你选择什么对你来说很重要。执行读取时,是否需要最新的数据。例如,您可以选择在读取数据帧时不包含锁,而在经过一些计算后重新分配数据帧时只包含锁。然而,您似乎需要完全的一致性保证,而您当前的锁定规程可以很好地使用。此外,当从多个线程写入数据时,pandas DataFrame没有内部一致性保证,因此在执行此操作时必须锁定


但是,您还必须知道,
cpython
实现使用了一个或全局解释器锁,在任何给定时间只允许执行一个python“线程”。要获得实际的并行性,您必须使用从
GIL
中解放出来的并行性。由于这一事实,我怀疑上面的代码执行速度是否比在单个线程中运行此操作快

任何时候一个线程可能正在写入一个共享对象,你都需要一个锁。您唯一不需要锁的时候是并发访问只读对象。但这意味着在我的示例中,我将始终需要锁。Process
add_data
通过添加行肯定会写入它,
drop_data
也会通过删除行来操作对象,
get_data
需要更新索引,所以也会修改碎片对象?是,可变数据是导致并发处理困难的原因。是的,我知道“线程”存在
GIL
/
cpython
问题。我处于并行执行不同进程的情况下(在线程中侦听MQTT代理的消息(
MQTT
module循环),将接收到的消息的数据添加到各个线程中的缓冲区,主线程在给定的时间间隔内处理数据)。因为它“伪造”了并行处理,所以我对它没意见。到目前为止,它似乎以0(50毫秒)的间隔跟上了传入消息的速度。但是,我希望减少由于锁定而导致的死区时间,因此尝试减少锁获取/释放过程。
import pandas as pd
import threading

thread_lock = threading.Lock()

df_data_buffer = pd.DataFrame({"key" : []})

def add_data_to_buffer(df_data_ingestion):        
    global df_data_buffer
    thread_lock.acquire()
    df_data_buffer = df_data_buffer.append(df_data_ingestion)
    thread_lock.release()

def get_data_from_buffer(key):
    thread_lock.acquire()
    df_data_buffer.reset_index(inplace=True, drop=True) #required for proper dropping by index
    df_extracted = df_data_buffer.loc[df_data_buffer["key"] == key].copy()
    thread_lock.release()
    drop_data(df_extracted.index)
    return df_extracted

def drop_data_from_buffer(df_index):
    global df_data_buffer
    thread_lock.acquire()
    df_data_buffer.drop(df_index, inplace=True)
    thread_lock.release()
    return True


df_data1 = pd.DataFrame({"key" : [1]})
t_add_data1 = threading.Thread(target=add_data, args=[df_data1])
t_add_data1.start()
t_add_data1.join()
print "*"*10, 1, "*"*10
print df_data_buffer

df_data2 = pd.DataFrame({"key" : [2]})
t_add_data2 = threading.Thread(target=add_data, args=[df_data2])
t_add_data2.start()
t_add_data2.join()
print "*"*10, 2, "*"*10
print df_data_buffer

key=1
df_data_extracted = get_data(key)
print "*"*10, "extract", "*"*10
print "df_data_extracted\n", df_data_extracted
print "df_data_buffer\n", df_data_buffer

print "*"*10, 3, "*"*10
df_data3 = pd.DataFrame({"key" : [3]})
t_add_data3 = threading.Thread(target=add_data, args=[df_data3])
t_add_data3.start()
t_add_data3.join()
print df_data_buffer