java - 托管语言是否锁定本机库的刷新和重新加载变量?
问题描述
当我们在C#和Java等托管语言中使用锁时,我们始终可以确保我们处理的是最新数据。
特别是在 Java 内存模型中,它们有一个称为Happens-before 关系的保证。但我不确定原生库会发生什么。
说,我有这样的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() );
如您所见,如果sharedData
从C端未声明为volatile
,那么是否仍然可以保证Thread 2始终可以获取Thread 1设置的最新值?
这同样适用于使用JNI的Java吗?
解决方案
如您所见,如果
sharedData
C 端没有声明为 volatile,那么是否仍然可以保证线程 2 始终可以获取线程 1 设置的最新值?
不,标记它对volatile
线程没有影响。
这同样适用于使用 JNI 的 Java 吗?
是的,它也适用于 PHP、Lua、Python 和任何其他可以以这种方式引入 C 库的语言。
为了详细说明您的第一个问题,volatile
C 中的关键字不用于线程,它用于告诉编译器不要优化该变量。
以下面的代码为例:
#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;
}
打开优化后,编译器可能会看到很多区域删除不必要的代码而不会产生副作用,并且这样做;例如,编译器可以看到它unsigned long v
总是ULONG_MAX
在do_run
函数中,并选择简单地返回ULONG_MAX
。
事实上,当我gcc -O3
在上面的代码上运行时,这正是发生的情况,do_run
函数立即返回并打印v = 18446744073709551615
。
如果要标记run
为volatile
,则编译器无法优化该变量,这通常意味着它无法以某些方式优化具有该变量的代码区域。
也就是说,当我更改run
为static volatile bool run;
然后编译 usinggcc -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 标准互斥锁。
我希望这能有所帮助。
推荐阅读
- android - 在Android中计算当前月份的星期日数
- node.js - 如何将数据从本地代码同步到heroku git
- android-camera - 如何在android的recyclerview适配器中使用onActivityResult?
- laravel - Laravel & Vuejs mixins:如何将新值赋予共享变量?
- mysql - 如何从数据库mysql中选择特定列中具有相同和最新值的所有行
- mysql - 无法浏览 phpmyadmin 和网址?
- php - Laravel - 即时更改电子邮件设置不起作用
- java - 将现有的 android 支持转换为 androidX
- c# - 使用 ssis 编码后将 .dat 文件写入 .txt 文件
- sql-server-2012 - 查找缺失的设置