У меня есть программа на C++ и программа на Rust, и я успешно заставил их общаться через разделяемую память POSIX (C++ и rust).
Сейчас я пытаюсь их синхронизировать. Мне уже удалось создать работающую, но неэффективную примитивную систему с использованием атомарного лога (создание AtomicBool
на стороне ржавчины вот так).
Однако мне бы очень хотелось использовать мьютекс/условную переменную для синхронизации между потоками, и именно здесь я застрял.
Кажется, я могу инициализировать его сторону C++, следуя этому примеру почти дословно.
Я попытался перевести это непосредственно в ржавчину:
let raw_shm = shm.get_shm();
let mut mtx_attrs = MaybeUninit::<nix::libc::pthread_mutexattr_t>::uninit();
if unsafe { nix::libc::pthread_mutexattr_init(mtx_attrs.as_mut_ptr()) } != 0 {
panic!("failed to create mtx_attrs");
};
let mtx_attrs = unsafe { mtx_attrs.assume_init() };
let mut cond_attrs = MaybeUninit::<nix::libc::pthread_condattr_t>::uninit();
if unsafe { nix::libc::pthread_condattr_init(cond_attrs.as_mut_ptr()) } != 0 {
panic!("failed to create cond_attrs");
};
let cond_attrs = unsafe { cond_attrs.assume_init() };
if unsafe {
nix::libc::pthread_mutexattr_setpshared(
&mtx_attrs as *const _ as *mut _,
PTHREAD_PROCESS_SHARED,
)
} != 0
{
panic!("failed to set mtx as process shared");
};
if unsafe {
nix::libc::pthread_condattr_setpshared(
&cond_attrs as *const _ as *mut _,
PTHREAD_PROCESS_SHARED,
)
} != 0
{
panic!("failed to set cond as process shared");
};
// I know that these offsets are correct, having used `offsetof` on the C++ side
let mtx_start = unsafe { &raw_shm.as_slice()[3110416] };
let mtx = unsafe { &*(mtx_start as *const _ as *const pthread_mutex_t) };
let cond_start = unsafe { &raw_shm.as_slice()[3110440] };
let cond = unsafe { &*(cond_start as *const _ as *const pthread_mutex_t) };
if unsafe {
nix::libc::pthread_mutex_init(&mtx as *const _ as *mut _, &mtx_attrs as *const _ as *mut _)
} != 0
{
panic!("failed to init mtx");
};
if unsafe {
nix::libc::pthread_cond_init(
&cond as *const _ as *mut _,
&cond_attrs as *const _ as *mut _,
)
} != 0
{
panic!("failed to init cond");
};
Все это проходит с возвращаемыми значениями 0... пока все хорошо.
Теперь я могу проверить это одним из двух способов:
if (pthread_mutex_lock(&shmp->mutex) != 0)
throw("Error locking mutex");
if (pthread_cond_wait(&shmp->condition, &shmp->mutex) != 0)
throw("Error waiting for condition variable");
и в ржавчине:
let sig = unsafe { nix::libc::pthread_cond_signal(&cond as *const _ as *mut _) };
dbg!(sig);
Несмотря на возврат 0
(т. е. успех), моя программа на C++ не выходит после condvar; он остается в ожидании, как если бы он никогда не получал сигнала.
for (unsigned int count = 0;; count++) {
if (pthread_cond_signal(condition) != 0)
throw("Error")
// sleep for a bit
}
а затем в ржавчине, что-то вроде:
loop {
if unsafe { nix::libc::pthread_mutex_lock(&mtx as *const _ as *mut _) } > 0 {
panic!("Failed to acquire lock")
};
if unsafe {
nix::libc::pthread_cond_wait(&cond as *const _ as *mut _, &mtx as *const _ as *mut _)
} > 0
{
panic!("Failed to acquire lock")
};
}
Делая это таким образом, вызов блокировки мьютекса выполняется успешно, но я получаю EINVAL
на pthread_cond_wait
определенный здесь, который я не могу исправить...
Я чувствую, что я близок ... есть мысли о том, как заставить это работать? (это в основном просто доказательство концепции).
Для потомков мне удалось заставить это работать.
Чтобы прояснить, как устроена программа, есть два бинарных файла: один C++ и один rust. Программа Rust порождает программу C++, используя std::process::Command
.
Обработка ошибок и импорт опущены для краткости.
Блок общей памяти имеет следующую структуру:
#[repr(c)]
struct SharedMemoryLayout {
ready: std::sync::atomic::AtomicBool,
mutex: libc::pthread_mutex_t,
condition: libc::pthread_cond_t,
}
Блоки общей памяти инициализируются нулями, поэтому ready
всегда будет false
с самого начала.
std::process::Command::spawn
, а затем ждет в цикле, пока ready
не станет true
.let proc = Command::new("/path/to/c++/binary").spawn().unwrap();
let ptr: *mut u8 = // pointer to first byte of shared memory block;
let ready: &AtomicBool = unsafe { &*(ptr as *mut bool as *const AtomicBool) };
loop {
if ready.load(Ordering::SeqCst) {
break
} else {
thread::sleep(Duration::from_secs(1));
}
}
struct SharedMemoryLayout
{
std::atomic_bool ready;
pthread_mutex_t mutex;
pthread_cond_t condition;
};
int fd = shm_open("name_of_shared_memory_block", O_RDWR, S_IRUSR | S_IWUSR);
struct SharedMemoryLayout *sync = (SharedMemoryLayout *)mmap(NULL, sizeof(*sync), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
mmap
и mutex
, прежде чем пометить блок памяти как готовый.pthread_mutexattr_t mutex_attributes;
pthread_condattr_t condition_attributes;
pthread_mutexattr_init(&mutex_attributes);
pthread_condattr_init(&condition_attributes);
pthread_mutexattr_setpshared(&mutex_attributes, PTHREAD_PROCESS_SHARED);
pthread_condattr_setpshared(&condition_attributes, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&sync->mutex, &mutex_attributes);
pthread_cond_init(&sync->condition, &condition_attributes);
pthread_mutexattr_destroy(&mutex_attributes);
pthread_condattr_destroy(&condition_attributes);
std::atomic_bool *ready = &syncp->ready;
ready->store(true);
А затем введите цикл сигнализации по условию:
for (unsigned int count = 0;; count++) {
// do something
sleep(1);
pthread_cond_signal(&sync->condition);
}
let mutex = unsafe {ptr.offset(4) as *mut pthread_mutex_t};
let condition = unsafe {ptr.offset(32) as *mut pthread_cond_t};
И теперь мы можем ждать условия, получая уведомление от программы C++.
loop {
unsafe {
pthread_mutex_lock(mutex);
pthread_cond_wait(condition, mutex);
pthread_mutex_unlock(mutex);
// Do something
}
}
Мой комментарий будет не очень полезен. Честно говоря, я думаю только о том, что это не лучшая идея. Хотя искренне желаю удачи.