Smalltalk80LanguageImplementationKor:Chapter 15

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 15 장 다중 독립적 프로세스

다중 독립적 프로세스

Object

    Magnitude
        Character
        Date
        Time

        Number
            Float
            Fraction
            Integer
                LargeNegativeInteger
                LargePositiveInteger
                SmallInteger

        LookupKey
            Association

    Link

        Process***

    Collection

        SequenceableCollection
            LinkedList

                Semaphore***

            ArrayedCollection
                Array

                Bitmap
                    DisplayBitmap

                RunArray
                String
                    Symbol
                Text
                ByteArray

            Interval
            OrderedCollection
                SortedCollection
        Bag
        MappedCollection
        Set
            Dictionary
                IdentifyDictionary

    Stream
        PositionableStream
            ReadStream
            WriteStream
                ReadWriteStream
                    ExternalStream
                        FileStream

        Random

    File
    FileDirectory
    FilePage

    UndefinedObject
    Boolean
        False
        True

    ProcessorScheduler***
    Delay***
    SharedQueue***

    Behavior
        ClassDescription
            Class
            MetaClass

    Point
    Rectangle
    BitBit
        CharacterScanner

        Pen

    DisplayObject
        DisplayMedium
            Form
                Cursor
                DisplayScreen
        InfiniteForm
        OpaqueForm
        Path
            Arc
                Circle
            Curve
            Line
            LinearFit
            Spline


Smalltalk-80 시스템은 Process, ProcessorScheduler, Semaphore라는 세 개의 클래스로 다중 독립적 프로세스를 지원한다. Process는 다른 Processes가 표현하는 액션과 상관없이 실행할 수 있는 액션의 시퀀스를 나타낸다. ProcessorScheduler는 시스템에서 Processes가 나타내는 액션을 실제로 실행하는 Smalltalk-80 가상 머신의 사용을 스케줄링한다. 실행할 준비가 된 액션을 가진 Processes가 많은 경우에는 ProcessorScheduler가 특정 시간에 이러한 가상 머신 중에 어떤 것이 실행할 것인지 결정한다. Semaphore는 독립적 프로세스가 그들의 액션을 서로 동기화하도록 허용한다. Semaphores는 좀 더 복잡한 동기식 상호작용을 생성하는 데에 사용할 수 있는 간단한 형태의 동기식 통신을 제공한다. Semaphores는 또 사용자 입력 장치나 실시간 시계와 같은 비동기식 하드웨어 장치와의 동기식 통신도 제공한다.


Semaphores가 유용한 동기 메커니즘이 되지 못하는 경우가 종종 있다. SharedQueue와 Delay의 인스턴스들은 동기화에 가장 공통으로 필요한 두 가지 요구를 충족하기 위해 Semaphores를 사용한다. SharedQueue는 독립적 프로세스들 간에 안전한 객체 전송을 제공하고, Delay는 프로세스가 실시간 시계와 동기화되도록 허용한다.


프로세스

"Process"(프로세스)는 표현식이 설명하는 액션의 시퀀스로, Smalltalk-80 가상 머신이 실행한다. 시스템 내 여러 프로세스가 비동기식 하드웨어 장치를 감시한다. 예를 들어 많은 프로세스들이 키보드, 지시(pointing) 장치와 실시간 시계를 감시한다. 시스템에 이용 가능한 메모리를 감시하는 프로세스도 있다. 사용자에게 가장 중요한 프로세스는 사용자가 직접 명시한 액션을 실행하는 것으로, 가령 텍스트, 그래픽 또는 클래스 정의의 편집을 예로 들 수 있겠다. 이러한 사용자 인터페이스 프로세스는 키보드와 지시 장치를 감시하는 프로세스와 통신하여 사용자가 무엇을 하는지 알아내야 한다. 프로세스는 사용자가 정의한 객체의 뷰 또는 시계를 업데이트하도록 추가할 수 있다.


블록으로 fork 단항 메시지를 전송하면 새로운 프로세스를 생성할 수 있다. 가령 아래 표현식은 EasternTime, MountainTime, PacificTime으로 명명된 3개의 시간을 화면에 표시하는 새 프로세스를 생성한다.

[EasternTime display.
    MountainTime display.
    PacificTime display] fork


새 프로세스를 구성하는 액션은 블록의 표현식에 의해 설명된다. fork 메시지는 이러한 표현식에 대해 value 메시지와 동일한 효과를 갖지만 메시지의 결과가 리턴되는 방식에는 차이가 있다. 블록이 value를 수신하면 그 표현식이 모두 실행될 때까지 리턴을 대기한다. 예를 들어, 아래 표현식은 3개의 시계가 완전히 표시되고 나서야 값을 생성한다.

