만약 여러 프로세스가 하나의 스크립트를 실행한다고 하자. 그런데 해당 스크립트가 공유 자원을 R/W하여, 스크립트를 Atomic하게 수행해야 한다면 어떻게 해야할까?
flock은 util-linux 에 포함된 터미널용 locking 메커니즘 명령도구이다.
https://github.com/util-linux/util-linux/blob/master/sys-utils/flock.c
test용 임의 파일 생성
asdf 는 여러 프로세스가 동시에 수정할 파일이다.
$ touch asdf
script 생성 lock_test.sh
아래와 같이 작성하면 Critical Section을 만들 수 있다.
#! /bin/bash
LOCK_FILE="./asdf.lock" # lock file 이름 지정
exec 200>$LOCK_FILE # 파일이 생성되고 "file descriptor 번호"가 지정됨
flock -x 200 || exit 1 # fd 200 으로 lock 얻기
# ----- critical section begin -----
# do something
echo "$1 - begin" >> asdf
sleep 5
echo "$1 - end" >> asdf
# ----- critical section end -----
flock -u 200 # fd 200의 lock 해제
exec 200>&- # fd 200 해제
flock -x 외 에 추가 옵션이 없다면 다른 프로세스는 lock이 해제될때 까지 기다린다. 그리고 -u 로 락을 해제할수 있다. 만약 -n
을 사용한다면 락이 이미 잡혀있을때 기다리지 않고 종료한다. -w 초 를 지정하면 지정된 초만 기다리고 종료한다.
-n
, --nonblock fail rather than wait-x
, --exclusive get an exclusive lock (default)-w
, --timeout wait for a limited amount of time-u
, --unlock remove a lock이 스크립트가 여러 프로세스로 하여금 동시에 실행되어도 asdf파일에는 동일한 번호에 대해 begin과 end가 나란히 write될 것이다.
exec 200>$LOCK_FILE
에 대하여:
여기서 exec 200
이 명령의 의미는 200
이라는 임의의 번호를 파일 디스크립터 번호로 사용한다는 의미. exec 200>file.name
은 file.name
이라는 파일을 파일 디스크립터 번호 200
으로 open해서 해당 파일에 쓰기작업을 수행할수 있도록 하는것. 그뒤에 200이라는 파일디스크립터로 해당 파일에 접근할 수 있다. 이 동작은 C언어에서 fopen()
을 사용하는것과 동일 하다.
이후 터미널에서 아래의 명령을 수행하면 fd 200으로 해당파일의 스트림이 생성된것을 알수 있다.
$ ls -l /proc/$$/fd
total 0
lrwx------ 1 jihuun jihuun 64 8월 3 07:12 0 -> /dev/pts/22
lrwx------ 1 jihuun jihuun 64 8월 3 07:12 1 -> /dev/pts/22
lrwx------ 1 jihuun jihuun 64 8월 3 07:12 2 -> /dev/pts/22
l-wx------ 1 jihuun jihuun 64 8월 3 12:04 200 -> /home/jihuun/Downloads/temp/asdf.lock
더 정확히 말하면 200>file.name
은 fd번호 200인 File Stream 의 출력을 file.name에 쓰는것이다. 참고로 echo "1234" > file.name
은 >
를 사용하는데 >
는 1>
과 같다 1은 stdout 이라는 표준 Stream의 file descriptor번호이다. 따라서 echo “1234”가 stdout에 출력하는 내용을 stdout이 아니라 file.name에 쓰라는 의미다.
그렇다면 이후에 만약 200이라는 file descriptor만을 가지고 파일을 읽고 쓰려면 어떻게 하면 될까? echo "hello" >&200
과 같이 >&200 으로 redirection 하면 fd번호 200인 파일에 hello 를 write할 수 있다. 이것은 stdout (fd 번호가 1)을 200으로 redirect한다는 의미이다. 참고로 쉘스크립트에서 빈번히 사용되는 2>&1
는 stderr (fd번호가 2)를 1, 즉 stdout 으로 redirect하라는 의미다. stream redirect 문법은 fd번호1 >& fd번호2
이다.
할당된 스트림을 쉘에서 해제하기 위한 명령은 exec 200>&-
이다.
Stream과 Redirection이 익숙치 않거나 더 궁금하다면 내가 작성한 글 All That Stream 을 참고하기 바란다.
만약 아래와 같이 flock을 사용하지 않고 동일한 파일에 write를 한다면 결과는?
no_lock_test.sh
#! /bin/bash
echo "$1 - begin" >> asdf2
sleep 5
echo "$1 - end" >> asdf2
test
for i in {1..20}; do bash no_lock_test.sh $i & done
bash <cmd> &
로 새로운 bash shell 20개를 백그라서운드에서 실행시켜 각각의 스크립트가 동일한 파일인 asdf 을 쓰도록 했다. 각각의 스크립트는 $i의 번호를 write한다.
결과
해당 스크립트의 critical section이 전혀 보장되지 못했다.
$ cat asdf2
1 - begin
2 - begin
3 - begin
5 - begin
4 - begin
9 - begin
8 - begin
11 - begin
6 - begin
13 - begin
10 - begin
14 - begin
7 - begin
18 - begin
20 - begin
16 - begin
15 - begin
12 - begin
17 - begin
19 - begin
2 - end
5 - end
3 - end
1 - end
8 - end
4 - end
13 - end
9 - end
10 - end
6 - end
11 - end
7 - end
20 - end
16 - end
18 - end
14 - end
15 - end
17 - end
12 - end
19 - end
$ for i in {1..20}; do bash lock_test.sh $i & done
$ cat asdf
1 - begin
1 - end
4 - begin
4 - end
12 - begin
12 - end
13 - begin
13 - end
8 - begin
8 - end
16 - begin
16 - end
3 - begin
3 - end
6 - begin
6 - end
19 - begin
19 - end
11 - begin
11 - end
2 - begin
2 - end
14 - begin
14 - end
9 - begin
9 - end
7 - begin
7 - end
17 - begin
17 - end
20 - begin
20 - end
18 - begin
18 - end
15 - begin
15 - end
10 - begin
10 - end
5 - begin
5 - end
각각의 서로다른 프로세스에서 실행된 스크립트가 모두 동일한 파일인 asdf에 원하는대로 write동작을 수행했다는것을 알수있다. 동일한 번호에 대해 begin과 end가 나란히 write되었다.
정보에 감사드립니다.