标准linux fcntl
调用不提供超时选项。我正在考虑用信号实现超时锁定。
这是阻止锁的描述:
F_SETLKW
该命令应等效于F_SETLK,不同之处在于,如果共享锁或排他锁被其他锁阻塞,则线程应等待,直到请求可以满足为止。 如果在fcntl()等待某个区域时接收到要捕获的信号,则fcntl()将被中断。从信号处理程序返回后,fcntl()应以errno返回-1。设置为[EINTR],则锁定操作将不会执行。
那么我需要使用哪种信号来表示要中断的锁?而且,由于我的进程中正在运行多个线程,所以我只想中断占用大量文件锁的IO线程,其他线程不应受到影响,但是信号是进程级的,我不确定如何处理这种情况。
我用信号写了一个简单的象征。
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秒后中断,但是第二个进程刚刚终止。
谁能告诉我我的代码出了什么问题?
答案 0 :(得分:1)
那么我需要使用哪种信号来表示要锁定 被打扰了吗?
最明显的信号选择是SIGUSR1
或SIGUSR2
。提供这些是为了满足用户定义的目的。
还有SIGALRM
,如果您使用的计时器会产生这样的信号来进行计时,那将是很自然的,只要您不使用它,即使通过编程方式生成也很有意义它用于其他目的。
由于有多个线程正在运行 进程中,我只想中断正在忙碌的IO线程 文件锁定,其他线程不应该受到影响,但是信号是 流程级别,我不确定该如何处理。
您可以通过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
不符合其自己的文档(至少在相对较旧的版本2.17中)。文档声称struct sigevent
有一个成员sigev_notify_thread_id
,但实际上没有。取而代之的是,它具有一个未记录的联合,其中包含对应的成员,并且它提供了一个宏来弥补这一差异-但该宏不能在指定的初始化程序中充当成员指示符。fcntl
锁基于每个进程进行操作。因此,同一进程的不同线程无法通过这种锁定相互排斥。此外,同一进程的不同线程可以修改通过其他线程获得的fcntl()
锁,而无需付出任何特殊努力或向任何一个线程发出任何通知。fcntl()
被未终止线程的任何信号中断,将返回EINTR
。因此,您可能想使用一个设置了每个线程肯定标志的信号处理程序,通过该标志程序可以验证是否接收到实际的计时器信号,以便在该锁被其他信号中断时重试该锁定。EINTR
锁定失败的情况下时间实际上已到期。答案 1 :(得分:0)
更好的解决方案可能是使用select():
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; }