GUI 프로그래밍에서 스레드 안전을 보장하는 것이 호출자의 책임 인 이유는 무엇입니까?


37

많은 곳에서 UI 구성 요소를 업데이트 할 때 (특히 Java Swing에서 Event Dispatch Thread에 있음 ) UI 스레드에 있는지 확인하는 것은 호출자의 책임이라는 것이 정식적인 지혜 1 임을 알았 습니다. .

왜 그렇습니까? 이벤트 디스패치 스레드는 MVC / MVP / MVVM 의 관점 에서 중요합니다 . 뷰를 어디에서나 처리하기 위해 뷰 는 뷰의 구현과 해당 뷰 구현의 스레딩 모델 사이에 긴밀한 연결을 만듭니다 .

특히 Swing을 사용하는 MVC 아키텍처 응용 프로그램이 있다고 가정 해 봅시다. 호출자가 Event Dispatch Thread에서 구성 요소를 업데이트해야하는 경우 JavaFX 구현을 위해 Swing View 구현을 교체하려고하면 대신 모든 Presenter / Controller 코드를 변경하여 JavaFX Application 스레드를 대신 사용해야합니다 .

따라서 두 가지 질문이 있다고 가정합니다.

  1. UI 구성 요소 스레드 안전을 보장하는 것은 호출자의 책임 인 이유는 무엇입니까? 위의 추론에서 결함은 어디에 있습니까?
  2. 이러한 스레드 안전 문제를 느슨하게 결합하면서도 스레드 안전을 유지하도록 응용 프로그램을 어떻게 설계 할 수 있습니까?

"호출자 책임"의 의미를 설명하기 위해 MCVE Java 코드를 추가하겠습니다 (여기서는 수행하지 않지만 가능한 최소한의 목적으로 노력하고있는 다른 좋은 방법이 있습니다).

발신자 책임 :

public class Presenter {
  private final View;

  void updateViewWithNewData(final Data data) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        view.setData(data);
      }
    });
  }
}
public class View {
  void setData(Data data) {
    component.setText(data.getMessage());
  }
}

책임보기 :

public class Presenter {
  private final View;

  void updateViewWithNewData(final Data data) {
    view.setData(data);
  }
}
public class View {
  void setData(Data data) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        component.setText(data.getMessage());
      }
    });
  }
}

1 : 해당 게시물의 작성자가 Swing on Stack Overflow에서 태그 점수가 가장 높습니다. 그는 여기저기서 이것을 말하고 다른 곳에서도 발신자의 책임 인 것을 보았다.


1
어, 성능 IMHO. 이러한 이벤트 게시는 무료로 제공되지 않으며 사소한 응용 프로그램에서는 그 수를 최소화하고 싶지만 너무 크지 않도록하려면 발표자에서 논리적으로 최소화 / 응축을 수행해야합니다.
Ordous

1
@Ordous 스레드 핸드 오프를 뷰에 배치하면서 게시가 최소화되도록 할 수 있습니다.
durron597

2
나는이 문제에 대해 토론하는 정말 좋은 블로그를 읽었습니다. 기본적으로 말하는 것은 가능한 교착 상태를 일으키고 구현 방법에 따라 경쟁 조건에 UI 키트 스레드를 안전하게 만들고 시도하는 것이 매우 위험하다는 것입니다. 뼈대. 성능 고려 사항도 있습니다. 그다지 많지는 않지만 Swing이 처음 릴리스되었을 때 성능이 나쁘다고 비판을 받았지만 실제로는 Swing의 잘못이 아니며 사용 방법에 대한 지식이 부족했습니다.
MadProgrammer

1
SWT는 위반하지 않을 경우 예외를 던지면서 스레드 안전 개념을 시행하지만, 적어도 사용자는이를 알고 있습니다. Swing에서 JavaFX로 변경하는 것을 언급했지만 거의 모든 UI 프레임 워크 에서이 문제가 발생하지만 Swing은 문제를 강조하는 것으로 보입니다. UI 호출이 올바르게 동기화되도록하는 중간 계층 (컨트롤러의 컨트롤러?)을 설계 할 수 있습니다. UI API의 관점에서 API의 UI가 아닌 부분을 어떻게 디자인 할 수 있는지 정확히 아는 것은 불가능합니다
MadProgrammer

1
대부분의 개발자는 UI API에 구현 된 모든 스레드 보호 기능이 제한적이거나 요구 사항을 충족하지 못한다고 불평합니다. 필요에
따라이

답변:


22

실패한 꿈의 에세이 가 끝날 무렵, 주요 Java 아키텍트 인 Graham Hamilton은 개발자가 "이벤트 큐 모델과 동등성을 유지해야한다면 다양한 명백한 규칙을 따라야하며" 이벤트 큐 모델은 "사람들이 모델을보다 안정적으로 따르고 안정적으로 작동하는 GUI 프로그램을 구성하는 데 도움이되는 것 같습니다."

