책/CSAPP

CSAPP 12.6 - 12

에린_1 2024. 2. 21. 23:47
728x90

12.6 병렬성을 위해서 쓰레드 이용하기

  • 모든 프로그램의 집합은 중첩되지 않도록 순차적, 동시성 프로그램으로 나눌 수 있다. 순차 프로그램은 단일 논리흐름으로 작성 할 수 있다. 동시성 프로그램은 다수의 동시성 흐름으로 작성할 수 있다. 병렬 프로그램은 다중 프로세서에서 돌아가는 동시성 프로그램이다. 그래서 병렬 프로그램의 집합은 동시성 프로그램 집합의 부분 집합이다.
  • 서로 다른 쓰레드들에 작업을 할당하는 가장 직접적인 접근방법은 이 배열을 t개의 중첩되지 않은 영역으로 나누고, 그 후에 t개의 서로 다른 쓰레드 각각을 자신의 영역에서 동작하도록 할당한다.
  • 메인 쓰레드는 고유의 쓰레드 ID를 각각 피어쓰레드로 전달한다. 각각의 피어쓰레드는 자신의 쓰레드 ID를 사용해서 자신이 작업해야 할 배열의 부분을 결정한다. 이와같이 작은 고유 쓰레드 ID를 피어쓰레드로 보내는 아이디어는 많은 병렬 응용에서 사용되는 일반적인 기술이다.
  • 동기화 오버헤드는 비싸고, 가능하면 피해야한다. 만일 피할 수 없다면, 오버헤드는 가능한 한 유용한 계산만큼 축소되어야 한다.

12.7 다른 동시성의 이슈

12.7.1 쓰레드 안정성

  • 쓰레드로 프로그램 할 때, 쓰레드 안정성이라고 부르는 특성을 가지는 함수를 작성하도록 유의해야한다.
  • 어떤 함수는 다수의 동시성 스레드로부터 반복적으로 호출될 때 항상 정확한 결과를 만드는 경우에만 쓰레드 - 안전 이라고 한다. 만일 어떤 함수가 쓰레드 - 안전하지 못하다면 이것은 쓰레드 - 위험이라고 한다.
  • 쓰레드 - 위험한 함수의 네 가지 클래스
    • 클래스1
      • 공유변수를 보호하지 않는 함수들 이 함수는 보호되지 않은 전역 카운터 변수를 증가시킨다. 이 쓰레드 - 위험한 함수의 클래스는 비교적 쓰레드 - 안전하게 만들기 쉽다 : 공유변수를 P와 V같은 동기화 연산으로 보호한다. 한 가지 장점은 호출하는 프로그램에는 아무 변경도 할 필요가 없다는 것이다. 한 가지 단점은 동기화 연산들이 이 함수를 느리게 할 것이라는 점이다.
    • 클래스2
      • 다중호출에 대해서 상태를 유지하는 함수들 rand 함수는 쓰레드 - 위험한데, 그 이유는 현재 호출의 결과가 이전 반복 실행으로부터의 중간 결과에 의존하기 때문이다.
      • rand 같은 함수를 쓰레드 안전하게 만드는 유일한 방법은 이 함수를 재작성해서 static 데이터를 전혀 사용하지 않도록 하는 것이며, 이를 위해서는 호출자가 상태정보를 인자들로 전달해야한다. 단점은 프로그래머가 이제는 호출하는 루틴에서도 코드를 변경해야 한다는 것이다. 잠재적으로 수 백개의 서로 다른 호출 위치가 존재하는 큰 규모의 프로그램에서 이와 같은 수정을 하는 것은 간단한 일이 아니며 에러가 발생하기 쉽다.
    • 클래스3
      • 정적변수를 가리키는 포인터를 리턴하는 함수. 만일 이러한 함수를 동시성 쓰레드로부터 호출한다면 재앙이 발생할 수 있으며, 그 이유는 한 개의 쓰레드가 사용하는 결과들이 다른 쓰레드에 의해 조용하게 덮어써지기 때문이다.
      • 이 클래스의 쓰레드 - 위험 함수들을 다루는데 두 가지 방법이 있다. 한 가지 옵션은 함수를 다시 작성해서 호출자가 결과를 저장하는 변수의 주소를 전달하는 것이다. 이렇게 하면 모든 공유 데이터를 없앨 수 있지만, 이를 위해서는 프로그래머가 함수의 소스 코드에 접근할 수 있어야 한다.
      • 만일 쓰레드 - 위험 함수가 수정하기 어렵거나 불가능하다면(ex : 코드가 매우 복잡하거나 소스코드가 없는 경우), 다른 옵션은 lock-and-copy 기술을 이용하는 것이다. 기본 아이디어는 뮤텍스를 쓰레드 - 위험 함수와 연계하는 것이다. 각각의 호출 위치에서 뮤텍스를 잠그고, 쓰레드 - 위험 함수를 호출하며, 함수가 리턴한 결과를 사적 메모리 위치로 복사하고, 그 후에 뮤텍스를 풀어준다. 프로그래머는 호출자의 변경을 최소화 하기 위해 lock-and-copy를 수행하는 쓰레드 안전한 래퍼함수를 정의해야 하며, 쓰레드 - 위험 함수로의 모든 호출을 이 래퍼로의 호출로 대체한다.
    • 클래스4
      • 쓰레드 - 위험 함수를 호출하는 함수들. 만일 어떤 함수 f가 쓰레드 - 위험 함수 g를 호출한다면 f는 쓰레드 - 위험한가? → 그때 그때 다르다. 만일 g가 다수의 호출에 걸쳐서 상태에 의존하는 클래스2의 함수라면 f도 쓰레드 위험이며 g를 재시작하는 것 말고는 다른 방도가 없다. 그렇지만 만일 g가 클래스1 또는 클래스3 함수고, 프로그래머가 호출 위치와 다른 결과로 생성되는 공유데이터를 뮤텍스로 보호한다면 f는 여전히 쓰레드 안전이다.

