[운영체제] 동기화 하드웨어

지니🧸·2023년 4월 7일
0

CS 저장소

목록 보기
20/48

소프트웨어 기반 해결책이 최신 컴퓨터 아키텍쳐에서 작동하지 않을 수 있음 > 하드웨어적 해결 필요

🥑 Memory Barrier

메모리 베리어, 메모리 장벽

메모리 모델: 컴퓨터 아키텍처가 응용 프로그램에게 메모리를 어떻게 제공하는가에 대한 모델

  1. 강한 순서, strongly ordered: 한 프로세서의 메모리 변경 결과가 다른 몯느 프로세서에 즉시 보임
  2. 약한 순서, weakly ordered: 한 프로세서의 메모리 변경 결과가 다른 프로세서에 즉시 보이지 않음

컴퓨터 아키텍처는 메모리의 모든 변경 사항을 다른 모든 프로세서로 전파하는 명령어 제공

  • 다른 프로세서에서 실행 중인 스레드에 메모리 변경 사항이 보이는 것을 보장함
  • 메모리 장벽/펜스: 메모리 장벽 명령어가 실행될 때, 시스템은 다음 명령어가 실행되기 전에 모든 적재/저장이 완료되도록 함
    • 메모리 장벽 명령어 위로 선언된 모든 문장들이 실행되어야 그 다음 문장이 실행됨

메모리 베리어 종류
1. Load barrier: 메모리에서 데이터를 읽어오는 명령어 다음에 실행되는 명령어들을 지연
데이터 읽기가 완료되기 전에 실행된 명령어로부터 발생하는 문제 해결

  1. Store barrier: 메모리에 데이터를 쓰는 명령어 이전에 실행되는 명령어들을 지연
    데이터 쓰기가 완료되기 전에 실행된 명령어로부터 발생하는 문제 해결

메모리 베리어 없이는: 원자적 연산이 제대로 작동안할 수도 있음

  • 스레드/프로세스 간 변수 접근 순서가 보장 안될 수 있음
  • 메모리 베리어로 스레드/프로세스 간 변수 접근 순서 제어 가능

🥑 Hardware instructions

하드웨어 명령어

한 워드의 내용을 원자적으로 검사하고 변경하거나 두 워드의 내용을 원자적으로 교환할 수 있는 명령어

  • 원자적: 인터럽트 되지 않는 하나의 단위로 실행

test_and_set() 명령어

  • 명령어가 원자적으로 실행됨
  • 다른 코어에서 이 명령어가 동시에 실행된다면 이들은 임의의 순서로 순차적으로 실행됨
  • lock이 가능한지 현재 lock 상태를 반환하고 lock을 true로 설정
    • lock의 초기값은 false
  • 예) 두 프로세스를 동시 실행할 때
    • 처음 실행하는 프로세스는 lock = false이므로 임계구역에 진입 가능
    • 임계구역에서 진입하면서 lock = true로 바꿔줌
    • 두번째 프로세스는 lock = true라서 임계구역 진입 불가
    • 상호 배제 만족

compare_and_swap() 명령어

  • 두 워드의 내용 교환에 기반을 둔 두 개의 워드에 대해 원자적인 연산 수행
int compare_and_swap(int *value, int expected, int new_value) {
	int temp = *value;
    if (*value == expected)
    	*value = new_value;
    return temp;
}
  • lock이 가능한지 현재 lock 상태를 반환하고 value==expected이면 value를 new value 값으로 설정
  • lock의 초기값은 0
  • 여러 프로세스가 동시에 실행될 때:
    • 첫 프로세스는 lock = 0이므로 임계구역에 진입 가능
    • 임계구역에 진입하면서 lock = 1로 바꿈
    • 나머지 프로세스는 lock = 1이라 임계구역에 진입 불가
    • 첫 프로세스가 임계구역을 떠나면서 lock = 0으로 바꿈
    • 다른 프로세스 진입 가능
  • 상호 배제 조건 만족