다시 말해서, 이벤트 큐 모델 위에 멀티 스레드 파사드를 배치하려고하면 추상화가 디버그하기 매우 어려운 명백하지 않은 방식으로 누수되는 경우가 있습니다. 그것은 종이에서 작동하는 것처럼 보이지만 생산에서 떨어지게됩니다.

작업자 스레드에서 진행률 표시 줄을 업데이트하는 것과 같이 단일 구성 요소 주위에 작은 래퍼를 추가해도 문제가되지 않습니다. 다중 잠금이 필요한보다 복잡한 작업을 수행하려고하면 멀티 스레드 계층과 이벤트 큐 계층이 상호 작용하는 방식에 대해 추론하기가 실제로 어려워지기 시작합니다.

이러한 종류의 문제는 모든 GUI 툴킷에 공통적입니다. 발표자 / 컨트롤러에서 이벤트 디스패치 모델을 가정한다고해서 특정 GUI 툴킷의 동시성 모델 하나에 만 밀접하게 연결되는 것은 아니며, 모든 모델에 연결 됩니다 . 이벤트 큐잉 인터페이스는 추상화하기 어렵지 않아야합니다.


25

GUI lib 스레드를 안전하게 만드는 것은 엄청난 두통과 병목 현상이 있기 때문입니다.

GUI의 제어 플로우는 종종 이벤트 큐에서 루트 창으로, GUI 위젯으로, 애플리케이션 코드에서 위젯으로 루트 창으로 전파되는 두 방향으로 진행됩니다.

루트 창을 잠그지 않는 잠금 전략을 세우는 것은 어렵습니다 ( 많은 경합이 발생할 수 있습니다) . 다른 나사산이 하향식으로 고정 된 상태에서 바닥을 잠그는 것은 즉각적인 교착 상태를 달성하는 좋은 방법입니다.

그리고 현재 스레드가 GUI 스레드인지 확인하면 매번 비용이 많이 들며 특히 읽기 업데이트 쓰기 시퀀스를 수행 할 때 실제로 GUI에서 발생하는 상황에 혼란을 줄 수 있습니다. 경쟁을 피하려면 데이터를 잠 가야합니다.


1
흥미롭게도 스레드 핸드 오프를 관리하는이 업데이트에 대한 큐 프레임 워크를 사용하여이 문제를 해결합니다.
durron597

2
@ durron597 : 그리고 다른 스레드가 영향을 줄 수있는 UI의 현재 상태에 따라 어떤 업데이트도 없습니까? 그런 다음 작동 할 수 있습니다.
중복 제거기

중첩 잠금이 필요한 이유는 무엇입니까? 자식 창의 세부 정보를 작업 할 때 전체 루트 창을 잠 가야하는 이유는 무엇입니까? 잠금 순서는 다중 순서 잠금을 올바른 순서 (하향식 또는 상향식이지만 호출자에게 선택하지 않음)로 잠그는 단일 노출 방법을 제공함으로써 해결할 수있는 문제입니다.
MSalters

1
루트가있는 @MSalters는 현재 창을 의미합니다. 확보해야 할 모든 잠금을 얻으려면 상위를 가져오고 잠금을 해제 (상단 잠금 만 보장하기 위해) 할 때 각 컨테이너를 잠글 필요가있는 계층을 걸어야합니다. 루트 창을 얻은 후 잠금 하향식을 수행합니다.
ratchet freak

@ratchetfreak : 잠그는 동안 잠그는 자식이 다른 스레드에 의해 제거되는 경우 약간 불행하지만 잠금과 관련이 없습니다. 다른 스레드가 방금 제거한 개체에서는 작동 할 수 없습니다. 그러나 왜 다른 스레드가 스레드에서 여전히 사용중인 객체 / 창을 제거합니까? UI뿐만 아니라 어떤 시나리오에서도 좋지 않습니다.
MSalters

17

스레드 공유 (공유 메모리 모델에서)는 추상화 노력을 무시하는 경향이있는 속성입니다. 간단한 예는 인 Set타입 : 동안 Contains(..)Add(...)Update(...)단일 스레드 시나리오에서 완벽하게 유효한 API는 멀티 스레드 시나리오가 필요하다 AddOrUpdate.