12.7.2 재진입성

  • 재진입(reentrancy) .가능한 함수라고 하는 쓰레드 - 안전 함수의 중요한 클래스가 있으며, 이들은 다수의 쓰레드에 의해 호출될 때 공유데이터는 전혀 참조하지 않는 특성으로 규정된다. 비록 쓰레드 - 안전과 재진입 가능이라는 용어가 유사어로 종종 사용될지라도(부정확하게) 여기에는 준수할 가치가 있는 명확한 기술적 구분이 존재한다.
  • 모든 함수들의 집합은 쓰레드 - 안전과 쓰레드 - 위험 함수들의 중첩되지 않은 집합으로 나누어진다. 재진입가능 함수들의 집합은 쓰레드 - 안전 함수들의 적절한 부분집합이다.
  • 재 진입 가능 함수들은 대개 재진입 불가능 쓰레드 - 안전 함수들보다 더 효율적인데, 그 이유는 이들이 동기화 연산을 필요로 하지 않기 때문이다. 추가로, 클래스2 쓰레드 - 위험 함수를 쓰레드 - 안전 함수로 변환하는 유일한 방법은 이 함수를 다시 작성해서 재진입 가능하게 만드는 것이다.
  • 일부 함수의 코드를 검사하고 사전에 이것이 재진입 가능인지 선언하는 것이 가능할까? 불행하게도 상황에 따라 다르다. 만일 모든 함수 인자가 값으로 전달되고(x포인터), 모든 데이터 참조가 지역 자동 스택 변수들로 이루어진다면 이 함수는 명시적으로 재진입 가능이며, 이것이 우리가 이 함수가 재진입성을 이것이 어떻게 호출되었는지에 관계없이 선언할 수 있다는 의미에서 그렇다.
  • 하지만 만일 우리의 가정을 약간 완화하고, 그렇지 않으면 명시적으로 재진입 가능한 함수에서 일부 매개변수들을 참조 형태로 전달되게 해주며, 그 후에 만일 호출하는 쓰레드들이 포인터를 공유되지 않은 데이터로 조심스럽게 전달하려고 한다면, 이것이 유일하게 재진입 가능하다는 의미에서 간접적으로 재진입 가능 함수를 가진다. 우리는 재진입 가능이라는 용어를 명시적이고 묵시적인 재진입 가능한 함수들 모두를 포함하기 위해서 항상 사용한다. 그렇지만, 재진입성은 때로는 호출자와 피호출자 모두의 특징이며 피호출자만을 위한것은 아니다.