[EasternTime display.
    MountainTime display.
    PacificTime display] value


블록에 value를 전송하여 리턴된 값은 블록에서 마지막 표현식의 값이다. 블록이 fork를 수신하면 즉시, 주로 그 표현식이 실행되기 전에 리턴한다. 이는 블록 내 표현식과 무관하게 fork 메시지 다음 표현식들이 실행되도록 해준다. 예를 들어, 아래의 두 표현식은 표시되는 3개의 시계와 무관하게 nameList라는 컬렉션의 내용이 정렬되는 결과를 야기한다.

[EasternTime display.
    MountainTime display.
    PacificTime display] fork.
alphabeticalList  nameList sort


시계가 하나라도 표시되기 전에 전체 컬렉션을 정렬할 수도 있고 컬렉션을 정렬하기 전에 모든 시계를 표시할 수도 있다. 이러한 두 가지 상황 또는 정렬과 시계 표시가 교대로 이루어지는지 여부는 display와 sort가 작성되는 방식에 따라 좌우된다. 두 프로세스 중 하나는 fork와 sort 메시지를 전송하고 나머지 하나는 display를 전송하는데, 둘 다 독립적으로 실행된다. 블록의 표현식은 fork로부터 리턴 시 평가되지 않을 수도 있으므로 fork의 값은 블록의 표현식 값과 무관해야 한다. 블록은 자체를 fork의 값으로서 리턴한다.


시스템 내 각 프로세스는 Process 클래스의 인스턴스에 의해 표현된다. fork에 대한 블록의 응답은 Process의 새 인스턴스를 생성하고 그것이 표현하는 표현식을 실행하도록 프로세스(processor)를 스케줄링하는 것이다. 블록은 또 Process의 새 인스턴스를 생성 및 리턴함으로써 newProcess 메시지에 응답하기도 하는데, 가상 머신은 그 표현식을 실행하도록 스케줄링되지 않는다. 이는 fork와 달리 Process로의 참조를 자체적으로 제공하기 때문에 유용하다. newProcess에 의해 생성된 Process는 “suspended”(보류된)것으로 불리는데, 그 표현식이 실행되지 않기 때문이다. 가령 아래의 표현식은 두 개의 Process를 생성하지만 display나 sort가 전송되지는 않는다.

clockDisplayProcess  [ EasternTime display ] newProcess.
sortingProcess  [ alphabeticalList  nameList sort ] newProcess


보류된 Processes 중 하나가 나타내는 액션은 실제로 Process에 메시지 resume을 전송하여 실행할 수 있다. 아래의 두 표현식은 EasternTime으로 display가 전송되고 nameList로 sort가 전송되는 결과를 야기할 것이다.

clockDisplayProcess resume.
sortingProcess resume


display와 sort는 서로 다른 Processes로부터 전송될 것이므로 그들의 표현식은 교차배치(interleave)될 것이다. resume의 또 다른 예로 BlockContext에서 fork의 구현을 들 수 있다.

fork
    self newProcess resume


상호 보완적인 메시지 suspend는 프로세서가 더 이상 표현식을 실행하지 않는 보류된 상태로 Process를 되돌린다. terminate 메시지는 Process의 보류 여부와 상관없이 다시 실행되지 못하도록 막는다.

프로세스 상태 변경하기(changing process state)
resume 수신자가 앞서도록(advance) 허용한다.
suspend 후에 (resume 메시지를 전송하여) 과정을 재개할 수 있는 방식으로 수신자의 앞섬(advancement)을 중단한다.
terminate 수신자의 앞섬을 중단한다.
Process 인스턴스 프로토콜


블록은 newProcessWith:라는 선택자를 가진 메시지도 이해하는데 이는 블록 인자에 대한 값을 제공하는 새로운 Process를 생성하여 리턴한다. newProcessWith: 의 인자는 요소들이 블록 인자의 값으로 사용되는 Array다. Array의 크기는 수신자가 취하는 블록 인자의 개수와 동일해야 한다. 예를 들면 다음과 같다.

displayProcess  [ :clock | clock display ]
    newProcessWith: (Array with: MountainTime)


새로운 Processes의 생성을 허용하는 BlockContext의 프로토콜은 아래 페이지에 표시하겠다.

