gpt4 book ai didi

java - 托管语言是否锁定刷新并重新加载 native 库的变量?

转载 作者:可可西里 更新时间:2023-11-01 18:39:21 28 4
gpt4 key购买 nike

当我们在 C#Java 等托管语言中使用锁时,我们始终可以确保我们处理的是最新数据。

特别是在 Java 内存模型中,它们有一种称为先发生关系 的保证。但我不确定原生库会发生什么情况。

比如说,我有这样的 C 函数:

static int sharedData;      // I'm not declaring this as volatile on purpose here.

void setData(int data) {
sharedData = data; // Not using any mutex or the like.
}

int getData() {
return sharedData;
}

我也有像这样的 C# 代码:

// Thread 1
while( true )
lock( key )
setData( ++i ); // Calling a native C function using P/Invoke.

// Thread 2
while( true )
lock( key )
DoSomeJob( getData() );

如您所见,如果 C 端的 sharedData 没有声明为 volatile,那么是否仍然可以保证 Thread 2总能得到线程1设置的最新值吗?

同样适用于使用 JNIJava 吗?

最佳答案

As you see, if sharedData from C side is not declared as volatile, then is there still a guarantee that Thread 2 can always get the latest value set by Thread 1?

不,将其标记为 volatile 对任何一种线程都没有影响。

Does the same apply to Java using JNI too?

是的,它也适用于 PHP、Lua、Python 和任何其他可以以这种方式引入 C 库的语言。

详细说明您的第一个问题,C 中的 volatile 关键字不用于线程,它用于告诉编译器不要优化该变量。

以下面的代码为例:

#include <stdio.h>
#include <stdbool.h>
#include <limits.h>

static bool run; // = false

void do_run(void)
{
unsigned long v = 1;
while (run) {
if (++v == ULONG_MAX) run = false;
}
printf("v = %lu\n", v);
}

void set_run(bool value)
{
run = value;
}

int main(int argc, char** argv)
{
set_run(true);
do_run();
return 0;
}

启用优化后,编译器可能会看到很多区域以无副作用地删除不必要的代码,然后这样做;例如,编译器可以在 do_run 函数中看到 unsigned long v 将始终是 ULONG_MAX 并选择简单地返回 ULONG_MAX

事实上,当我在上面的代码上运行 gcc -O3 时,这正是发生的事情,do_run 函数立即返回并打印 v = 18446744073709551615

如果您将run 标记为volatile,那么编译器将无法优化该变量,这通常意味着它无法优化具有该变量的代码区域某些方式。

也就是说,当我将 run 更改为 static volatile bool run; 然后使用 gcc -O3 进行编译时,我的程序现在停止等待让循环迭代 18446744073709551615 次。


除此之外,当您调用外部库时,您拥有的唯一线程安全是由该库中使用的语言提供的。

对于 C,您必须在函数中明确指定线程安全。因此,对于您的代码,即使您在托管代码中使用了 lock 上下文,它对托管代码进行锁定,并且 C 代码本身仍然不是线程安全的.

以下面的代码为例:

C代码

static volatile int sharedData;
static volatile bool doRun;
static pthread_t addThread;

void* runThread(void* data)
{
while (doRun) {
++sharedData;
}
return NULL;
}

void startThread(void)
{
doRun = true;
pthread_create(&addThread, NULL, &runThread, NULL);
}

void stopThread(void)
{
doRun = false;
}

void setData(int data)
{
sharedData = data;
}

int getData(void)
{
return sharedData;
}

C#代码

// Thread 1
startThread();
while (true) {
lock (key) {
setData(++i);
}
}

// Thread 2
while (true) {
lock (key) {
i = getData();
}
}
stopThread();

在这段代码中,当 lock (key) 被调用时,您唯一的保证是 i 将在 C# 代码中受到保护。但是,由于 C 代码也在运行一个线程(因为线程 1 调用了 startThread),那么您无法保证 C#代码将被正确同步。

要使 C 代码线程安全,您必须专门添加互斥量或信号量以满足您的需要:

static int sharedData;
static volatile bool doRun;
static pthread_t addThread;
static pthread_mutex_t key;

void* runThread(void* data)
{
while (doRun) {
pthread_mutex_lock(&key);
++sharedData;
pthread_mutex_unlock(&key);
}
return NULL;
}

void startThread(void)
{
doRun = true;
pthread_mutex_init(&key, NULL);
pthread_create(&addThread, NULL, &runThread, NULL);
}

void stopThread(void)
{
doRun = false;
pthread_mutex_lock(&key);
pthread_mutex_unlock(&key);
pthread_mutex_destroy(&key);
}

void setData(int data)
{
pthread_mutex_lock(&key);
sharedData = data;
pthread_mutex_unlock(&key);
}

int getData(void)
{
int ret = 0;
pthread_mutex_lock(&key);
ret = sharedData;
pthread_mutex_unlock(&key);
return ret;
}

通过这种方式,底层库调用得到了适当的保护,并且共享该库内存的任意数量的进程也将是线程安全的。

我应该注意,上面使用 POSIX 进行线程同步,但也可以使用 WinAPI 或 C11 标准互斥锁,具体取决于您的目标系统。

希望对您有所帮助。

关于java - 托管语言是否锁定刷新并重新加载 native 库的变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56787106/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com