我想在一个线程中分配内存,安全地"借出"指向另一个线程的指针,以便它可以读取该内存。
我使用的高级语言转换为C。高级语言有线程(未指定的线程API,因为它的跨平台 - 见下文)并支持标准的C多线程原语,如原子比较交换,但它并不是真的记录(没有用例)。 这种高级语言的限制是:
现在,对于大型(不想要副本)或可变大小(我认为数组大小是类型的一部分)消息,这是不切实际的。我想发送这样的消息,这里是我想要实现它的大纲:
我需要知道如何在没有数据竞争的情况下确保无效。我的理解是我需要使用内存围栏,但我并不完全确定哪一个(ATOMIC_RELEASE,...)以及循环中的位置(或者如果我需要的话)。
因为我的高级语言需要跨平台,所以我需要得到答案:
pthread_mutex_init
和pthread_mutex_lock
+ pthread_mutex_unlock
InitializeCriticalSection
和EnterCriticalSection
+ LeaveCriticalSection
如果有帮助,我假设以下架构:
并使用以下编译器(您可以假设所有这些编译器的最新版本):
到目前为止,我只在Windows上构建,但是当应用程序完成后,我希望以最少的工作将其移植到其他平台。因此,我试图从一开始就确保跨平台兼容性。
这是我假设的工作流程:
真实代码太大而无法发布。这里简化了(足以显示如何访问共享内存)使用互斥锁的伪代码(如消息队列):
static pointer p = null
static mutex m = ...
static thread_A_buffer = malloc(...)
Thread-A:
do:
// Send pointer to data
int index = findFreeIndex(thread_A_buffer)
// Assume different value (not 42) every time
thread_A_buffer[index] = 42
// Call some "memory fence" here (after writing, before sending)?
lock(m)
p = &(thread_A_buffer[index])
signal()
unlock(m)
// wait for processing
// in reality, would wait for a second signal...
pointer p_a = null
do:
// sleep
lock(m)
p_a = p
unlock(m)
while (p_a != null)
// Free data
thread_A_buffer[index] = 0
freeIndex(thread_A_buffer, index)
while true
Thread-B:
while true:
// wait for data
pointer p_b = null
while (p_b == null)
lock(m)
wait()
p_b = p
unlock(m)
// Call some "memory fence" here (after receiving, before reading)?
// process data
print *p_b
// say we are done
lock(m)
p = null
// in reality, would send a second signal...
unlock(m)
此解决方案有效吗?重新提出问题,线程B打印" 42"? 总是,在所有考虑过的平台和操作系统(pthreads和Windows CS)上? 或者我是否需要添加其他线程原语,例如内存栅栏?
我花了好几个小时看了许多相关的SO问题,并阅读了一些文章,但我还是不完全确定。基于@Art评论,我可能不需要做任何事情。我相信这是基于POSIX标准4.12内存同步的声明:
[...]使用同步线程执行的函数,并使内存与其他线程同步。以下函数使内存与其他线程同步。
我的问题是,这句话并没有明确说明它们是否意味着"所有被访问的内存",或者只有在锁定和解锁之间访问的内存。"我读过人们为这两种情况辩护,甚至有些人暗示它是故意写的,是为了让编译器实现者在实现中有更多的余地!
此外,这适用于pthreads,但我也需要知道它如何应用于Windows线程。
我会根据标准文档中的引号/链接或其他一些高度可靠的来源选择任何答案,证明我不需要围栏或在上述平台配置下显示我需要的 ,适用于Windows / Linux / MacOS案例的至少。如果在这种情况下Windows线程的行为类似于pthreads,我也喜欢这样的链接/引用。
以下是我读过的一些(最好的)相关问题/链接,但是存在冲突信息会让我怀疑我的理解。
答案 0 :(得分:1)
我对C++11
的文档以及C11:n1570.pdf中的类似措辞的审核使我了解以下内容。
如果在线程之间执行某种形式的协作同步,则数据在线程之间可以安全地使用。如果有一个队列,它在一个互斥锁中从队列中读取一个项目,并且如果在保持互斥锁的同时将项目添加到队列中,那么第二个线程中可读的内存将是已写入的内存。第一个帖子。
这是因为不允许编译器和底层CPU基础结构组织通过排序的副作用。
从n1570起
评估如果A与B,A同步,则在评估B之前发生线程间 是在B之前依赖排序,或者,对于某些评估X:
- 与X同步,X在B之前排序,
- 在X和X之间的线程发生在B之前或
之前对A进行排序- 在X和X之间的线程发生在B
之前发生了一个内部线程
因此,为了确保新线程中可见的内存是一致的,那么以下内容将保证结果。
互锁写入会导致线程A上的所有前面的操作被排序,并在线程B看到读取之前进行高速缓存刷新。
将数据写入队列以进行“其他线程处理”后,第一个线程无法安全(解锁)修改或读取对象中的任何内存,直到它知道(通过某种机制)其他线程不再访问数据。只有通过某种同步机制才能看到正确的结果。
C ++和C标准都旨在形式化编译器和CPU的现有行为。因此,虽然使用pthreads和C99标准的正式保证较少,但预计这些保证是一致的。
主题A
int index = findFreeIndex(thread_A_buffer)
这一行存在问题,因为它没有显示任何同步原语。如果findFreeIndex的机制只依赖于线程A写入的内存,那么这将起作用。如果线程B或任何其他线程修改了内存,则需要进一步锁定。
lock(m)
p = &(thread_A_buffer[index])
signal()
unlock(m)
这包含在......
中15如果
,评估A在评估B之前是依赖性排序的- A对原子对象M执行释放操作,并且在另一个线程中,B对M执行消耗操作并读取由A标记的释放序列中的任何副作用写入的值,或
- 对于某些评估X,A在X和X携带a之前是依赖排序的 对B的依赖。
和
18评估A在评估B之前发生,如果A在B或A之间进行排序 发生在B之前。
同步之前的操作“在同步之前发生”,并保证在其他线程中同步后可见。
锁定(获取)和解锁(释放),确保线程A中的信息有完整且对B可见的严格排序。
thread_A_buffer[index] = 42; // happens before
目前内存thread_A_buffer在A上可见,但在B上读取它会导致未定义的行为。
lock(m); // acquire
虽然发布需要,但我看不到收购的任何结果。
p = &thread_A_buffer[index];
unlock(m);
A的所有指令流现在对B可见(由于它与m同步)。
thread_A_buffer[index] = 42; << This happens before and ...
p = &thread_A_buffer[index]; << carries a dependency into p
unlock(m);
A中的所有内容现在都可以被B看到,因为
评估如果A与B同步,A在B之前是依赖顺序的,或者对于某些评估X,则在评估B之前发生线程间的交互。
- 与X同步,X在B之前排序,
- 在X和X之间的线程发生在B之前或
之前对A进行排序- 在X和X之间的线程发生在B之前发生了一个内部线程。
pointer p_a = null
do:
// sleep
lock(m)
p_a = p
unlock(m)
while (p_a != null)
这段代码是完全安全的,读入p_a的值将与另一个线程一起排序,并且在同步写入线程b后将不为空。同样,锁定/解锁会导致严格的排序,确保读取值为写入值。
线程B的所有交互都在一个锁中,所以再次完全安全。
如果A在将对象提供给B之后修改了对象,那么它将不起作用,除非有进一步的同步。
答案 1 :(得分:0)
我也将Nim用于个人项目。 Nim有一个垃圾收集器,你必须避免使用它的内存处理程序使用它的C调用:
https://nim-lang.org/docs/backends.html
在Linux中,malloc使用内部互斥锁来避免并发访问的损坏。我认为Windows也是如此。您可以自由使用内存,但需要避免多个“免费”内容。或访问冲突(您必须保证只有一个线程正在使用内存,并且可以“免费”)。
您提到您使用自定义堆实现。可能可以从其他线程访问此堆,但您必须检查此库是否不会执行“免费”操作。用于由另一个线程处理的指针。如果这个自定义堆实现是Nim的垃圾收集器,那么你必须不惜一切代价避免使用它并执行内存访问的自定义C实现,并使用Nim的C调用内存malloc和free。
答案 2 :(得分:0)
如果你想拥有平台独立性,那么你需要使用多个os和c: