Существующую разделяемую блокировку можно преобразовать в эксклюзивную (и наоборот), выполнив еще один вызов flock() и указав соответствующее значение для аргумента operation. Эта процедура заблокируется, если другой процесс уже удерживает разделяемую блокировку для данного файла (разве что было задано значение LOCK_NB — тогда этого не произойдет).
Атомарность операции преобразования блокировки
Вызов flock() хоть и не является частью стандарта SUSv3, но поддерживается в большинстве реализаций UNIX. Некоторые из них требуют подключения заголовочного файла
Применение вызова flock() демонстрируется в листинге 51.1. Эта программа блокирует файл и, перейдя в режим сна на заданное количество секунд, разблокирует его обратно. Она принимает три аргумента командной строки. Первый из них представляет файл, подлежащий блокировке. Второй определяет ее тип (разделяемая или эксклюзивная) и необходимость включения флага LOCK_NB (неблокирующий режим). Третий аргумент обозначает количество секунд, которое должно пройти между блокировкой и разблокировкой; он не является обязательным и по умолчанию равен 10 секундам.
Листинг 51.1. Использование вызова flock()
filelock/t_flock.c
#include
#include
#include "curr_time.h" /* Объявление currTime() */
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int fd, lock;
const char *lname;
if (argc < 3 || strcmp(argv[1], "-help") == 0 ||
strchr("sx", argv[2][0]) == NULL)
usageErr("%s file lock [sleep-time]\n"
" 'lock' is 's' (shared) or 'x' (exclusive)\n"
" optionally followed by 'n' (nonblocking)\n"
" 'sleep-time' specifies time to hold lock\n", argv[0]);
lock = (argv[2][0] == 's')? LOCK_SH: LOCK_EX;
if (argv[2][1] == 'n')
lock |= LOCK_NB;
fd = open(argv[1], O_RDONLY); /* Открываем файл, который нужно заблокировать */
if (fd == -1)
errExit("open");
lname = (lock & LOCK_SH)? "LOCK_SH": "LOCK_EX";
printf("PID %ld: requesting %s at %s\n", (long) getpid(), lname, currTime("%T"));
if (flock(fd, lock) == -1) {
if (errno == EWOULDBLOCK)
fatal("PID %ld: already locked — bye!", (long) getpid());
else
errExit("flock (PID=%ld)", (long) getpid());
}
printf("PID %ld: granted %s at %s\n", (long) getpid(), lname, currTime("%T"));
sleep((argc > 3)? getInt(argv[3], GN_NONNEG, "sleep-time"): 10);
printf("PID %ld: releasing %s at %s\n", (long) getpid(),
lname, currTime("%T"));
if (flock(fd, LOCK_UN) == -1)
errExit("flock");
exit(EXIT_SUCCESS);
}
filelock/t_flock.c
С помощью программы из листинга 51.1 можно выполнить ряд экспериментов, помогающих изучить поведение вызова flock(). Отдельные примеры показаны в следующей сессии командной строки. Сначала создадим файл, после чего запустим экземпляр нашей программы, который будет находиться в фоне, удерживая блокировку на протяжении 60 секунд:
$ touch tfile
$ ./t_flock tfile s 60 &
[1] 9777
PID 9777: requesting LOCK_SH at 21:19:37
PID 9777: granted LOCK_SH at 21:19:37
Теперь запустим еще один экземпляр той же программы, который успешно запрашивает и снимает разделяемую блокировку:
$ ./t_flock tfile s 2
PID 9778: requesting LOCK_SH at 21:19:49
PID 9778: granted LOCK_SH at 21:19:49
PID 9778: releasing LOCK_SH at 21:19:51
Но если запустить еще один экземпляр данной программы, который попытается получить эксклюзивную блокировку в неблокирующем режиме, то его запрос будет немедленно отклонен:
$ ./t_flock tfile xn
PID 9779: requesting LOCK_EX at 21:20:03
PID 9779: already locked — bye!
При попытке получить эксклюзивную блокировку в блокирующем режиме программа будет заблокирована. Ее запрос будет удовлетворен только после того, как фоновый процесс, уснувший на 60 секунд, снимет свою блокировку:
$ ./t_flock tfile x
PID 9780: requesting LOCK_EX at 21:20:21
PID 9777: releasing LOCK_SH at 21:20:37
PID 9780: granted LOCK_EX at 21:20:37
PID 9780: releasing LOCK_EX at 21:20:47
51.2.1. Семантика наследования и снятия блокировок