🥑 volatile 키워드

volatile - 자바 코드의 변수를 메인 메모리에 저장할 것을 명시하기 위해 쓰임

  • 모든 volatile 변수는 컴퓨터의 메인 메모리로부터 읽힘
  • volatile 변수에 대한 쓰기 작업은 메인 메모리로 직접 이루어짐
  • CPU 캐시가 쓰이지 않음
  • 최적화 대상 아님
    • 최적화: 불필요한 코드 없애기, 성능이 느린 저장영역(메모리 등)에 대한 접근 줄이기, 코드의 순서 변경하기
      • (예) 컴파일러는 변수 재정의 등 무의미한 코드는 알아서 삭제함
        • volatile 변수는 변수 대입 작업 전부 실행
  • 외부 요인에 의해 변수 값이 변경될 가능성이 있는 변수에는 volatile을 꼭 써야 함
    • 외부 요인: memory-mapped I/O, 인터럽트 서비스 루틴, 멀티 쓰레드 환경 등

Java 5부터 Happens-Before 보장

  • 한 쓰레드가 volatile 변수를 수정할 때, 이 쓰레드가 volatile 변수를 수정하기 전에 수정한 모든 변수들이 함께 메인 메모리에 저장(flush)됨
  • 한 쓰레드가 volatile 변수를 메인 메모리에서 읽어들일 때, volatile 변수를 수정하면서 메인 메모리에 함께 저장된 다른 모든 변수들도 함께 읽여들여짐

volatile의 남발은 성능에 악영향

  • 컴파일러 단위에서 수행 가능한 최적화 막힘
  • 해당 변수를 사용하는 모든 위치에서 레지스터 값을 읽어도되는 상황까지 모두 메모리/캐쉬에서 읽도록 함

필요한 위치에 memory barrier를 사용한다면 굳이 volatile 사용할 필요 없음

🥑 멀티코어 환경에서 동기화

멀티코어 프로세세어세는 여러 코어가 동시에 공유 메모리에 접근할 수 있음
-> 공유 자원에 대한 동시 접근을 제어하고 일관성을 유지하기 위한 동기화 기법이 필요함
-> 멀티코어 환경에서 동기화를 이뤄지게 하는 여러 가지 방법 존재

Lock 사용

  • 여러 개의 스레드가 동시에 접근하는 경우에 발생할 수 있는 데이터 불일치 문제 방지
  • 뮤텍스, 세마포어 등

원자적 연산

여러 코어가 동시에 수행하는 연산 간의 데이터 경쟁 발생 방지
동시 접근 제어

하드웨어적 락: 메모리 베리어

메모리 베리어를 사용하면 캐시와 메인 메모리 간의 데이터를 동기화시켜 캐시와 메모리 간의 일관성을 유지 & 동시성 문제 해결

  1. 프로세서는 메인 메모리에서 데이터를 읽어와 캐시에 저장
  2. 캐시에 저장된 데이터는 메인 메모리와 불일치할 수 있음
  • 코어1에서 메모리의 데이터를 읽어와 캐시에 저장
  • 코어 2에서 동일한 메모리의 데이터 변경
  • 다시 메인 메모리에 저장
  • 코어1의 캐시에는 변경된 데이터가 반영되지 않은 채로 남아있을 수 있음
  1. 메모리 베리어는 이러한 캐시와 메인 메모리의 일관성 유지를 위해 사용

메모리 베리어를 사용하면 코어가 메모리에 대한 연산을 완료하기 전, 다른 코어가 동일한 메모리에 접근하는 것을 방지함

락 없는 동기화 방법

원자적 연산/메모리 베리어 등 활용

병렬 프로그래밍 라이브러리 및 프레임워크

병렬 프로그래밍을 지원하는 라이브러리/프레임워크 사용


참고:

profile
우당탕탕

0개의 댓글