flock 으로 shell script 를 atomic하게 실행하기

숲사람·2023년 8월 3일
0

UNIX & C

목록 보기
12/12

배경

만약 여러 프로세스가 하나의 스크립트를 실행한다고 하자. 그런데 해당 스크립트가 공유 자원을 R/W하여, 스크립트를 Atomic하게 수행해야 한다면 어떻게 해야할까?

Flock 사용

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.namefile.name이라는 파일을 파일 디스크립터 번호 200으로 open해서 해당 파일에 쓰기작업을 수행할수 있도록 하는것. 그뒤에 200이라는 파일디스크립터로 해당 파일에 접근할 수 있다. 이 동작은 C언어에서 fopen() 을 사용하는것과 동일 하다.

      이후 터미널에서 아래의 명령을 수행하면 fd 200으로 해당파일의 스트림이 생성된것을 알수 있다.

      $ ls -l /proc/$$/fd
      total 0
      lrwx------ 1 jihuun jihuun 64  83 07:12 0 -> /dev/pts/22
      lrwx------ 1 jihuun jihuun 64  83 07:12 1 -> /dev/pts/22
      lrwx------ 1 jihuun jihuun 64  83 07:12 2 -> /dev/pts/22
      l-wx------ 1 jihuun jihuun 64  83 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 을 참고하기 바란다.

동작 테스트 1

만약 아래와 같이 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

flock 동작 테스트

$ for i in {1..20}; do bash lock_test.sh $i & done
  • test 결과
    $ 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되었다.
profile
기록 & 정리 아카이브 용도 (보다 완성된 글은 http://soopsaram.com/documentudy)

1개의 댓글

comment-user-thumbnail
2023년 8월 3일

정보에 감사드립니다.

답글 달기