стандартный вызов Linux fcntl не предоставляет опцию тайм-аута. Я рассматриваю возможность реализации блокировки тайм-аута с сигналом.
Вот описание блокировки блокировки:
F_SETLKW
Эта команда должна быть эквивалентна F_SETLK, за исключением того, что если общая или монопольная блокировка блокируется другими блокировками, поток должен ожидать, пока запрос не будет удовлетворен. Если сигнал, который должен быть перехвачен, получен, пока функция fcntl() ожидает область, функция fcntl() должна быть прервана. По возвращении из обработчика сигнала функция fcntl() должна вернуть -1 с errno, установленным в [EINTR], и операция блокировки не должна выполняться.
Итак, какой сигнал мне нужно использовать, чтобы указать, что блокировка должна быть прервана? И поскольку в моем процессе запущено несколько потоков, я хочу прервать только этот поток ввода-вывода, который блокируется для блокировки файла, другие потоки не должны быть затронуты, но сигнал находится на уровне процесса, я не уверен, как справиться эта ситуация.
Я написал простую реализацию с использованием сигнала.
int main(int argc, char **argv) {
std::string lock_path = "a.lck";
int fd = open(lock_path.c_str(), O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
if (argc > 1) {
signal(SIGALRM, [](int sig) {});
std::thread([](pthread_t tid, unsigned int seconds) {
sleep(seconds);
pthread_kill(tid, SIGALRM);
}, pthread_self(), 3).detach();
int ret = file_rwlock(fd, F_SETLKW, F_WRLCK);
if (ret == -1) std::cout << "FAIL to acquire lock after waiting 3s!" << std::endl;
} else {
file_rwlock(fd, F_SETLKW, F_WRLCK);
while (1);
}
return 0;
}
запустив ./main, а затем ./main a, я ожидаю, что первый процесс удержит блокировку навсегда, а второй процесс попытается получить блокировку и прервется через 3 секунды, но второй процесс только что завершился.
Может ли кто-нибудь сказать мне, что не так с моим кодом?





Лучшим решением может быть использование Выбрать():
https://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html
#include <errno.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/time.h> int input_timeout (int filedes, unsigned int seconds) { fd_set set; struct timeval timeout; /* Initialize the file descriptor set. */ FD_ZERO (&set); FD_SET (filedes, &set); /* Initialize the timeout data structure. */ timeout.tv_sec = seconds; timeout.tv_usec = 0; /* select returns 0 if timeout, 1 if input available, -1 if error. */ return TEMP_FAILURE_RETRY (select (FD_SETSIZE, &set, NULL, NULL, &timeout)); } int main (void) { fprintf (stderr, "select returned %d.\n", input_timeout (STDIN_FILENO, 5)); return 0; }
позволяет ли select() ждать блокировки записи?
Даже если бы select() позволял ждать блокировки записи (а я не думаю, что это так), это не достигло бы цели OP блокировка файла. И это не просто вопрос вызова fcntl() после того, как все конфликтующие блокировки очищены, потому что этому присуще состояние гонки - какой-то другой процесс может снять конфликтующую блокировку между select() и fcntl(), а затем именно то, что пытается OP избежать происходит в любом случае.
So what kind of signal I need to use to indicate the lock to be interrupted?
Наиболее очевидным выбором сигнала будет SIGUSR1 или SIGUSR2. Они предоставляются для определенных пользователем целей.
Также есть SIGALRM, что было бы естественно, если вы используете таймер, выдающий такой сигнал, для ведения хронометража, и который имеет смысл даже генерировать программно, если вы не используете его для других целей.
And since there're multiple threads running in my process, I only want to interrupt this IO thread who is blokcing for the file lock, other threads should not be affected, but signal is process-level, I'm not sure how to handle this situation.
Вы можете доставить сигнал выбранному потоку в многопоточном процессе с помощью функции pthread_kill(). Это также хорошо подходит для случая, когда несколько потоков одновременно ожидают блокировки.
С обычным kill() у вас также есть альтернатива: заставить все потоки блокировать выбранный сигнал (sigprocmask()), а затем сделать так, чтобы поток пытался разблокировать его непосредственно перед этим. Когда выбранный сигнал доставляется процессу, поток, который в данный момент не блокирует его, получит его, если такой поток доступен.
Это предполагает, что обработчик сигнала уже настроен для обработки выбранного сигнала (он не должен ничего делать), и что номер сигнала для использования доступен через символ LOCK_TIMER_SIGNAL. Он обеспечивает желаемое поведение тайм-аута в качестве функции-оболочки вокруг fcntl() с командой F_SETLKW, как описано в вопросе.
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/syscall.h>
// glibc does not provide a wrapper function for this syscall:
static pid_t gettid(void) {
return syscall(SYS_gettid);
}
/**
* Attempt to acquire an fcntl() lock, with timeout
*
* fd: an open file descriptor identifying the file to lock
* lock_info: a pointer to a struct flock describing the wanted lock operation
* to_secs: a time_t representing the amount of time to wait before timing out
*/
int try_lock(int fd, struct flock *lock_info, time_t to_secs) {
int result;
timer_t timer;
result = timer_create(CLOCK_MONOTONIC,
& (struct sigevent) {
.sigev_notify = SIGEV_THREAD_ID,
._sigev_un = { ._tid = gettid() },
// note: gettid() conceivably can fail
.sigev_signo = LOCK_TIMER_SIGNAL },
&timer);
// detect and handle errors ...
result = timer_settime(timer, 0,
& (struct itimerspec) { .it_value = { .tv_sec = to_secs } },
NULL);
result = fcntl(fd, F_SETLKW, lock_info);
// detect and handle errors (other than EINTR) ...
// on EINTR, may want to check that the timer in fact expired
result = timer_delete(timer);
// detect and handle errors ...
return result;
}
Это работает, как и ожидалось для меня.
try_lock изменять расположение выбранного ею сигнала.timer_* предоставляют интервальные таймеры POSIX, но возможность назначения определенного потока для получения сигналов от такого таймера специфична для Linux.-lrt для функций timer_*.struct sigevent Glibc не соответствует его собственной документации (по крайней мере, в относительно старой версии 2.17). В документах утверждается, что у struct sigevent есть член sigev_notify_thread_id, но на самом деле его нет. Вместо этого у него есть недокументированное объединение, содержащее соответствующий член, и он предоставляет макрос для устранения разницы, но этот макрос не работает как указатель члена в назначенном инициализаторе.fcntl блокировки работают для каждого процесса. Таким образом, разные потоки одного и того же процесса не могут исключать друг друга с помощью такой блокировки. Более того, разные потоки одного и того же процесса могут изменять fcntl() блокировки, полученные через другие потоки, без каких-либо особых усилий или уведомления любого из потоков.fcntl() вернет EINTR, если будет прерван сигналом Любые, который не завершает поток. Поэтому вы можете захотеть использовать обработчик сигнала, который устанавливает утвердительный флаг для каждого потока, с помощью которого вы можете убедиться, что фактический сигнал таймера был получен, чтобы повторить попытку блокировки, если она была прервана другим сигналом.EINTR.спасибо за ответ, я написал простую реализацию, основанную на вашей идее, но она работает не так, как я ожидал. Не могли бы вы взглянуть на мой добавленный код и посмотреть, что происходит не так? Спасибо!
@ZiqiLiu, мне не очевидно, что не так с вашей попыткой, и я не могу исключить, что проблема в функции file_rwlock, которую вы не представили. Но пример реализации, который я добавил к этому ответу, работает так, как я ожидаю, в тесте, построенном по тем же принципам, что и ваш.
Ваш добавленный код — это C++, а не C. Если вы действительно ищете ответ о C++, пометьте вопрос соответствующим образом — ответы для C++, скорее всего, будут отличаться от ответов для C.