[OS] Mutex, Semaphore, Monitor

Monitor

The difficulty of using semaphore

  • 세마포어는 synchronizatio에 편리하고 효과적이다.
  • 하지만 timing errors(타이밍 문제) 발생 e.g. semaphore’s problem
    • 모든 프로세스가 1로 초기화된 바이너리 세마포어 뮤텍스를 사용하는 경우, 모든 프로세스는 임계영역 진입 전에 wait(mutex)를, 임계영역을 빠져나올 때 signal(mutex)를 호출해야 한다. 만약 이 과정이 없다면 임계영역에 여러 프로세스가 존재하는 순간이 발생할 수 있다.

위와 같은 문제점을 방지하기 위해 고수준의 동기화 도구인 monitor 탄생했다.

monitor type

  • 상호 배제(mutual exclusion)를 제공해주는 기능들을 포함한 ADT

conditional variable

  • 모니터는 몇몇 동기화 스키마를 모델링 할 때 부족했기 때문에 추가적인 동기화 메커니즘을 제공하는 condition 이라는 도구를 정의해야 했다.
  • condition variable은 wait(), signal() 메소드를 제공한다.
1
2
3
4
/* exmaple using conditional variables */
condition x, y;
x.wait();
x.signal();

Java Monitors

  • monitor-lock 혹은 intrinsic-lock이라고 불리는 쓰레드 동기화 도구인 monitor-like를 제공한다.

    e.g. synchronized keyword, wait(), notify() method

  • synchronized 키워드

    • 임계영역에 해당하는 코드 블록을 선언할 때 사용하는 Java의 키워드
    • synchronized 키워드로 선언된 코드 블록(임계 영역)에는 모니터 락을 획득해야 진입 가능
    • 모니터락을 가진 객체 인스턴스를 지정 가능
    • 메소드에 선언하면 메소드 코드 블록 전체가 임계 영역으로 지정되고, 이 때, 모니터락을 가진 객체 인스터는 this 인스턴스
1
2
3
synchronized (object) {
// critical section
}
1
2
3
public synchronized void add() {
// critical section
}
  • wait(), notify() 메소드
    • Java의 모든 객체가 가진 메소드(java.lang.Object 클래스에 선언된 메소드)
    • 쓰레드가 어떤 객체의 wait() 메소드를 호출하면 해당 객체의 모니터락을 획득하기 위해 대기 상태로 진입
    • 쓰레드가 어떤 객체의 nofity() 메소드를 호출하면 해당 객체 모니터에 대기 중인 쓰레드 하나를 깨운다.
    • notifyAll() 메소드를 호출하면 해당 객체 모니터에 대기 중인 모든 쓰레드를 깨운다.

e.g. Java Non-Synchronization Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class NonSyncronizationExample {
static class Counter {
public static int count = 0;
public static void increment() {
count++;
}
}

static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.increment();
}
}
}

public static void main(String[] args) throws Exception {
Thread[] threads = new Tread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable());
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
System.out.println("counter = " + Counter.count);
}
}

e.g. Java Synchronization Example1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SyncronizationExample1 {
static class Counter {
public static int count = 0;
synchronized public static void increment() {
// This code block is critical section
// since this block is defined with synchronized keyword
count++;
}
}

static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.increment();
}
}
}

public static void main(String[] args) throws Exception {
Thread[] threads = new Tread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable());
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
System.out.println("counter = " + Counter.count);
}
}

e.g. Java Synchronization Example2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class SyncronizationExample2 {
static class Counter {
private static Object object = new Object();
public static int count = 0;
public static void increment() {
synchronized (object) {
count++;
}
}
}

static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.increment();
}
}
}

public static void main(String[] args) throws Exception {
Thread[] threads = new Tread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable());
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
System.out.println("counter = " + Counter.count);
}
}

e.g. Java Synchronization Example3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SyncronizationExample3 {
static class Counter {
public static int count = 0;
public void increment() {
synchronized (this) {
Counter.count++;
}
}
}

static class MyRunnable implements Runnable {
Counter counter;

public MyRunnable(Counter counter) {
this.counter = counter;
}

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
}
}

public static void main(String[] args) throws Exception {
Thread[] threads = new Tread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable(new Counter()));
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
System.out.println("counter = " + Counter.count);
}
}

e.g. Java Synchronization Example4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SyncronizationExample4 {
static class Counter {
public static int count = 0;
public void increment() {
synchronized (this) {
Counter.count++;
}
}
}

static class MyRunnable implements Runnable {
Counter counter;

public MyRunnable(Counter counter) {
this.counter = counter;
}

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
}
}

public static void main(String[] args) throws Exception {
Thread[] threads = new Tread[5];
Counter counter = new Counter();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new MyRunnable(counter));
threads[i].start();
}
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
System.out.println("counter = " + Counter.count);
}
}

Liveness

  • 세마포어와 모니터는 Critical Secion Problem의 progress(avoid deadlock), bounded-waiting(avoid starvation)을 충족하지 못 한다. 이를 해결하기 위해 등장한 개념이 Liveness
  • Liveness의 두 가지 기능: deadlock, priority inversion
Author

Jaeyun Cha

Posted on

2021-06-17

Updated on

2023-04-11

Licensed under

댓글