12.7.3 쓰레드 프로그램에서 기존 라이브러리 함수 사용하기

  • 대부분 리눅스 함수들은 표준 C라이브러리에서 정의된 함수를 포함해서 일부 예외를 제외하고는 쓰레드 - 안전하다.
  • 몇 함수는 쓰레드 - 위험한데 rand와 strtok을 제외하고, 이들은 클래스3의 변형된 형태이며, 이들은 정적변수로의 포인터를 리턴한다. 만일 쓰레드를 이용하는 프로그램에서 이중 하나의 함수를 호출할 필요가 있다면, 호출자에게 가장 덜 피해를 주는 방법은 lock-and-copy 방식이다. 그렇지만 lock-and-copy 방식은 많은 단점이 있다. 첫째, 추가적인 동기화 과정 때문에 프로그램 속도가 느려진다. 둘째, 복잡한 구조체의 구조체를 가리키는 포인터를 리턴하는 함수들은 전체 구조체 계층구조를 복사하기 위해서는 구조체 가장 말단까지 복사해야한다. 셋째, lock-and-copy 방식은 호출들에 대해서 정적 상태의 의존하는 rand 같은 클래스2 쓰레드 - 위험 함수들에 대해서는 동작하지 않는다.

12.7.4 경쟁상대

  • 경쟁(race)은 프로그램의 정확성이 다른 쓰레드가 y지점에 도착하기 전에 자신의 제어흐름에서 x지점에 도착하는 하나의 쓰레드에 의존할 때 일어난다. 경쟁은 대개 프로그래머들이 쓰레드가 실행상태 공간을 지나가는 어떤 특정궤적을 따른다고 가정하며, 쓰레드 프로그램이 모든 가능한 궤적에 대해서도 정확하게 동작해야 한다는 불문율을 잊어버리기 때문에 일어난다.

12.7.5 교착상태

  • 세마포어는 교착상태(deadlock)라고 부르는 못된 종류의 잠재적인 런타임 에러를 유발하며, 이것은 다수의 쓰레드가 절대로 참이 될 수 없는 조건을 기다리면서 정지되어있는 경우를 말한다. 진행그래프는 교착상태를 이해하기 위한 소중한 도구다
    • 프로그래머가 P와 V연산을 잘못 배치해서 두 개의 세마포어를 위한 금지된 구역이 겹치게 된다. 만일 일부 실행 궤적이 교착상태 d에 도달하게 되었다면, 더 이상의 진행은 불가능한데, 그 이유는 겹치는 금지 구역이 모든 합법적인 방향으로의 진행을 막기 때문이다.
    • 겹치는 금지구역은 교착구역이라고 부르는 상태들의 집합을 만든다. 만일 어떤 궤적이 교착구역 내의 상태와 닿는다면 교착상태는 피할 수 없다. 궤적들은 교착구역에 진입할 수 는 있지만, 결코 떠날 수는 없다.
    • 교착 상태는 언제나 예측 가능한 것이 아니므로 특히 어렵다. 프로그램이 어떤 머신에서는 잘 돌다가도 다른 머신에서는 교착상태에 빠질 수 있다. 가장 나쁜경우는 서로 다른 실행들이 서로 다른 궤적을 가지기 때문에 에러가 반복되지 않는다는 점이다.
728x90

' > CSAPP' 카테고리의 다른 글

CSAPP 12  (1) 2024.02.24
CSAPP 10  (0) 2024.02.24
CSAPP 12.4 - 12.5  (0) 2024.02.20
CSAPP 12-12.3  (0) 2024.02.19
CSAPP 11  (1) 2024.02.18