스케줄링(scheduling)
fork 새로운 Process를 생성하여 수신자가 포함하는 표현식의 실행을 스케줄링한다.
newProcess 수신자가 포함하는 표현식의 실행에 새로운 보류 Process를 응답한다. 새로운 Process가 스케줄링되지 않는다.
newProcessWith: argumentArray 수신자가 포함하는 표현식의 실행에 새로운 보류 Process를 응답하고 수신자의 블록 인자 값으로 argumentArray의 요소를 제공한다.
BlockContext 인스턴스 프로토콜


스케줄링

Smalltalk-80 가상 머신에는 Process가 나타내는 액션의 시퀀스를 실행할 수 있는 프로세서가 하나 있다. 따라서 Process가 resume 메시지를 수신하면 그 액션이 즉시 실행되지 않을 수도 있다. 액션이 현재 실행 중인 Process를 "active"(활성)이라고 부른다. 활성 Process가 suspend 또는 terminate 메시지를 수신할 때마다 resume를 수신한 프로세스로부터 새로운 활성 Process가 선택된다. ProcessorScheduler 클래스의 단일 인스턴스는 resume을 수신한 모든 Processes를 추적한다. 해당 ProcessorScheduler의 인스턴스는 Processor라는 전역 변수를 갖고 있다. 활성 Process는 Processor로 activeProcess를 전송하면 찾을 수 있다. 예를 들어, 활성 Process는 아래의 표현식을 이용해 종료할 수 있다.

Processor activeProcess terminate


이는 해당 Process에서 마지막으로 실행되는 표현식이 될 것이다. 메서드에서 그 다음에 어떤 표현식이 오든 절대 실행되지 않을 것이다. Processor는 또한 terminateActive 메시지에 대한 응답으로 활성 Process를 종료할 것이다.

Processor terminateActive


우선순위(Priorities)

대개 Processes는 선착순 선처리(first-come first-served) 방식으로 프로세서의 사용을 스케줄링한다. 활성 Process가 suspend 또는 terminate를 수신할 때마다 가장 오래 기다린 Process가 새로운 활성 Process가 될 것이다. Process가 언제 실행될 것인지에 대한 제어를 좀 더 제공하기 위해 Processor는 매우 간단한 우선순위 메커니즘을 사용한다. 우선순위의 수는 고정되어 있고 정수를 증가시켜 번호를 매긴다. 요청 순서와 상관없이 번호가 큰 Process가 낮은 우선순위의 Process보다 프로세서의 사용권을 빨리 획득한다. Process가 생성되면 (for 또는 newProcess를 이용해) 그것을 생성한 Process와 동일한 우선순위를 획득할 것이다. Process의 우선순위는 우선순위를 인자로 한 priority: 메시지를 전송하면 변경할 수 있다. 아니면 우선순위를 인자로 한 메시지 forkAt: 를 사용하여 그것이 fork될 때 Process의 우선순위를 명시할 수도 있다. 가령 Process에서 우선순위 4로 실행되는 아래 표현식을 고려해보자.

wordProcess  [['now' displayAt: 50@100] forkAt: 6.
    ['is' displayAt: 100@100] forkAt: 5.
    'the' displayAt: 150@100]
        newProcess.
wordProcess priority: 7.
'time' displayAt: 200@100.
wordProcess resume.
'for' displayAt: 250@100


화면에 표시되는 순서는 다음과 같을 것이다.

time
the time
now the time
now is the time
now is the time for


우선순위는 Processes로 전송되는 메시지와 BlockContexts로 전송되는 메시지로 조작된다.

접근하기(accessing)
priority: anInteger 수신자의 우선순위를 anInteger로 설정한다.
Process 인스턴스 프로토콜


스케줄링하기(scheduling)
forkAt: priority 수신자가 포함하는 표현식의 실행을 위한 새 프로세스를 생성하라. 우선순위 수준 priority에서 새 프로세스를 스케줄링한다.
BlockContext 인스턴스 프로토콜


Smalltalk-80 시스템 내 메서드는 사실상 리터럴 정수로 우선순위를 명시하지 않는다. 사용하기 적절한 우선순위는 항상 Processor로 메시지를 전송하여 얻을 수 있다. 우선순위를 얻기 위해 사용되는 메시지는 ProcessorScheduler 클래스에 대한 프로토콜에 표시되어 있다.


Processor로 전송되는 또 다른 메시지는 활성 Process와 우선순위가 동일한 다른 Processes가 프로세서로 접근할 수 있도록 해준다. ProcessorScheduler는 활성 Process를 보류함으로써 yield 메시지에 응답하고 그것을 우선순위를 기다리는 Processes 리스트 끝으로 위치시킨다. 리스트의 첫 Process가 활성 Process가 된다. 우선순위가 같은 Processes가 더 이상 없을 경우 yield는 어떤 효과도 보이지 않는다.