UI에도 동일하게 적용됩니다. 목록 상단에 항목 개수가있는 항목 목록을 표시하려면 모든 변경 시마다 두 항목을 모두 업데이트해야합니다.

  1. 잠금으로 인해 작업 순서가 올바른지 확인하지 않으므로 툴킷은 해당 문제점을 해결할 수 없습니다.
  2. 보기는 문제점을 해결할 수 있지만 목록의 맨 위에있는 숫자가 목록의 항목 수와 일치해야하는 비즈니스 규칙을 허용하고보기를 통해서만 목록을 업데이트하는 경우에만 가능합니다. MVC가 정확히 무엇인지는 아닙니다.
  3. 발표자는이 문제를 해결할 수 있지만 스레딩과 관련하여보기에 특별한 요구가 있음을 알고 있어야합니다.
  4. 멀티 스레딩 가능 모델에 대한 데이터 바인딩은 또 다른 옵션입니다. 그러나 그것은 UI를 염두에 두어야 할 것들로 모델을 복잡하게 만듭니다.

그중 어느 것도 실제로 유혹적으로 보이지 않습니다. 발표자가 스레딩 처리를 담당하도록하는 것이 좋지는 않지만 작동하기 때문에 대안이 더 나쁘기 때문에 권장됩니다.


View와 Presenter 사이에 스왑 아웃 될 수있는 레이어가있을 수 있습니다.
durron597

2
.NET은 System.Windows.Threading.DispatcherWPF와 WinForms UI-Threads로 디스패치를 ​​처리합니다. 발표자와 뷰 사이의 레이어는 확실히 유용합니다. 그러나 스레드 독립성이 아닌 툴킷 독립성 만 제공합니다.
패트릭

9

나는 얼마 전에이 문제에 대해 논의하는 정말 좋은 블로그를 읽었습니다 (Karl Bielefeldt가 언급 함). 기본적으로 말하는 것은 가능한 교착 상태를 소개하고 그것이 어떻게 작동하는지에 따라 UI 키트 스레드를 안전하게 시도하는 것이 매우 위험하다는 것입니다 프레임 워크에 경쟁 조건을 구현했습니다.

성능 고려 사항도 있습니다. 그다지 많지는 않지만 Swing이 처음 릴리스되었을 때 성능이 나쁘다고 비판을 받았지만 실제로는 Swing의 잘못이 아니 었으며 사용 방법에 대한 지식이 부족했습니다.

SWT는 위반하지 않을 경우 예외를 던지면서 스레드 안전 개념을 시행하지만, 적어도 사용자는이를 알고 있습니다.

예를 들어 페인팅 프로세스를 살펴보면 요소가 페인팅되는 순서가 매우 중요합니다. 한 구성 요소의 그림이 화면의 다른 부분에 부작용을 갖지 않기를 바랍니다. 레이블의 텍스트 속성을 업데이트 할 수 있지만 두 개의 다른 스레드로 그려진 경우 결과가 손상 될 수 있습니다. 따라서 모든 페인팅은 일반적으로 요구 사항 / 요청 순서에 따라 단일 스레드 내에서 수행되지만 실제 실제 페인트주기 수를 줄이기 위해 압축되는 경우도 있습니다.

Swing에서 JavaFX로 변경하는 것을 언급했지만 두꺼운 UI 클라이언트뿐만 아니라 웹과 같은 모든 UI 프레임 워크 에서이 문제가 발생할 수 있습니다 .Swing은 문제를 강조하는 것으로 보입니다.

UI 호출이 올바르게 동기화되도록하는 중간 계층 (컨트롤러의 컨트롤러?)을 설계 할 수 있습니다. UI API의 관점에서 API의 UI가 아닌 부분을 어떻게 설계하는지 정확히 알 수는 없으며 대부분의 개발자는 UI API에 구현 된 스레드 보호가 제한적이거나 요구 사항을 충족하지 않았다고 불평 할 것입니다. 필요에 따라이 문제를 해결하는 방법을 결정할 수 있도록하는 것이 좋습니다.

고려해야 할 가장 큰 문제 중 하나는 알려진 입력을 기반으로 주어진 이벤트 순서를 정당화하는 기능입니다. 예를 들어, 사용자가 창의 크기를 조정하는 경우 Event Queue 모델은 주어진 이벤트 순서가 발생 함을 보장하지만 간단 해 보일 수 있지만 큐가 다른 스레드에 의해 이벤트가 트리거되도록 허용하면 더 이상 순서를 보장 할 수 없습니다. 이벤트가 발생할 수 있습니다 (경쟁 조건) 그리고 갑자기 다른 상태에 대해 걱정하고 다른 일이 발생할 때까지 한 가지 일을하지 않아야하며 주위에 상태 플래그를 공유해야 스파게티가 생깁니다.

좋아, 당신은 이벤트가 발행 된 시간을 기반으로 이벤트를 주문한 일종의 대기열을 가짐 으로써이 문제를 해결할 수 있지만 이미 가지고 있지 않은가? 또한 스레드 B가 스레드 A 후에 이벤트를 생성한다는 것을 여전히 보장 할 수는 없습니다.

