💡 스핀락 (Spinlock)
- 멀티스레드 또는 멀티프로세싱 환경에서 공유 자원에 대한 동시 접근을 방지하기 위해 사용되는 락(Lock) 매커니즘.
- 일반적인 뮤텍스(Mutex)와는 달리, 스핀락은 락을 획득할 때 락이 해제될 때까지 계속해서 루프를 돌며(lock busy-waiting) 확인.
📝 특징
- 바쁜 대기(Busy-waiting) 방식 사용
- 락을 획득하기 위해 대기하는 스레드가 CPU를 점유하며 지속적으로 확인(바쁘게 대기)함.
while(lock != UNLOCKED) { /*계속 확인*/ } 형태로 구현됨.
- 경량화된 락
- 컨텍스트 스위칭 없이 사용자 모드에서 락을 확인하므로 비용이 낮음.
- 락이 짧은 시간 동안만 점유되는 경우 효율적임.
- 스케줄러 개입 없음
- 운영체제의 스케줄링을 발생시키지 않으므로 매우 빠르게 락을 획득할 수 있음.
- 적합한 상황
- 락 점유 시간이 매우 짧을 때 (짧은 임계 구역).
- 멀티코어 시스템에서 락이 빈번하게 해제되는 경우.
- 비효율적인 상황
- 락 점유 시간이 길어지면 CPU를 낭비하게 됨.
- 단일 코어 환경에서는 비효율적임 (다른 스레드가 락을 해제할 기회가 없음).
📌 사용 예시 (C 언어)
#include <stdatomic.h>
#include <stdio.h>
#include <pthread.h>
#define UNLOCKED 0
#define LOCKED 1
typedef atomic_int spinlock;
void spinlock_lock(spinlock *lock) {
while(atomic_exchange(lock, LOCKED) == LOCKED) {
}
}
void spinlock_unlock(spinlock *lock) {
atomic_store(lock, UNLOCKED);
}
spinlock my_lock = ATOMIC_VAR_INIT(UNLOCKED);
void* critical_section(void* arg) {
spinlock_lock(&my_lock);
printf("Thread %d is in the critical section.\n", *(int*)arg);
spinlock_unlock(&my_lock);
return NULL;
}
int main() {
pthread_t threads[4];
int thread_ids[4] = {0, 1, 2, 3};
for(int i = 0; i < 4; i++) {
pthread_create(&threads[i], NULL, critical_section, &thread_ids[i]);
}
for(int i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
🔍 코드 설명
spinlock_lock() 함수
atomic_exchange()를 사용하여 락을 얻기 시도.
- 성공할 때까지 계속해서 반복 (busy-waiting).
spinlock_unlock() 함수
- 락을 해제하기 위해
atomic_store() 사용.
- 멀티스레드 환경에서 락 사용
- 각 스레드는
spinlock_lock()으로 락을 얻은 후, 공유 자원에 접근.
- 작업이 끝나면
spinlock_unlock()으로 락을 해제.
📊 장점과 단점
✅ 장점
- 락 획득이 매우 빠름 (스케줄러 호출 없음).
- 멀티코어 시스템에서 효율적으로 작동.
- 단순한 구현으로 인한 오버헤드 감소.
❌ 단점
- CPU 자원 낭비: 락을 얻기 위해 계속 루프를 돌며 대기하기 때문에 CPU 사용률이 높음.
- 단일 코어 환경에서는 비효율적.
- 임계 구역이 긴 경우에는 부적합.
📌 사용 사례
- 커널이나 드라이버 수준의 짧은 임계 구역 보호.
- 멀티코어 환경에서의 경량화된 동기화 메커니즘.
- 하드웨어에서 제공하는 원자적 연산(Atomic Operation)을 활용할 수 있는 상황.
📌 Spinlock 예제 (Java)
import java.util.concurrent.atomic.AtomicBoolean;
public class SpinLock {
private final AtomicBoolean lock = new AtomicBoolean(false);
public void lock() {
while (!lock.compareAndSet(false, true)) {
}
}
public void unlock() {
lock.set(false);
}
public void criticalSection(int threadId) {
lock();
try {
System.out.println("Thread " + threadId + " is in the critical section.");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
for (int i = 0; i < 4; i++) {
final int threadId = i;
new Thread(() -> spinLock.criticalSection(threadId)).start();
}
}
}
🔍 코드 설명
SpinLock 클래스
AtomicBoolean을 사용하여 락의 상태를 관리함.
compareAndSet() 메서드를 이용하여 락을 획득하거나 해제함.
lock() 메서드에서 busy-waiting 루프를 사용하여 락을 계속 확인함.
criticalSection() 메서드
- 임계 구역을 보호하기 위해
lock()과 unlock()을 사용하여 접근을 제어함.
Thread.sleep(100);는 임계 구역을 점유하는 시간을 의미.
main() 메서드
- 4개의 스레드를 생성하여
criticalSection()을 호출하게 함.
- 스레드가 동시에 접근하지 못하도록 락이 보호해줌.
📊 장점과 단점
✅ 장점
- 경량화된 락 메커니즘으로 매우 빠르게 락을 획득할 수 있음.
- 컨텍스트 스위칭을 피할 수 있어 성능 이점이 있음.
❌ 단점
- CPU 자원을 낭비할 수 있음 (특히 임계 구역이 길거나 점유 시간이 길 경우).
- 단일 코어 환경에서는 비효율적임.
- JVM의 스케줄러와 충돌할 가능성이 있음.
💡 주의 사항
- Java에서 스핀락을 사용할 때는 특정 상황(예: 짧은 임계 구역 보호) 에서만 사용하는 것이 좋음.
- 일반적으로 Java에서는
ReentrantLock이나 synchronized 키워드가 더 효율적일 수 있음.
- 특히, Java의
ReentrantLock은 공정성(Fairness) 옵션도 제공하므로, 스핀락 대신 사용하기 적합한 경우가 많음.
✍️ 스핀락을 사용해야 할 때
- 락을 점유하는 시간이 매우 짧을 때.
- 멀티코어 환경에서 성능을 극대화하고자 할 때.
- JVM의 Lock Optimization을 고려했을 때 성능을 더 높이고 싶을 때.