접근하기(accessing)
activePriority 현재 실행 중인 프로세스의 우선순위를 응답한다.
activeProcess 현재 실행 중인 프로세스를 응답한다.
프로세스 상태 변경(process state change)
terminateActive 현재 실행 중인 프로세스를 종료한다.
yield 현재 실행 중인 프로세스의 우선순위에 있는 다른 프로세스에게 실행할 기회를 준다.
우선순위명(priority names)
highIOPriority 가장 시간 결정적인 입/출력 프로세스가 실행되어야 하는 우선순위를 응답한다.
lowIOPriority 대부분의 입/출력 프로세스가 실행되어야 하는 우선순위를 응답한다.
systemBackgroundPriority 시스템 배경 프로세스가 실행되어야 하는 우선순위를 응답한다.
timingPriority 실시간을 추적하는 시스템 프로세스가 실행되어야 하는 우선순위를 응답한다.
userBackgroundPriority 사용자가 생성한 배경 프로세스가 실행되어야 하는 우선순위를 응답한다.
userInterruptPriority 원하는 즉각적 서비스와 사용자가 생성한 프로세스가 실행되어야 하는 우선순위를 응답한다.
userSchedulingPriority 사용자 인터페이스 프로세스가 실행되어야 하는 우선순위를 응답한다.
ProcessorScheduler 인스턴스 프로토콜


ProcessorScheduler로 전송되는 우선순위를 요청하는 메시지는 프로토콜 설명에 표준이므로 위에 알파벳 순으로 열거하였다. 동일한 메시지를 우선순위가 낮은 순으로 아래 소개하였으며, 해당 우선순위를 가진 Processes의 예도 함께 소개하였다.

우선순위명(priority names)
timingPriority 실시간 시계를 감시하는 Process (본 장에서 이후에 Wakeup 클래스의 설명을 참고).
highIOPriority 로컬 네트워크 통신 장치를 감시하는 Process.
lowIOPriority 사용자 입력 장치를 감시하는 Process와 로컬 네트워크에서 패킷을 분배하는 Process.
userInterruptPriority 사용자 인터페이스에 의해 fork되고 즉시 실행되어야 하는 프로세스.
userSchedulingPriority 사용자 인터페이스를 통해 (편집하기, 보기, 프로그래밍하기, 디버깅하기) 명시된 액션을 실행하는 Process.
userBackgroundPriority 사용자 인터페이스에 의해 fork되고 다른 일이 발생하지 않을 때 실행되어야 하는 프로세스.
systemBackgroundPriority 다른 일이 발생하지 않을 때 실행되어야 하는 시스템 Process.
ProcessorScheduler 인스턴스 프로토콜


세마포어(Semaphores)

Process가 나타내는 액션의 시퀀스는 다른 Processes가 표현하는 액션과 비동기식으로 실행된다. 하나의 Process의 함수는 다른 Process의 함수와 무관하다. 이는 상호작용이 전혀 필요 없는 Process에 적절하다. 예를 들어, 아래 표시된 두 개의 Processes는 시계를 표시하고 서로 전혀 상호작용할 필요가 없는 컬렉션을 정렬한다.

[EasternTime display.
    MountainTime display.
    PacificTime display] fork.
alphabeticalList  nameList sort


하지만 상당히 독립적인 몇 가지 Processes도 때로는 상호작용을 해야 한다. 의존성이 낮은 Processes의 액션은 상호작용하는 동안 동기화되어야 한다. Semaphores의 인스턴스는 독립적인 Processes 간에 간단한 형태의 동기식 통신을 제공한다. Semaphore는 프로세스 간에 간단한 (정보의 1 비트 이하) 시그널의 동기식 통신을 제공한다. Semaphore는 아직 생성되지 않은 시그널을 소모하려 시도하는 Process에 nonbusy wait을 제공한다. Semaphores는 Processes 간 상호작용에 유일하게 제공되는 안전한 메커니즘이다. 그 외 상호작용에 대한 메커니즘은 동기성을 확보하기 위해 Semaphores를 사용해야 한다.


