동기화를 위한 동기

Choose a study mode

Play Quiz
Study Flashcards
Spaced Repetition
Chat to Lesson

Podcast

Play an AI-generated podcast conversation about this lesson
Download our mobile app to listen on the go
Get App

Questions and Answers

공유 데이터에 대한 동시(병렬) 접근은 어떤 문제를 야기할 수 있습니까?

데이터의 비일관성(data inconsistency)을 야기할 수 있습니다.

운영체제에서 구축되는 더 높은 수준의 동기화 기본 요소(primitive)에는 어떤 것들이 있습니까? (세 가지 이상)

모니터(Monitors), 락(Locks), 세마포어(Semaphores), 조건 변수(Condition Variables) 등이 있습니다.

동시성(concurrency) 제어에서 락(Lock)의 기본적인 역할은 무엇입니까?

임계 구역(critical section)이 단일 원자적 명령어(single atomic instruction)처럼 실행되도록 보장하는 것입니다.

락 변수(lock variable)가 가질 수 있는 두 가지 상태는 무엇이며, 각각 무엇을 의미합니까?

<ol> <li>사용 가능(available / unlocked / free): 락을 아무 스레드도 보유하고 있지 않은 상태.</li> <li>획득됨(acquired / locked / held): 정확히 하나의 스레드가 락을 보유하고 있으며, 해당 스레드가 임계 구역에 있는 상태.</li> </ol> Signup and view all the answers

락 구현의 목표 중 '정확성(Correctness)'을 구성하는 세 가지 요소는 무엇입니까?

<p>상호 배제(Mutual Exclusion), 진행(Progress, deadlock-free), 한정된 대기(Bounded waiting, starvation-free)</p> Signup and view all the answers

임계 구역 보호를 위해 단순히 인터럽트를 비활성화하는(disabling interrupts) 방법은 현대의 멀티프로세서 시스템에서도 효과적인 해결책이다.

<p>False (B)</p> Signup and view all the answers

단순히 플래그(flag)를 사용하여 락을 구현하려는 첫 시도(First attempt)에서 발생하는 주요 문제점 두 가지는 무엇입니까?

<ol> <li>상호 배제(Mutual Exclusion) 실패: 두 개 이상의 스레드가 동시에 임계 구역에 진입할 수 있는 경쟁 상태(race condition) 발생 가능.</li> <li>스핀 대기(Spin-waiting)로 인한 자원 낭비: 락을 얻을 때까지 CPU 시간을 계속 소모하며 대기.</li> </ol> Signup and view all the answers