사람들이 자신의 코드에 대해 생각해야하는 것에 대해 화를내는 주된 이유는 코드 / 디자인에 대해 생각하기 때문입니다. "왜 더 간단 할 수 없습니까?" 단순한 문제가 아니기 때문에 더 간단 할 수 없습니다.

PS3가 출시되었을 때와 소니가 셀 프로세서에 대해 이야기를 나 and 고 별도의 로직 라인을 수행하고 오디오, 비디오를 디코딩하고 모델 데이터를로드 및 분석하는 기능을 기억합니다. 한 게임 개발자가 물었습니다. "모두 훌륭하지만 스트림을 어떻게 동기화합니까?"

개발자가 말한 문제는 언젠가는 모든 별도의 스트림을 출력을 위해 단일 파이프로 동기화해야한다는 것입니다. 가난한 발표자는 익숙한 문제가 아니기 때문에 어깨를 으 ged했다. 분명히, 그들은 지금이 문제를 해결하기위한 해결책을 가지고 있었지만 당시에는 재미있었습니다.

현대 컴퓨터는 여러 장소에서 동시에 많은 정보를 입수하고 있습니다.이 모든 정보는 다른 정보의 표시를 방해하지 않는 입력을 처리하고 사용자에게 멀리 전달해야하기 때문에 복잡한 문제가 없습니다. 하나의 간단한 솔루션.

이제는 프레임 워크를 전환 할 수있는 기능이 있습니다.하지만 설계하기 쉽지는 않지만 MVC를 잠시 사용하고 MVC를 다중 계층화 할 수 있습니다. 즉, UI 프레임 워크 관리를 직접 처리하는 MVC를 가질 수 있습니다. 그런 다음 다른 (잠재적으로 다중 스레드) 프레임 워크와의 상호 작용을 처리하는 상위 계층 MVC에서 다시 래핑 할 수 있으므로 하위 MVC 계층에 알림 / 업데이트되는 방법을 결정하는 것은이 계층의 책임입니다.

그런 다음 코딩을 사용하여 디자인 패턴과 팩토리 또는 빌더 패턴을 인터페이스하여 이러한 다른 레이어를 구성합니다. 즉, 다중 스레드 프레임 워크는 아이디어로 중간 계층을 사용하여 UI 계층에서 분리됩니다.


2
웹에서는이 문제가 발생하지 않습니다. JavaScript는 의도적으로 스레딩을 지원하지 않습니다. JavaScript 런타임은 사실상 하나의 큰 이벤트 큐입니다. (예, 엄격히 말해서 JS에는 WebWorkers가 있다는 것을 알고 있습니다. 이것들은 너프 스레드이며 다른 언어의 배우처럼 행동합니다.)
James_pic

1
@James_pic 당신이 실제로 가지고있는 것은 브라우저가 이벤트 큐의 동기화 역할을하는 부분입니다. 기본적으로 우리가 이야기했던 것입니다. 호출자는 툴킷 이벤트 큐에서 업데이트가 발생했는지 확인해야합니다
MadProgrammer

예, 정확히 웹과 관련하여 중요한 차이점은 런타임은 이벤트 큐 외부에서 코드를 실행할 수있는 메커니즘을 제공하지 않기 때문에 호출자의 코드에 관계없이이 동기화가 발생한다는 것입니다. 따라서 발신자는 이에 대해 책임을지지 않아도됩니다. 런타임 환경이 이벤트 루프 인 경우 기본적으로 모든 코드는 이벤트 루프를 인식합니다.
James_pic

1
즉, 자체 이벤트 루프 내 이벤트 루프가있는 브라우저 UI 프레임 워크가 있습니다 (Angular를보고 있습니다). 이러한 프레임 워크는 더 이상 런타임에 의해 보호되지 않기 때문에 호출자는 다른 프레임 워크의 멀티 스레드 코드와 마찬가지로 이벤트 루프 내에서 코드가 실행되도록해야합니다.
James_pic

"디스플레이 전용"컨트롤이 스레드 안전성을 자동으로 제공하는 데 특별한 문제가 있습니까? 하나의 스레드에 의해 작성되고 다른 스레드에 의해 읽히는 편집 가능한 텍스트 제어가있는 경우, 이러한 조치 중 하나 이상을 제어의 실제 UI 상태와 동기화하지 않고 스레드에 쓰기를 표시 할 수있는 좋은 방법은 없습니다. 그러나 디스플레이 전용 컨트롤에서 이러한 문제가 중요합니까? 나는 그것들이 그들에게 수행되는 오퍼레이션들에 필요한 잠금 또는 인터록을 쉽게 제공 할 수 있고 호출자가 언제 타이밍을 무시할 수 있다고 생각할 것이다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.