Semaphore와 통신은 Semaphore로 signal을 전송하여 Process 안에서 시작된다. 또 다른 통신의 끝에서는 다른 Process가 동일한 Semaphore로 wait을 전송하여 간단한 통신을 수신하길 기다린다. 두 메시지가 전송되는 순서는 중요하지 않으며, 신호를 기다리는 Process는 하나가 전송될 때까지 진행되지 않을 것이다. Semaphore는 signal 메시지를 수신한 개수만큼의 wait 메시지로부터 리턴될 것이다. signal과 두 개의 wait가 Semaphore로 전송되면 이는 두 개의 wait 메시지 중 하나로부터 리턴될 것이다. 해당하는 signal이 전송되지 않은 wait 메시지를 Semaphore가 수신하면 wait가 전송된 프로세스를 보류한다.

통신(communication)
signal 수신자를 통해 시그널을 전송한다. 하나 또는 이상의 Processes가 시그널을 수신하기 위해 보류되었다면 가장 오래 기다린 프로세스의 진행을 허용한다. 대기 중인 Process가 없을 경우 초과 시그널을 기억하라.
wait 활성 Process는 진행하기 전에 수신자를 통해 시그널을 수신해야 한다. 어떤 시그널도 전송되지 않았다면 활성 Process는 시그널이 하나 전송될 때까지 보류될 것이다.
Semaphore 인스턴스 프로토콜


보류되었던 프로세스들은 보류된 순서대로 다시 재개될 것이다. Process의 우선순위는 프로세서의 사용을 위해 스케줄링될 때 Processor에 의해서만 고려된다. Semaphore를 기다리는 각 Process는 우선순위와 상관없이 선착순 선처리 방식으로 재개될 것이다. Semaphore는 Process가 프로세서 용량을 사용하지 않고 전송되지 않은 시그널을 기다리는 것을 허용한다. Semaphore는 signal이 전송될 때까지 wait로부터 리턴하지 않는다. 특정 활동에 독립 프로세스를 생성 시 한 가지 이점으로는, 프로세스가 이용할 수 없는 무언가를 필요로 할 경우 그것이 이용가능해질 때까지 기다리는 동안 다른 프로세스가 진행될 수 있다는 점을 들 수 있다. 프로세스가 이용 가능하거나 이용 불가한 것을 필요로 하는 경우의 예로는 하드웨어 장치, 사용자 이벤트(키누름 또는 지시 장치 움직임), 공유 데이터 구조가 있다. 특정 시간 또한 프로세스가 진행할 때 필요로 하는 것으로 간주할 수 있다.


상호 배제(Mutual Exclusion)

Semaphores는 구분된 Processes에서 특정 기능을 상호 배타적으로 사용하도록 확보하는 데에 사용할 수 있다. 가령 Semaphore는 구분된 Processes가 안전하게 접근할 수 있는 데이터 구조체를 제공하는 데에 사용할 수 있겠다. 아래의 간단한 선입 선출식 데이터 구조체 정의는 상호 배제의 규정을 갖고 있지 않다.

클래스명 SimpleQueue
슈퍼클래스 Object
인스턴스 변수명 contentsArray
readPosition
writePosition
클래스 메서드
instance creation
    new
        self new: 10
    new: size
        super new init: size
인스턴스 메서드
accessing
    next
        | value |
        readPosition = writePosition
            ifTrue: [self error: 'empty queue']
            ifFalse: [value  contentsArray at: readPosition.
                contentsArray at: readPosition put: nil.
                readPosition  readPosition + 1.
                value]
    nextPut: value
        writePosition > contentsArray size
            ifTrue: [self makeRoomForWrite].
        contentsArray at: writePosition put: value.
        writePosition  writePosition +1.
        value
    size
        writePosition - readPosition

testing
    isEmpty
        writePosition = readPosition

private
    init: size
        contentsArray  Array new: size.
        readPosition  1.
        writePosition  1
    makeRoomForWrite
        | contentsSize |
        readPosition = 1
            ifTrue: [contentsArray grow]
            ifFalse:
                [contentsSize  writePosition - readPosition.
                    1 to: contentsSize do:
                        [ :index |
                            contentsArray
                                at: index
                                put: (contentsArray at: index + readPosition - 1)].
                    readPosition  1.
                    writePosition  contentsSize + 1]


SimpleQueue는 contentsArray라는 Array에 그 내용을 기억시키고 두 개의 색인을 readPosition과 writePosition이라는 contentsArray에 유지한다. 새로운 내용은 writePosition에서 추가되고 readPosition에서 제거된다. Private 메시지 makeRoomForWrite는 새로운 객체를 저장할 공간이 contentsArray의 끝에서 발견되지 않을 때 전송된다. contentsArray가 완전히 차면 그 크기가 증가한다. 그 외의 경우 내용이 contentsArray의 처음으로 이동한다.


