Как вы видите, если sharedData
со стороны C не объявлен как энергозависимый, то есть ли еще гарантия, что поток 2 всегда может получить последнее значение, установленное потоком 1?
Нет, и пометить его volatile
никак не влияет на многопоточность.
То же самое относится и к Java с использованием JNI?
Да, и этотакже применимо к 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;
и затем компилирую с использованием gcc -O3
, моя программа теперь останавливается, ожидаячтобы цикл повторялся 18446744073709551615 раз.
Кроме того, когда вы выполняете вызов во внешнюю библиотеку, единственная ваша безопасность потока - это то, что обеспечивается языком, используемым в этой библиотеке.
Для C вы должны явно указать безопасность потоков в функциях.Таким образом, для вашего кода, даже если вы используете контекст lock
в управляемом коде, это only блокировка для управляемого кода, а сам код 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.
Надеюсь, это поможет.