피터슨 알고리즘(Peterson's Algorithm)은 어떤 상황을 위한 상호 배제 알고리즘이며, 주요 특징은 무엇입니까?

<p>정확히 두 개의 스레드(tid=0, 1)가 하드웨어의 특별한 지원 없이, 기본적인 로드(load)와 스토어(store) 연산만으로 임계 구역에 대한 상호 배제를 달성하는 알고리즘입니다.</p> Signup and view all the answers

원자적 연산(Atomic operation)이란 무엇입니까?

<p>실행 중간에 다른 명령어에 의해 중단되거나 간섭받지 않는, 하나의 논리적 단위로 실행되는 연산입니다.</p> Signup and view all the answers

TestAndSet (또는 xchg) 원자적 명령어를 사용하여 스핀락(spinlock)을 어떻게 구현할 수 있습니까? (acquire 함수의 핵심 로직)

<p><code>while (TestAndSet(&amp;lock-&gt;flag, 1) == 1);</code> 와 같이 <code>TestAndSet</code> 함수를 반복 호출합니다. 이 함수는 <code>flag</code>의 현재 값을 반환하고 원자적으로 <code>flag</code>를 1로 설정합니다. 반환된 값이 1이면 (즉, 락이 이미 잠겨 있으면) 계속 반복(spin)하고, 0이면 (락이 풀려 있었으면) 반복을 멈추고 락을 획득한 것이 됩니다.</p> Signup and view all the answers

CompareAndSwap(int *ptr, int expected, int new) 원자적 명령어는 어떻게 동작합니까?

<p>먼저 <code>*ptr</code>의 현재 값이 <code>expected</code> 값과 같은지 비교합니다. 같으면 <code>*ptr</code>의 값을 <code>new</code> 값으로 업데이트하고 성공(주로 true 또는 이전 값)을 반환합니다. 같지 않으면 값을 변경하지 않고 실패(주로 false 또는 현재 값)를 반환합니다. 이 모든 과정은 원자적으로 수행됩니다.</p> Signup and view all the answers

로드 연결(Load-Linked, LL)과 저장 조건(Store-Conditional, SC) 명령어 쌍은 어떻게 락 구현에 사용됩니까?

<p><code>LoadLinked</code>로 메모리 주소(<code>&amp;lock-&gt;flag</code>)의 값을 읽어옵니다. 이후 <code>StoreConditional</code>을 사용하여 해당 주소에 새로운 값(예: 1)을 쓰려고 시도합니다. <code>StoreConditional</code>은 <code>LoadLinked</code> 이후 해당 메모리 주소에 다른 쓰기 작업이 없었을 경우에만 성공하며 값을 쓰고 1을 반환합니다. 만약 중간에 다른 쓰기가 있었다면 실패하고 0을 반환합니다. 락 획득 시에는 <code>StoreConditional</code>이 성공할 때까지 LL과 SC를 반복합니다.</p> Signup and view all the answers

POSIX 스레드(Pthreads) 라이브러리에서 상호 배제를 제공하기 위해 사용되는 락의 이름은 무엇입니까?

<p><code>pthread_mutex_t</code> (또는 간단히 뮤텍스, mutex)</p> Signup and view all the answers

TestAndSet이나 CompareAndSwap을 사용한 기본적인 스핀락은 공정성(Fairness)을 보장한다.

<p>False (B)</p> Signup and view all the answers

FetchAndAdd(int *ptr) 원자적 명령어는 어떤 작업을 수행합니까?

<p><code>*ptr</code>이 가리키는 메모리의 현재 값을 읽어서 반환하고, 동시에 해당 메모리의 값을 원자적으로 1 증가시킵니다.</p> Signup and view all the answers

티켓 락(Ticket Lock)은 어떻게 스레드 간의 공정성(fairness)을 보장합니까?

<p>각 스레드는 락을 획득하기 위해 원자적인 <code>FetchAndAdd</code> 연산을 사용하여 고유한 '티켓' 번호를 받습니다. 락은 현재 서비스할 '턴(turn)' 번호를 유지합니다. 스레드는 자신의 티켓 번호가 현재 턴 번호와 일치할 때까지 기다립니다. 락을 해제할 때 턴 번호를 1 증가시켜 다음 티켓 번호를 가진 스레드가 진행할 수 있게 하므로, 요청 순서대로 락을 획득하게 됩니다.</p> Signup and view all the answers

티켓 락(Ticket Lock)에서 락을 기다리는 스레드는 자신의 티켓 번호(myturn)가 락의 현재 턴 번호(lock->turn)와 _____ 때까지 스핀합니다.

<p>같아질</p> Signup and view all the answers

스핀락(Spin lock)의 주요 비효율성은 무엇이며, 이를 피하기 위해 일반적으로 무엇이 필요합니까?

<p>주요 비효율성은 스레드가 락을 기다리면서 CPU 시간을 아무 작업도 하지 않고 계속 소모(스핀)한다는 점입니다. 이를 피하기 위해서는 운영체제(OS)의 지원이 필요하며, 스레드를 대기 상태로 전환(sleep/block)시키는 메커니즘을 사용합니다.</p> Signup and view all the answers

스핀 대기(Spin-waiting) 대신 스레드를 잠들게(sleep) 하는 방식의 장점은 무엇입니까?

<p>스레드가 락을 기다리는 동안 CPU 자원을 소모하지 않고 다른 유용한 작업을 수행할 수 있도록 CPU를 양보(yield)하게 됩니다.</p> Signup and view all the answers

리눅스(Linux)에서 효율적인 락 구현을 위해 제공하는, 솔라리스(Solaris)의 park/unpark와 유사한 메커니즘은 무엇입니까?

<p>퓨텍스(Futex, Fast Userspace Mutex)</p> Signup and view all the answers

리눅스 퓨텍스(futex)는 사용자 공간(userspace)의 _____ 정수와 커널 공간(kernelspace)의 _____ _____ 를 결합한 구조입니다.

<p>원자적 (atomic) / 대기 큐 (wait queue)</p> Signup and view all the answers

스핀락(Spinlock)은 어떤 조건에서 빠른 성능을 보입니까?

<p>CPU 코어가 여러 개 있고(many CPUs) 락이 매우 짧은 시간 동안만 유지될 때(locks held a short time) 빠릅니다.</p> Signup and view all the answers

스핀락(Spinlock)은 어떤 조건에서 느린 성능을 보입니까?

<p>CPU 코어가 하나이거나(one CPU) 락이 오랜 시간 동안 유지될 때(locks held a long time) 느립니다.</p> Signup and view all the answers

스핀락 구현에서 순수하게 스핀하는 대신 yield()를 호출하여 CPU를 양보하는 것은 높은 경합(high contention) 상황에서의 모든 성능 문제를 해결한다.

<p>False (B)</p> Signup and view all the answers

멀티프로세서(Multiprocessor) 환경에서 락을 기다릴 때, 스핀 대기(Spin-wait)와 블로킹(Blocking) 중 어떤 방식을 선택하는 것이 이상적입니까? (락 유지 시간 't'와 컨텍스트 스위치 비용 'C'를 사용하여 설명)

<p>락이 해제될 때까지의 예상 시간(t)이 컨텍스트 스위치 비용(C)보다 짧다면 (t &lt; C), 스핀 대기하는 것이 유리합니다. 반대로 예상 시간(t)이 컨텍스트 스위치 비용(C)보다 길다면 (t &gt; C), 스레드를 블로킹(재우는 것)하는 것이 유리합니다.</p> Signup and view all the answers

이단계 대기(Two-Phase Waiting 또는 Two-Phase Locks) 전략은 어떻게 동작합니까?

<p>두 단계로 구성됩니다. 첫 번째 단계에서는 락을 얻기 위해 잠시 동안(보통 컨텍스트 스위치 비용 C와 비슷한 시간) 스핀 대기를 시도합니다. 이 단계에서 락을 얻지 못하면, 두 번째 단계로 넘어가 스레드를 대기 큐에 넣고 잠들게(block) 합니다. 락이 해제되면 나중에 깨어나게 됩니다.</p> Signup and view all the answers

램포트의 베이커리 알고리즘(Bakery Algorithm by Lamport)은 어떤 공정성(fairness) 속성을 제공하며, 이를 어떻게 달성합니까?

<p>선입선출(First-Come-First-Serve, FCFS) 공정성을 제공합니다. 각 스레드는 임계 구역에 진입하기 전에 '번호(number)' 또는 '레이블(label)'을 받습니다. 스레드는 다른 모든 스레드보다 낮은 번호를 가질 때까지 기다린 후 임계 구역에 진입합니다. 번호가 같은 경우 스레드 ID를 사용하여 순서를 정하는 사전식 순서(lexicographic order)를 사용합니다.</p> Signup and view all the answers

Signup and view all the answers

Flashcards

동기화의 동기

공유 데이터에 대한 병렬 접근은 일관성 없는 데이터를 만들 수 있습니다.

잠금(Locks)

임계 영역이 단일 원자적 명령처럼 실행되는 것을 보장합니다.

잠금 변수

잠금이 사용 가능한지 또는 획득되었는지 나타내는 변수입니다.

잠금() 의미

잠금을 획득하려고 시도합니다. 잠금을 획득하면 임계 영역에 들어갑니다.

Signup and view all the flashcards

상호 배제

뮤텍스 잠금은 한 번에 하나의 스레드만 임계 영역에 있도록 합니다.

Signup and view all the flashcards

진행 (교착 상태 없음)

잠금이 무기한으로 지연되지 않도록 합니다.

Signup and view all the flashcards

공정성

각 스레드는 대략 동일한 시간 동안 기다립니다.

Signup and view all the flashcards

성능

CPU가 불필요하게 사용되지 않습니다.

Signup and view all the flashcards

잠금 구축

하드웨어 지원을 사용하여 상호 배제를 제공합니다.

Signup and view all the flashcards

인터럽트 비활성화

단일 프로세서 시스템의 상호 배제를 위한 초기 솔루션입니다.

Signup and view all the flashcards

플래그 사용

공유 변수가 설정되었는지 여부를 나타내는 플래그를 사용합니다.

Signup and view all the flashcards

스핀 대기

스레드가 다른 스레드를 기다리면서 시간을 낭비합니다.

Signup and view all the flashcards

test-and-set 명령어

플래그를 동시에 테스트하고 설정합니다.

Signup and view all the flashcards

Peterson의 알고리즘

두 스레드 간의 상호 배제를 위한 소프트웨어 기반 솔루션입니다.

Signup and view all the flashcards

Peterson 알고리즘 목표

하드웨어 지원 없이 atomic critical section을 보장합니다.

Signup and view all the flashcards

유한 대기

각 프로세스는 최대 하나의 임계 섹션을 기다립니다.

Signup and view all the flashcards

Peterson 알고리즘 문제

최신 하드웨어에서 작동하지 않습니다(캐시 일관성 문제).

Signup and view all the flashcards

원자적 작업

인터럽트 간의 코드, 로드 및 스토어

Signup and view all the flashcards

instruction

단순 잠금 생성을 지원하는 명령어입니다.

Signup and view all the flashcards

TestAndSet 특징

ptr이 가리키는 오래된 값을 반환합니다. ptr이 가리키는 기존 값을 새 값으로 동시에 업데이트합니다.

Signup and view all the flashcards

Compare-And-Swap

주소의 값을 예상 값과 비교합니다.

Signup and view all the flashcards

Load-Linked 상태

Store-conditional은 간헐적 스토어가 없는 경우에만 성공합니다.

Signup and view all the flashcards

뮤텍스 이름

POSIX 라이브러리에서 잠금 메커니즘에 사용하는 이름입니다.

Signup and view all the flashcards

공정성

각 스레드가 같은 시간 동안 기다립니다.

Signup and view all the flashcards

성능

CPU가 불필요하게 사용되지 않습니다.

Signup and view all the flashcards

스핀락 공정성

기본 스핀 잠금이 공정하지 않습니다.

Signup and view all the flashcards

인출 후 더하기

특정 주소에서 값을 원자적으로 증가시키고 이전 값을 반환합니다.

Signup and view all the flashcards

티켓 잠금

인출 후 더하기로 구축 가능합니다.

Signup and view all the flashcards

많은 스피닝

코드에 여전히 스핀 대기가 있습니다. 일부 경우에 비효율적일 수 있습니다.

Signup and view all the flashcards

양보

CPU를 포기하면 다른 스레드가 실행할 수 있습니다.

Signup and view all the flashcards

큐 사용

스레드를 절전 모드로 전환하는 큐를 사용합니다.

Signup and view all the flashcards

Setpark()

Solaris는 이러한 루틴 호출로 파크할 것임을 나타낼 수 있습니다.

Signup and view all the flashcards

Futex

Linux는 futex를 제공합니다.

Signup and view all the flashcards

차단 잠금 구현

잠금 구현이 schedular 준비 큐에서 대기 스레드를 제거합니다.

Signup and view all the flashcards

대기 대 차단

각 접근 방식은 다른 상황에서 더 좋습니다.

Signup and view all the flashcards

스핀 대기

가정상에 기초를 두는 행동

Signup and view all the flashcards

2단계 대기

최악의 경우의 성능을 묶으십시오. 실제/최적의 비율은 언제 최악의 가능한 성능이 발생합니까?

Signup and view all the flashcards

2단계 잠금

잠금이 해제될 시점을 실현합니다.

Signup and view all the flashcards

필터 잠금

코딩 스타일

Signup and view all the flashcards

베이커리 란포트 알고리즘

코딩 표준

Signup and view all the flashcards

Study Notes

동기화를 위한 동기

  • 프로세스는 병렬적으로 실행될 수 있음
    • 언제든지 인터럽트를 받을 수 있고 부분적으로만 완료될 수 있음
  • 공유 데이터에 대한 동시 접근은 데이터 불일치를 일으킬 수 있음
  • OS에는 높은 수준의 동기화 기본 요소들이 구축되어 있음
    • 스레드 간 명령어의 올바른 순서를 보장하는 작업
  • 동기화 기본 요소의 예는 다음과 같음
    • 모니터, 잠금, 세마포어, 조건 변수
    • 로드, 저장, Test&Set, 인터럽트 비활성화

Locks: 기본 아이디어

  • 임계 영역이 단일한 원자적 명령어인 것처럼 실행되도록 보장
    • 공유 변수의 정규 업데이트의 예
      • balance = balance + 1;
  • 임계 영역 주위에 일부 코드 추가
    • lock_t mutex; // 전역적으로 할당된 '뮤텍스' 잠금
    • lock(&mutex);
    • balance = balance + 1;
    • unlock(&mutex);

Locks: 기본 아이디어

  • 잠금 변수는 잠금의 상태를 유지
    • 사용 가능(또는 잠금 해제됨 또는 비어 있음)
      • 스레드가 잠금을 보유하지 않음
    • 획득됨(또는 잠김 또는 보류됨)
      • 정확히 하나의 스레드가 잠금을 보유하고 아마도 임계 영역에 있음

lock()의 의미

  • lock()
    • 잠금을 획득하려고 시도
    • 다른 스레드가 잠금을 보유하지 않으면 스레드가 잠금을 획득
    • 임계 영역으로 들어감
      • 이 스레드를 잠금의 소유자라고 함
    • 잠금을 보유한 첫 번째 스레드가 그 안에 있는 동안 다른 스레드는 임계 영역에 들어오지 못함

잠금 구현 목표

  • 정확성
    • 상호 배제
      • 한 번에 임계 영역에 스레드가 하나만 있음
    • 프로그레스 (교착 상태 없음)
      • 여러 동시 요청이 있는 경우 하나가 진행되도록 허용해야 함
    • 범위 지정 (기아 없음)
      • 각 대기 스레드가 결국 들어가도록 허용해야 함
  • 공정성
    • 각 스레드가 동일한 시간 동안 대기함
  • 성능
    • CPU가 불필요하게 사용되지 않음 (예: 스피닝)

잠금 구축

  • 효율적인 잠금은 낮은 비용으로 상호 배제를 제공
  • 잠금을 구축하려면 하드웨어와 OS의 도움이 필요

인터럽트 제어

  • 임계 영역에 대한 인터럽트 비활성화
    • 상호 배제를 제공하는 데 사용되는 가장 초기의 솔루션 중 하나
    • 단일 프로세서 시스템용으로 발명됨
      • void lock() { DisableInterrupts(); }
      • void unlock() { EnableInterrupts(); }
  • 문제점
    • 응용 프로그램에서 너무 많은 신뢰가 필요
      • 탐욕스럽거나 악의적인 프로그램이 프로세서를 독점할 수 있음
    • 멀티프로세서에서 작동하지 않음
    • 인터럽트 마스크 또는 언마스크 코드가 최신 CPU에서 느리게 실행됨

하드웨어 지원이 필요한 이유는 무엇입니까?

  • 첫 번째 시도: 잠금이 보류되었는지 여부를 나타내는 플래그 사용
    • 코드에 문제가 있음
      • typedef struct __lock_t { int flag; } lock_t;
      • void init (lock t *mutex) {mutex->flag = 0;} //락이 사용 가능하면 0, 잠금되면 1
      • void lock (lock t *mutex) {while (mutex->flag == 1) ; // 테스트 플래그 // spin-wait (아무 것도 안 함) mutex->flag = 1; // 이제 설정하세요! }
      • void unlock (lock t *mutex) {mutex->flag = 0;}

하드웨어 지원이 필요한 이유는 무엇입니까? (계속)

  • 문제 1: 상호 배제 없음 (플래그=0이라고 가정)
    • 스레드 1
      • call lock ()
      • while (flag == 1)
      • interrupt: 스레드 2로 전환
      • flag = 1; // 플래그를 1로 설정!(너무!)
    • 스레드 2
      • call lock ()
      • while (flag == 1)
      • flag = 1;
      • interrupt: 스레드 1로 전환
  • 문제 2: 스핀 대기 시간은 다른 스레드를 기다리는 데 시간을 낭비
  • 솔루션: 하드웨어에서 지원하는 원자적 명령어가 필요!
    • test-and-set 명령어, 원자적 교환으로도 알려져 있음

Peterson's Algorithm

  • 하드웨어 지원 없이 임계 영역을 원자적으로 보장할 수 있는 알고리즘 방법
  • 두 개의 스레드만 해당 (tid = 0, 1)
  • 로드 및 스토어만 사용 (원자적 명령어)
    • 1개의 고급 언어가 여러 개의 명령어로 나뉘지 않음
      • int turn = 0; // 공유
      • Boolean lock[2] = {false, false};
      • Void acquire() { lock[tid] = true; turn = 1-tid; while (lock[1-tid] && turn == 1-tid) /* wait */; }
      • Void release() { lock[tid] = false; }

Different Cases: All Work

  • 스레드 0만 잠금을 원함
    • Lock[0]= true;
    • turn = 1;
    • while (lock[1] && turn ==1);
    • LOCK AQUIRED !
    • Lock[0]=false;
  • 스레드 1만 잠금을 원함
    • Lock[1] = true;
    • turn = 0;
    • while (lock[0] && turn ==0);
    • LOCK AQUIRED !
    • Lock[1]=false;

Different Cases: All Work

  • 스레드 0과 스레드 1이 모두 잠금을 원함;
    • Lock[0] = true;
    • turn = 1;
    • while (lock [1] && turn ==1);
    • LOCK AQUIRED !
    • Lock[0]=false;
    • Lock[1] = true;
    • turn = 0;
    • while (lock [0] && turn == 0)
    • LOCK AQUIRED !
    • Lock[1]=false;

Peterson's Algorithm: 직관

  • 상호 배제: 다음 경우에만 임계 영역에 들어감
    • 다른 스레드가 들어오기를 원하지 않음
    • 다른 스레드가 들어오기를 원하지만 차례가 되면
  • 프로그레스: 두 스레드 모두 while() 루프에서 영원히 대기할 수 없음
    • 다른 프로세스가 들어오기를 원하지 않는 경우 완료 다른 프로세스 (매칭 turn)는 결국 완료됨
  • 범위 지정 대기(예제에 표시되지 않음)
    • 각 프로세스는 최대 하나의 임계 영역에서 대기
  • 문제점
    • 최신 하드웨어에서 작동하지 않음 (캐시 일관성 문제)
    • 3개 이상의 스레드에서 작동하지 않음

동기화 구현

  • 구현하려면 원자적 작업이 필요
  • 원자적 작업: 다른 명령어를 인터리브할 수 없음
  • 원자적 작업의 예
    • 유니프로세서에서 인터럽트 사이의 코드
      • 타이머 인터럽트 비활성화, I/O를 수행하지 않음
    • 단어의 로드 및 저장
      • Load r1, B
      • Store rl, A
    • 특별 HW 명령어
      • Test&Set
      • Compare&Swap

Test And Set (Atomic Exchange, XCHG)

  • 간단한 잠금 생성을 지원하는 명령어
    • int TestAndSet (int *ptr, int new) {
    • int old = *ptr; // ptr에서 이전 값 가져오기
    • *ptr = new; // 'new'를 ptr에 저장
    • return old; // 이전 값을 반환
    • }
  • ptr이 가리키는 이전 값을 반환(테스트)
  • 동시에 해당 값을 새로 업데이트(설정)
  • 이 작업 시퀀스는 원자적으로 수행됨

XCHG를 사용한 LOCK 구현

  • typedef struct lock_t { int flag; } lock_t;
  • void init(lock t *lock) { lock->flag = 0; }
  • void acquire(lock_t *lock) { while(xchg(&lock->flag, 1) == 1) ; // spin-wait (아무 것도 안 함) }
  • void release (lock t *lock) { lock->flag = 0; }

Compare-And-Swap

  • 주소 (ptr)의 값이 예상 값과 같은지 테스트
    • 같으면 ptr이 가리키는 메모리 위치를 새 값으로 업데이트
  • 어느 경우든 해당 메모리 위치에서 실제 값을 반환
    • int CompareAndSwap (int *ptr, int expected, int new) {
    • int actual = *ptr;
    • if (actual == expected) *ptr = new;
    • return actual;
    • }
  • Compare-and-Swap 하드웨어 원자적 명령어(C 스타일)
    • void lock (lock t *lock) {
    • while (CompareAndSwap(&lock->flag, 0, 1) == 1) ; // 스핀
    • }
  • Compare-and-swap을 사용한 스핀 잠금

Compare-And-Swap (컨트)

  • compare-and-swap의 C 호출 가능 x86 버전
    • char CompareAndSwap (int *ptr, int old, int new) {
      • unsigned char ret;
      • // Note that sete sets a 'byte' not the word
      • asm volatile (
      • " lock\n"
      • " cmpxchgl %2,%1\n"
      • " sete %0\n"
      • : "=q" (ret), "=m" (*ptr)
      • : "r" (new), "m" (*ptr), "a" (old)
      • : "memory");
      • return ret;
    • }

Load-Linked and Store-Conditional

  • int LoadLinked(int *ptr) { return *ptr; }
  • int StoreConditional (int *ptr, int value) {
    • if (no one has updated *ptr since the LoadLinked to this address) {
    • *ptr = value;
    • return 1; // 성공!
    • } else {
    • return 0; // 업데이트 실패
    • }
  • }
  • Load-linked And Store-conditional
    • 저장 조건은 주소에 간헐적인 저장이 발생하지 않는 경우에만 성공
      • 성공: 1을 반환하고 ptr에서 value로 값을 업데이트
      • 실패: ptr의 값이 업데이트되지 않고 O가 반환

Load-Linked and Store-Conditional (Cont.)

  • void lock (lock_t *lock) {
    • while (1) {
    • while (LoadLinked(&lock->flag) == 1) ;
    • if (StoreConditional(&lock->flag, 1) == 1)
    • return;
    • }
  • }
  • void unlock (lock_t *lock) { lock->flag = 0; }
  • LL/SC를 사용하여 잠금 구축
  • void lock (lock t *lock) {
    • while (LoadLinked(&lock->flag) || !StoreConditional(&lock->flag, 1)) ;
    • }
  • LL/SC를 사용한 lock ()의 더 간결한 형식

Pthread Locks - mutex

  • 앞의 명령어를 프로그래머가 바로 갖다 쓰기에는 제한적
  • POSIX 라이브러리에서 잠금에 사용하는 이름
    • 스레드 간 상호 배제 제공에 사용
      • pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
      • Pthread_mutex_lock(&lock); // pthread_mutex_lock()용 wrapper
      • balance = balance + 1;
      • Pthread_mutex_unlock(&lock);
  • 다양한 변수를 보호하기 위해 다른 잠금을 사용할 수 있음 -> 동시성 증가 (더 세분화된 접근 방식)

잠금 구현 목표

  • 정확성
    • 상호 배제
      • 한 번에 임계 영역에 스레드가 하나만 있음
    • 프로그레스 (교착 상태 없음)
      • 여러 동시 요청이 있는 경우 하나가 진행되도록 허용해야 함
    • 범위 지정 (기아 없음)
      • 각 대기 스레드가 결국 들어가도록 허용해야 함
  • 공정성
    • 각 스레드가 동일한 시간 동안 대기함
  • 성능
    • CPU가 불필요하게 사용되지 않음

기본 스핀락은 공정하지 않음

  • 스케줄러는 잠금/잠금 해제와 무관함

Fetch-And-Add

  • 특정 주소에서 이전 값을 반환하면서 값을 원자적으로 증가
    • int FetchAndAdd (int *ptr) {
    • int old = *ptr;
    • *ptr = old + 1;
    • return old;
    • }
  • Fetch-And-Add 하드웨어 원자적 명령어(C 스타일)

Ticket Lock (1)

  • 티켓 잠금은 fetch-and add로 구축할 수 있음
    • 모든 스레드에 대한 프로그레스 보장 -> 공정성
      • typedef struct lock_t {
      • int ticket;
      • int turn;
      • } lock t;
      • void lock_init (lock t *lock) {
      • lock->ticket = 0;
      • lock->turn = 0;
      • }
      • void lock (lock t *lock) {
      • int myturn = FetchAndAdd(&lock->ticket);
      • while (lock->turn < myturn)
      • ; // 스핀
      • }
      • void unlock(lock_t *lock) {
      • FetchAndAdd(&lock->turn);
      • }

Ticket Lock (2)

  • 티켓 잠금은 fetch-and add로 구축할 수 있음
    • 모든 스레드에 대한 프로그레스 보장 -> 공정성
      • typedef struct lock_t {
      • int ticket;
      • int turn;
      • } lock t;
      • void lock init(lock t *lock) {
      • lock->ticket = 0;
      • lock->turn = 0;
      • }
      • void lock(lock t *lock) {
      • int myturn = FetchAndAdd(&lock->ticket);
      • while (lock->turn != myturn)
      • ; // 스핀
      • }
      • void unlock (lock t *lock) {
      • FetchAndAdd(&lock->turn);
      • }

Ticket Lock (3)

  • 잠금 해제에도 FAA가 필요함??
    • typedef struct lock_t {
    • int ticket;
    • int turn;
    • } lock t;
    • void lock_init(lock_t *lock) {
    • lock->ticket = 0;
    • lock->turn = 0;
    • }
    • void lock (lock t *lock) {
    • int myturn = FetchAndAdd(&lock->ticket);
    • while (lock->turn != myturn)
    • ; // 스핀
    • }
    • void unlock (lock t *lock) {
    • &lock->turn++; /* no other thread can touch it ! */
    • }

너무 많은 회전

  • 하드웨어 기반 스핀 잠금은 간단하고 작동함
  • 일부 경우에는 이러한 솔루션이 매우 비효율적일 수 있음
    • 스레드가 spinning 상태가 되면 전체 시간 조각을 가치를 확인하는 것 외에는 아무 것도 하지 않는 데 낭비

스핀 잠금 평가

  • 정확성: 확인
    • 스핀 잠금은 단일 스레드만 임계 영역에 진입할 수 있도록 허용
  • 공정성: 아니요
    • 스핀 잠금은 공정성을 보장하지 않음
    • 실제로 스레드 spinning이 영원히 돌 수 있음
  • 성능:
    • 단일 CPU에서는 성능 오버헤드가 매우 고통스러울 수 있음
    • 스레드 수가 CPU 수와 대략 동일한 경우 스핀 잠금이 상당히 잘 작동

간편한 접근 방식: Yield만 하기

  • spinning 상태로 유지하려는 경우 CPU를 다른 스레드에 제공
    • OS 시스템 호출은 호출자를 실행 상태에서 준비 상태로 이동
  • 컨텍스트 전환의 비용이 상당할 수 있으며 기아 문제가 여전히 존재
    • void init() { flag = 0; }
    • void lock() {
    • while (TestAndSet(&flag, 1) == 1)
    • yield(); // CPU를 제공하세요
    • }
    • void unlock() { flag = 0; }
  • Test-and-set 및 yield가 있는 잠금

큐 사용: 스핀 대신 sleep

  • 잠금을 입력하기 위해 기다리는 스레드를 추적하는 큐
  • park()
    • 호출 스레드를 sleep 상태로 전환
  • unpark(threadID)
    • threadID로 지정된 특정 스레드를 깨우기

큐 사용: 스핀 대신 sleep

  • typedef struct lock_t { int flag; int guard; queue_t *q; } lock_t;
  • void lock init(lock_t *m) {
    • m->flag = 0;
    • m->guard = 0;
    • queue_init(m->q);
  • }
  • void lock (lock_t *m) {
    • while (TestAndSet(&m->guard, 1) == 1)
    • ; // 스핀으로 가드 잠금 획득
    • if (m->flag == 0) {
    • m->flag = 1; // 잠금 획득됨
    • m->guard = 0;
    • } else {
    • queue_add (m->q, gettid());
    • m->guard = 0;
    • park();
  • }
  • }
  • 큐, Test-and-set, Yield 및 wakeup이 있는 잠금

큐 사용: 스핀 대신 sleep

  • void unlock (lock_t *m) {
    • while (TestAndSet(&m->guard, 1) == 1)
    • ; // 스핀으로 가드 잠금 획득
    • if (queue_empty(m->q)) m->flag = 0; // 잠금을 release; 원하는 사람이 없으면
    • else unpark (queue_remove (m->q)); // 다음 스레드에 대한 hold lock (for next thread!)
    • m->guard = 0;
  • }
  • 큐, Test-and-set, Yield 및 wakeup이 있는 잠금 (Cont.)

Wakeup/waiting race

  • 잠금 release가 park() 호출 직전인 경우 (스레드 B) -> 스레드 B는 (잠재적으로) 영원히 sleep 상태로 유지됨
  • Solaris는 setpark()라는 세 번째 시스템 호출을 추가하여 이 문제를 해결
    • 이 루틴을 호출하면 스레드가 park()에 대해 표시할 수 있음
    • 인터럽트가 발생하고 다른 스레드가 park 가 실제로 호출되기 전에 unpark 를 호출하는 경우 후속 park는 sleep 대신 즉시 반환
      • queue_add (m->q, gettid());
      • setpark(); // 신규 코드
      • m->guard = 0;
      • park();
  • lock () 내의 코드 수정

Futex

  • Linux는 futex를 제공 (Solaris의 park 및 unpark와 유사)
    • futex_wait(address, expected)
      • *address == expected인 경우 호출 스레드를 sleep 상태로 전환
      • 그렇지 않으면 호출은 값 EAGAIN으로 즉시 반환
    • futex_wake(address)
      • 큐에서 대기 중인 스레드를 깨우기

Futex

  • futex는 사용 공간의 atomic 정수에 연결된 커널 공간 대기 큐로 구성.
  • 여러 프로세스 또는 스레드가(서로 간섭하지 않도록 원자적 연산을 사용하여) 사용자 공간에서 정수 전체에서 작동
  • 대기 큐에서 연산을 요청하도록 비싼 시스템 호출에 resorting하는 것만으로, 또는 대기 큐에 현재 프로세스를 넣습니다
  • 적절하게 프로그래밍 Futex 기반 잠금 대기 중 lock가 없을 때를 제외하고 시스템 호출이 사용되지는 않습니다: 이러한 작업의 대부분은 프로세스 간 arbitration 필요하지 않기 때문에, 대부분의 경우 일어나기가 쉽지 않을 것입니다

Futex (컨트)

  • nptl 라이브러리에서 lowlevellock.h의 스니펫
    • 정수 v의 높은 비트: 잠금이 보관되는지 아닌지 추적
    • 다른 모든 비트: 대기자의 수
      • void mutex_lock (int *mutex) {
      • int v;
      • /* Bit 31 was clear, we got the mutex (this is the fastpath) */
      • if (atomic_bit_test_set (mutex, 31) == 0)
      • return;
      • atomic_increment (mutex);
      • while (1) {
      • if (atomic_bit_test_set(mutex, 31) == 0) {
      • atomic_decrement (mutex);
      • return;
      • }
      • /* We have to wait now. First make sure the futex value
      • we are monitoring is truly negative (i.e. locked). */
      • v = *mutex;
      • ...
  • Linux 기반 Futex 잠금

Futex (컨트)

  • if (v >= 0) /Checking again that lock is still unavailable/
    • continue;
    • futex wait (mutex, v); /* mutex == v (!0)인 경우 sleeps */
    • }
    • }
    • void mutex_unlock(int *mutex) {
    • /* Adding 0x80000000 to the counter results in 0 if and only if
    • there are not other interested threads: 0x80 + 0x80 = 0x00 */
    • if (atomic add zero (mutex, 0x80000000))
    • return;
    • /* There are other threads waiting for this mutex,
    • wake one of them up */
    • futex wake (mutex);
    • }
  • Linux 기반 Futex 잠금 (Cont.)

잠금 평가

  • 잠금 구현이 좋은지 확인하는 방법
  • 공정성:
    • 프로세스가 요청된 것과 동일한 순서로 잠금을 획득합니까?
  • 성능
    • 두 가지 시나리오:
      • 낮은 경합 (스레드 수가 더 적고 일반적으로 잠금이 사용 가능)
      • 높은 경합(CPU당 스레드 수가 많고 각 CPU가 경합)

스핀 잠금 성능

  • 빠를 때...
    • 다수의 CPU
    • 잠금이 짧은 시간 동안 보관
    • advantage: 컨텍스트 스위치를 피합니다
  • 느릴 때...
    • 하나의 CPU
    • 잠금이 긴 시간 동안 보관
    • disadvantage: 스핀이 비생산적

스핀 잠금 성능

  • no yield: A B C D A B C D
  • yield: A A B
  • Yield가 유용한 이유는 무엇입니까?
  • Yield가 모든 성능 문제를 해결하지 못하는 이유는 무엇입니까?

스핀 잠금 성능

  • Waste...
    • Yield 없음: O(스레드 * time_slice)
    • Yield 있음: O(스레드 * context_switch)
  • 따라서 Yield가 있더라도 높은 경합에서는 느립니다

잠금 구현: 대기 시 차단

  • 잠금 구현은 대기 중인 스레드를 스케줄러 준비 큐에서 제거 (예: park() 및 unpark())
  • 스케줄러는 준비된 스레드를 실행함
  • 우려 사항이 잘 분리됨

스핀 대기 대 차단

  • 각 접근 방식은 다른 상황에서 더 적합
  • 유니프로세서
    • 대기 중인 프로세스가 스케줄링됨 --> 잠금을 보유한 프로세스가 아님
    • 대기 중인 프로세스는 항상 프로세서를 포기해야 함
    • 각 잠금에 대기자 큐를 연결(이전 구현에서와 같이)
  • 멀티프로세서
    • 대기 중인 프로세스가 스케줄링됨 --> 잠금 보유 프로세스가 스핀 상태이거나 차단 상태일 수 있음
    • 잠금이 release되는 시간(t)에 따라 다름
    • 잠금이 빠르게 release됨 --> 스핀 대기
    • 잠금이 느리게 release됨 --> 차단
    • 빠름과 느림은 컨텍스트 전환 비용(C)과 관련

스핀 대기를 할까? 차단을 할까?

  • 잠금 release되기 전 시간(t)을 안다면 최적의 행위를 결정할 수 있음
  • 스핀 대기 시 CPU가 낭비되는 시간
    • t
  • 차단 시 낭비되는 시간
    • C
  • 가장 적합한 행위 (t<C)일 때
    • 스핀 대기
  • (t>C)일 때
    • 차단
  • 문제점:
    • 미래를 알아야 함; 특정한 예측을하는데 너무 많은 간접비가 발생

2단계 대기

  • 이론: 최악의 경우 성능 바운딩; 실제/최적의 ratio 최악의 성능이 언제 발생합니까?
    • 매우 긴 시간 동안 스핀 >> C
    • Ratio: t/C (범위 없음)
  • 알고리즘: C에 대해 스핀 대기한 후 차단 --> 최적의 2 요소.
  • 두 가지 케이스:
    • t < C: 최적의 스핀대기 t에 대한 스핀대기, 스핀대기 t도
    • t > C: (C비용) 즉시 블록하면, 스핀 C 지불하기 때문에 (2 C의 가격) ->2 대립의 알고리즘
  • 경쟁적인 분석의 예시

2단계 잠금

  • 2단계 잠금 락은 spining 락 잠금이 해제될 것 같다면 유용할 것이라는 생각에서 비롯
  • 1단계
    • 잠금을 획들이 가능한 시점까지 스핀락을 잠시 동안 작동시킨다
    • 1단계 작동 구간에서 잠금 획득 실패 시에는 2단계 작동 구간으로 넘어간다
  • 2단계
    • 요청 스레드를 sleep 상태로 전환시킨다
    • 요청 스레드는 잠금 해제가 감지된 시점에서 부터 스레드를 깨운다

Filter Lock

  • class Filter implements Lock {
    • int[] level; int[] victim;
    • public Filter(int n) {
    • level = new int[n]; victim = new int[n]; // use 1..n-1
    • for (int i = 0; i < n; i++) { level[i] = 0; }
    • }
    • public void lock() {
    • int me = ThreadID.get();
    • for (int i = 1; i < n; i++) { //attempt level 1 .. n-1
    • level[me] = i; victim[i] = me; // 스핀으로 lock을 획득
    • while ((∃k != me) (level[k] > = i && victim[i] == me)) {};
    • }
    • }
    • public void unlock() {
    • int me = ThreadID.get(); level[me] = 0;
    • }
  • }

Bakery Algorithm by Lamport

  • class Bakery implements Lock {
    • volatile boolean[] flag;
    • volatile Label[] label;
    • public Bakery (int n) {
    • flag = new boolean[n];
    • label = new Label [n];
    • for (int i = 0; i < n; i++) {
    • flag[i] = false; label [i] = 0;
    • }
    • }
    • public void lock() {
    • flag[i] = true;
    • label [i] = max(label[0], …,label[n-1])+1;
    • while (∃k flag[k]
    • && (label[i],i) > (label[k],k));
    • }
    • public void unlock() {
    • flag[i] = false;
    • }
  • }

Bakery Algorithm by Lamport

  • First-Come-First-Serve 제공
  • 교착 상태 없음
  • 어떻게 작동 하는 가?
    • "번호" 가져오기
    • 더 낮은 번호가 제공될 때까지 기다리기
  • 어휘 편집 순서
    • (a,i) > (b,j)
    • a > b이거나 a = b이고 i > j인 경우
  • label[i]로 오버플로가 가능합니까? (32비트: 예, 64비트??)

Studying That Suits You

Use AI to generate personalized quizzes and flashcards to suit your learning preferences.

Quiz Team

Related Documents

More Like This

Use Quizgecko on...
Browser
Browser