다른 Processes들로부터 SimpleQueue로 메시지를 보낼 때 문제는 next 또는 nextPut: 에 대해 한 번에 하나 이상의 Process가 실행될 수 있다는 점이다. 하나의 Process로부터 SimpleQueue로 next 메시지가 전송되었고 아래와 같은 표현식이 실행되었다고 가정해보자.

value  contentsArray at: readPosition


여기서 우선순위가 높은 Process가 깨어나 동일한 SimpleQueue로 또 다른 next 메시지가 전송된다. readPosition이 증가되지 않았으므로 위 표현식이 두 번째로 실행되면 동일한 객체를 value로 바인딩할 것이다. 우선순위가 높은 Process는 contentsArray로부터 객체로의 참조를 제거하고, readPosition을 증가시킨 후 그것이 제거한 객체를 리턴할 것이다. 우선순위가 낮은 Process가 제어를 다시 받으면 readPosition이 증가되어 contentsArray로부터 다음 객체로의 참조를 제거한다. 이 객체는 next 메시지 중 하나의 값이어야 하지만 그 값은 소멸(discarded)되었으므로 두 개의 next 메시지 모두 동일한 객체를 리턴한다.


상호 배제를 확보하기 위해 각 Process는 자원을 사용하기 전에 같은 Semaphore를 기다렸다가 완료되면 Semaphore로 신호를 보낸다. 아래의 SimpleQueue의 서브클래스는 상호 배제를 제공하여 구분된 Processes로부터 그 인스턴스를 사용할 수 있도록 한다.

클래스명 SimpleSharedQueue
슈퍼클래스 SimpleQueue
인스턴스 변수명 accessProtect
인스턴스 메서드
accessing
    next
        | value |
        accessProtect wait.
        value  super next.
        accessProtect signal.
        value
    nextPut: value
        accessProtect wait.
        super nextPut: value.
        accessProtect signal.
        value

private
    init: size
        super init: size.
        accessProtect  Semaphore new.
        accessProtect signal


상호 배제는 Semaphores의 일반적인 사용 예에 해당하므로 그에 대한 메시지를 포함한다. 이 메시지의 선택자는 critical: 이다. critical: 의 구현은 아래와 같다.

critical: aBlock
    | value |
    self wait.
    value  aBlock value.
    self signal.
    value


상호 배제에 사용된 Semaphore는 하나의 excess 신호로 시작되어야 첫 번째 Process가 임계 영역으로 들어갈 수 있다. Semaphore 클래스는 새로운 인스턴스를 한 번 signal하는 특수 초기화 메시지 forMutualExclusion을 제공한다.

상호 배제(mutual exclusion)
critical: aBlock 다른 임계 블록이 실행되지 않을 때 aBlock을 평가한다.
Semaphore 인스턴스 프로토콜


인스턴스 생성(instance creation)
forMutualExclusion 하나의 excess 시그널로 새로운 Semaphore를 응답한다.
Semaphore 클래스 프로토콜


SimpleSharedQueue의 구현은 아래와 같이 읽히도록 변경이 가능하다.

클래스명 SimpleSharedQueue
슈퍼클래스 SimpleQueue
인스턴스 변수명 accessProtect
인스턴스 메서드
accessing
    next
        | value |
        accessProtect critical: [ value  super next ].
        value
    nextPut: value
        accessProtect critical: [super nextPut: value ].
        value

private
    init: size
        super init: size.
        accessProtect  Semaphore forMutualExclusion


자원공유(Resource Sharing)

두 개의 Processes가 자원을 공유하기 위해서는 자원에 대한 상호 배제적 접근으론 부족하다. Processes는 자원의 이용 가능성에 대해서도 통신할 수 있어야 한다. SimpleSharedQueue는 동시적 접근으로는 혼동되지 않겠지만 빈 SimpleSharedQueue에서 객체를 제거하려는 시도가 이루어지면 오류가 발생한다. 비동기식 Processes의 환경에서는 (next를 전송함으로써) 객체를 제거하려는 시도를 (nextPut: 을 전송하여) 객체를 추가한 직후에만 가능하도록 확보한다면 불편할 것이다. 따라서 Semaphores는 공유 자원의 이용 가능성을 시그널링하는 데에도 사용된다. 자원을 나타내는 Semaphore는 자원의 각 단위(unit)가 이용 가능해진 후에 "signalled"(시그널링)되고, 각 단위를 소모하기까지 "waited"(대기한다). 따라서 자원이 생성되기 전에 소모하려는 시도가 이루어지면 소모자는 단순히 기다린다.


