목차
- 상호 배제성
- 재진입 가능성
- synchronized 키워드를 통한 자동 락 관리
- 디락 예방 전략
- 요약
동기화 메커니즘은 여러 스레드가 공유 리소스에 접근할 때 동작을 제어하는 데 사용됩니다.
병렬 처리에서 임계 구간은 특정 리소스에 대한 접근을 제한하는 코드 블록으로, 동시에 하나의 스레드만 실행할 수 있습니다.
락 과정: 리소스 접근 전에 락을 획득해야 하며, 이미 다른 스레드가 소유하고 있을 경우 대기 상태가 됩니다.
임계 구간 실행: 락을 획득한 후 리소스를 처리합니다.
해제: 리소스 사용이 완료되면 락을 해제하여 다른 스레드가 접근할 수 있도록 합니다.
락 객체 자체는 임계 구간이 아니며, 리소스 보호를 위한 도구입니다.
1. 상호 배제성
ReentrantMutex를 사용한 동시성 제어
package Study
// 라이브러리 참조
import std.sync.*
import std.collection.*
var count = 100
// 재진입 가능한 세마포어
let mutex = Mutex()
main () {
let tasks = ArrayList()
// 100개의 스레드 생성
for (_ in 0..100) {
let task = spawn {
// 락 획득
mutex.lock()
count++
// 락 해제
mutex.unlock()
}
tasks.add(task)
}
for (task in tasks) {
// 메인 스레드 대기
task.get()
}
println(count)
}
2. 재진입 가능성
동일한 스레드가 락을 여러 번 획득할 수 있는 특성
package Study
// 라이브러리 참조
import std.sync.*
import std.collection.*
import std.convert.*
class Account {
private var balance: Float64
private var mutex: Mutex = Mutex()
init (amount: Float64) {
this.balance = amount
}
// 예금 처리
func deposit(amount: Float64) {
// 락 획득
this.mutex.lock()
this.balance += amount
println("입금: ${amount.format(".2")}, 잔액: ${this.balance.format(".2")}")
// 락 해제
this.mutex.unlock()
}
// 잔액 확인
func verify(amount: Float64) -> Bool {
// 잔액 상태 확인
var status = false
// 락 획득
mutex.lock()
if (this.balance >= amount) {
status = true
}
// 락 해제
this.mutex.unlock()
return status
}
func withdraw(amount: Float64) {
// 락 획득
mutex.lock()
if (verify(amount)) {
this.balance -= amount
println("출금: ${amount.format(".2")}, 잔액: ${this.balance.format(".2")}")
} else {
println("잔액 부족, 잔액: ${this.balance.format(".2")}")
}
// 락 해제
mutex.unlock()
}
}
main () {
let account = Account(0.0)
let user1 = spawn {
account.deposit(5000.0)
}
let user2 = spawn {
account.withdraw(200.0)
}
user1.get()
user2.get()
}
3. synchronized 키워드 활용
명시적인 락 관리 대신 자동 처리 기능
package Study
// 라이브러리 참조
import std.sync.*
import std.collection.*
var count = 100
// 재진입 가능한 세마포어
let mutex = Mutex()
main () {
let tasks = ArrayList()
// 100개의 스레드 생성
for (_ in 0..100) {
let task = spawn {
// 자동 락 관리
synchronized(mutex) {
count++
}
}
tasks.add(task)
}
for (task in tasks) {
// 메인 스레드 대기
task.get()
}
println(count)
}
4. 디락 예방 전략
다중 리소스 접근 시 발생하는 데드락 상황
package Study
// 라이브러리 참조
import std.sync.*
import std.collection.*
import std.convert.*
import std.time.*
class Account {
private var balance: Float64
init (amount: Float64) {
this.balance = amount
}
// 예금 처리
func deposit(amount: Float64) {
this.balance += amount
println("입금: ${amount.format(".2")}, 잔액: ${this.balance.format(".2")}")
}
// 잔액 확인
func verify(amount: Float64) -> Bool {
// 잔액 상태 확인
var status = false
if (this.balance >= amount) {
status = true
}
return status
}
func withdraw(amount: Float64) {
if (verify(amount)) {
this.balance -= amount
println("출금: ${amount.format(".2")}, 잔액: ${this.balance.format(".2")}")
} else {
println("잔액 부족, 잔액: ${this.balance.format(".2")}")
}
}
}
main () {
let acc1 = Account(3000.0)
let acc2 = Account(1000.0)
// 첫 번째 세마포어
let lock1 = Mutex()
// 두 번째 세마포어
let lock2 = Mutex()
// 스레드1
let task1 = spawn {
synchronized(lock1) {
println("스레드1 lock1 획득")
sleep(Duration.second)
synchronized(lock2) {
println("스레드1 lock2 획득")
acc1.withdraw(300.0)
acc2.deposit(300.0)
}
}
}
// 스레드2
let task2 = spawn {
synchronized(lock2) {
println("스레드2 lock2 획득")
sleep(Duration.second)
synchronized(lock1) {
println("스레드2 lock1 획득")
acc1.deposit(300.0)
acc2.withdraw(300.0)
}
}
}
task1.get()
task2.get()
}
수정된 버전
package Study
// 라이브러리 참조
import std.sync.*
import std.collection.*
import std.convert.*
import std.time.*
class Account {
private var balance: Float64
init (amount: Float64) {
this.balance = amount
}
// 예금 처리
func deposit(amount: Float64) {
this.balance += amount
println("입금: ${amount.format(".2")}, 잔액: ${this.balance.format(".2")}")
}
// 잔액 확인
func verify(amount: Float64) -> Bool {
// 잔액 상태 확인
var status = false
if (this.balance >= amount) {
status = true
}
return status
}
func withdraw(amount: Float64) {
if (verify(amount)) {
this.balance -= amount
println("출금: ${amount.format(".2")}, 잔액: ${this.balance.format(".2")}")
} else {
println("잔액 부족, 잔액: ${this.balance.format(".2")}")
}
}
}
main () {
let acc1 = Account(3000.0)
let acc2 = Account(1000.0)
// 첫 번째 세마포어
let lock1 = Mutex()
// 두 번째 세마포어
let lock2 = Mutex()
// 스레드1
let task1 = spawn {
synchronized(lock1) {
println("스레드1 lock1 획득")
sleep(Duration.second)
synchronized(lock2) {
println("스레드1 lock2 획득")
acc1.withdraw(300.0)
acc2.deposit(300.0)
}
}
}
// 스레드2
let task2 = spawn {
synchronized(lock1) {
println("스레드2 lock1 획득")
sleep(Duration.second)
synchronized(lock2) {
println("스레드2 lock2 획득")
acc1.deposit(300.0)
acc2.withdraw(300.0)
}
}
}
task1.get()
task2.get()
}
5. 요약
이번 챕터에서는 셰지 언어의 재진입 가능한 세마포어 기능을 상세히 설명했습니다. 다음 챕터에서는 재진입 가능한 읽기/쓰기 세마포어에 대해 다룰 예정입니다.