SafeSharedQueue 클래스는 어떻게 Semaphores를 이용해 자원의 이용 가능성에 대해 통신하는지를 보여주는 예이다. SafeSharedQueue는 SimpleSharedQueue와 비슷하지만 큐 내용의 이용 가능성을 나타내기 위해 valueAvailable이라는 또 다른 Semaphore를 이용한다. SafeSharedQueue는 Smalltalk-80 시스템에 위치하지 않으며, 본문에서 하나의 예제로 설명할 것이다. SharedQueue는 사실상 시스템 내에서 여러 프로세스 간 통신하는 데에 사용되는 클래스다. SharedQueue는 SafeSharedQueue의 것과 비슷한 기능을 제공한다. SharedQueue에 대한 프로토콜 명세는 본 장의 뒷부분에서 제공하겠다.

클래스명 SafeSharedQueue
슈퍼클래스 SimpleQueue
인스턴스 변수명 accessProtect
valueAvailable
인스턴스 메서드
accessing
    next
        | value |
        valueAvailable wait.
        accessProtect critical: [ value  super next ].
        value
    nextPut: value
        accessProtect critical: [ super nextPut: value ].
        valueAvailable signal.
        value

private
    init: size
        super init: size.
        accessProtect  Semaphore forMutualExclusion.
        valueAvailable  Semaphore new


하드웨어 인터럽트

Semaphore의 인스턴스는 하드웨어 장치와 Processes 간 통신에도 사용된다. 이 공간에서 해당 인스턴스들은 하드웨어 장치가 경험하는 상태의 변화에 대해 통신하는 수단으로서 인터럽트를 대신한다. Smalltalk-80 가상 머신은 세 가지 조건으로 Semaphores를 시그널링하도록 명시된다.

  • 사용자 이벤트: 키보드에 키가 눌러졌거나, 지시 장치에서 버튼이 눌러졌거나, 지시 장치가 이동하였다.
  • 시간만료(timeout): 시계에서 특정 밀리초 값에 도달하였다.
  • 저공간: 이용 가능한 객체 메모리가 특정 한계 이하로 떨어졌다.


세 개의 Semaphores는 사용자 이벤트, 밀리초 시계, 메모리 사용률을 감시하는 세 개의 Process에 상응한다. Process는 흥미로운 일이 발생할 때까지 스스로를 보류한 채 적절한 Semaphore로 wait를 전송한다. Semaphore가 시그널링될 때마다 Process가 재개될 것이다. 가상 머신은 프리미티브 메서드를 제공함으로써 세 가지 유형의 모니터링에 대해 통지받는다. 예를 들어, timeout 시그널은 Processor로 전송되는 signal:atTime: 과 연관된 프리미티브 메서드가 요청할 수 있다.


Wakeup 클래스는 이러한 Semaphores의 이용 방법들 중 하나를 어떻게 사용하는지 보여주는 일례에 해당한다. Wakeup은 밀리초 시계를 감시함으로써 Processes로 알람 시계 서비스를 제공한다. Wakeup은 Smalltalk-80 시스템에 존재하지 않으며, 본문에서는 사용 예로 제시되었을 뿐이다. Delay는 Smalltalk-80에서 사실상 밀리초 시계를 감시하는 클래스다. Delay는 Wakeup의 것과 비슷한 기능을 제공한다. Delay에 대한 프로토콜 명세는 본 장의 뒷부분에서 제공할 것이다.


Wakeup은 명시된 밀리초 수만큼 Process의 전송을 보류하는 메시지를 제공한다. 아래의 표현식은 1초의 ¾ 만큼 그 Process를 보류한다.

Wakeup after: 750


Wakeup이 after: 메시지를 수신하면 wakeup이 발생해야 하는 시계 값을 기억하는 새로운 인스턴스를 할당한다. 새로운 인스턴스는 wakeup에 도달할 때까지 활성 Process가 보류될 Semaphore를 포함한다. Wakeup은 wakeup 시간으로 정렬된 리스트에 모든 인스턴스를 보관한다. Process는 이러한 wakeup 시간 중 가장 빠른 시간을 가상 머신의 밀리초 시계에서 감시하고, 적당하게 보류된 Process가 계속 진행하도록 허용한다. 이러한 Process는 initializeTimingProcess에 대한 클래스 메서드에서 생성된다. 시계를 감시하는 데에 사용된 Semaphore는 TimingSemaphore로 명명된 클래스 변수에 의해 참조된다. 가상 머신은 nextWakeup에 대한 인스턴스 메서드에서 발견되는 아래의 메시지를 이용해 시계를 감시해야 함을 통지받는다.

Processor signal: TimingSemaphore atTime: resumption Time


재개를 기다리는 인스턴스의 리스트는 PendingWakeups라는 클래스 변수에 의해 참조된다. PendingWakeups로 상호 배제적 접근을 제공하는 AccessProtect라는 또 다른 Semaphore도 있다.

클래스명 Wakeup
슈퍼클래스 Object
인스턴스 변수명 alarmTime
alarmSemaphore
클래스 변수명 PendingWakeups
AccessProtect
TimingSemaphore
클래스 메서드
alarm clock service
after: millisecondCound
    (self new sleepDuration: millisecondCount) waitForWakeup
class initialization
    initialize
        TimingSemaphore  Semaphore new.
        AccessProtect  Semaphore forMutualExclusion.
        PendingWakeups  SortedCollection new.
        self initializeTimingProcess
    initializeTimingProcess
        [[true]
            whileTrue:
                [TimingSemaphore wait.
                    AccessProtect wait.
                    PendingWakeups removeFirst wakeup.
                    PendingWakeups isEmpty
                        ifFalse: [PendingWakeups first nextWakeup].
                    AccessProtect signal]]
                            forkAt: Processor timingPriority
인스턴스 메서드
process delay
    waitForWakup
        AccessProtect wait.
        PendingWakeups add: self.
        PendingWakeups first = = self
            ifTrue: [self nextWakeup].
        AccessProtect signal.
        alarmSemaphore wait
comparison
    < otherWakeup
        alarmTime < otherWakeup wakeupTime
accessing
    wakeupTime
        alarmTime
private
    nextWakeup
        Processor signal: TimingSemaphore atTime: resumptionTime
    sleepDuration: millisecondCount
        alarmTime  Time millisecondClockValue + millisecondCount.
        alarmSemaphore  Semaphore new
    wakeup
        alarmSemaphore signal


SharedQueue 클래스

SharedQueue 클래스는 Processes 간 객체의 안전한 통신을 제공하는 인스턴스를 가진 시스템 클래스다. 그 프로토콜과 구현 모두 본 장의 앞에 소개한 SafeSharedQueue 예제와 비슷하다.

접근하기(accessing)
next 아직 제거되지 않은 수신자에 추가된 첫 번째 객체를 응답한다. 수신자가 비어 있다면 객체가 추가될 때까지 활성 Process를 보류한다.
nextPut: value 수신자의 내용에 value를 추가한다. Process가 객체를 기다리면서 보류 중일 경우 계속 진행되도록 허용한다.
SharedQueue 인스턴스 프로토콜


Delay 클래스

Delay는 명시된 시간만큼 Process의 보류를 허용한다. Delay는 활성 Process를 얼만큼 보류할 것인지 명시함으로써 생성된다.

halfMinuteDelay  Delay forSeconds: 30.
shortDelay  Delay forMilliseconds: 50


Delay를 생성한다고 해서 활성 Process의 진행 과정에는 영향을 미치지 않는다. Delay가 활성 Process를 보류하는 것은 wait 메시지에 대해 응답할 때 발생한다. 아래 표현식은 모두 활성 Process를 30초간 보류할 것이다.

halfMinuteDelay wait.
(Delay forSeconds: 30) wait


인스턴스 생성(instance creation)
forMilliseconds: millisecondCount wait 메시지가 전송되면 millisecondCount 밀리초 동안 활성 Process가 보류될 새로운 인스턴스를 응답한다.
forSeconds: secondCount wait 메시지가 전송되면 secondCount 초 동안 활성 Process가 보류될 새로운 인스턴스를 응답한다.
untilMilliseconds: millisecondCount wait 메시지가 전송되면 밀리초 시계가 millisecondCount 값에 도달할 때까지 활성 Process가 보류될 새로운 인스턴스를 응답한다.
일반 문의(general inquiries)
millisecondClockValue 밀리초 시계의 현재 값을 응답한다.
Delay 클래스 프로토콜


접근하기(accessing)
resumptionTime 지연된 process가 재개될 밀리초 시계의 값을 응답한다.
프로세스 지연(Process delay)
wait 밀리초 시계가 적절한 값에 도달할 때까지 활성 Process를 보류한다.
Delay 클래스 프로토콜


아래 표현식을 이용해 간단한 시계를 구현할 수 있다.

[[true] whileTrue:
    [Time now printString displayAt: 100@100.
        (Delay forSeconds: 1) wait]] fork

현재 시간이 화면에 1초 동안 표시될 것이다.


Notes