PySide2 QThread

Evenv loop

Being an event-driven toolkit, events and event delivery play a central role in Qt architecture.

이벀트 λ£¨ν”„μ˜ 컨셉은 μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

while is_active:
while not event_queue_is_empty:
"""
event_queue 확인
event 처리
"""
dispatch_next_event()
# eventκ°€ λ°œμƒν•  λ•ŒκΉŒμ§€ λŒ€κΈ°
wait_for_more_events()

ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰μ‹œν‚€λ©΄ μŠ€νƒμ΄ μ•„λž˜ λ°©ν–₯으둜 증가할 λ•Œ, μ•„λž˜μ™€ 같은 λ°©μ‹μœΌλ‘œ μŠ€νƒμ΄ μŒ“μž…λ‹ˆλ‹€.

  1. main(int, char *)
  2. QApplication::exec()
  3. […]
  4. QWidget::event(QEvent *)
  5. Button::mousePressEvent(QMouseEvent *)
  6. Button::clicked()
  7. […]
  8. Worker::doWork()

QApplication::exec()에 μ˜ν•΄ 메인 이벀트 루프가 μ‹€ν–‰λ©λ‹ˆλ‹€. 이벀트 루프가 μ‹€ν–‰λ˜λŠ” λ™μ•ˆ 마우슀둜 λ²„νŠΌμ„ ν΄λ¦­ν•˜λ©΄ μ λ‹Ήν•œ μ ˆμ°¨μ— 따라 QWidget::event(QEvent *)λ₯Ό ν˜ΈμΆœν•˜κ²Œ λ©λ‹ˆλ‹€. 마우슀 ν΄λ¦­μ΄λΌλŠ” 것을 νŒλ³„ν•˜μ—¬ Button::mousePressEvent(QMouseEvent *)λ₯Ό ν˜ΈμΆœν•˜κ³ , Button::clicked() μ‹œκ·Έλ„μ„ λ°œμƒμ‹œν‚΅λ‹ˆλ‹€. μ΅œμ’…μ μœΌλ‘œ Worker::doWork() μŠ¬λ‘―μ„ ν˜ΈμΆœν•˜μ—¬ 정해진 일을 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

μ—¬κΈ°μ„œ doWork()κ°€ 정해진 일을 μ²˜λ¦¬ν•˜λŠ”λ° 였랜 μ‹œκ°„μ΄ κ±Έλ¦°λ‹€λ©΄ 메인 이벀트 루프가 블둝킹 λ‹Ήν•˜κ²Œ λ˜λ©΄μ„œ ν™”λ©΄ κ°±μ‹ , 타이머, λ„€νŠΈμ›Œν¬ 톡신 등에 문제λ₯Ό λ°œμƒμ‹œν‚΅λ‹ˆλ‹€. 이 μƒνƒœκ°€ 길어지면 λŒ€λΆ€λΆ„ μœˆλ„μš° λ§€λ‹ˆμ €λ“€μ€ μ‘μš©ν”„λ‘œκ·Έλž¨μ΄ μ‘λ‹΅ν•˜μ§€ μ•ŠμŒμ„ μ‚¬μš©μžμ—κ²Œ μ•Œλ €μ£Όκ²Œ λ©λ‹ˆλ‹€.

λ”°λΌμ„œ μ²˜λ¦¬ν•  μ‹œκ°„μ΄ 많이 ν•„μš”ν•˜λ”λΌλ„ 메인 이벀트 λ£¨ν”„λ‘œ 빨리 λŒμ•„κ°ˆ 수 μžˆλ„λ‘ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Όν•©λ‹ˆλ‹€.

Forcing event dispatching

QCoreApplication::processEvents()

μ‹œκ°„μ΄ 였래 걸릴 κ²ƒμœΌλ‘œ μ˜ˆμƒλ˜λŠ” μž‘μ—…μ΄ μžˆλ‹€λ©΄, QCoreApplication::processEvents()λ₯Ό μž‘μ—… 쀑간에 ν˜ΈμΆœν•˜μ—¬ μž‘μ—…μ„ μ€‘λ‹¨ν•˜κ³  λ‹€λ₯Έ μ΄λ²€νŠΈκ°€ μžˆλŠ” 지 확인 ν›„ 끝내고 λŒμ•„μ™€μ„œ 쀑단 지점 λΆ€ν„° μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ 일을 μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

info

processEvent()λ₯Ό 톡해 이벀트 루프에 μ§„μž…ν–ˆμ„ λ•Œ, processEvent()λ₯Ό ν¬ν•¨ν•œ 이벀트λ₯Ό ν˜ΈμΆœν•˜λ©΄ λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. QEventLoop::ExcludeUserInputEvents 등을 μ‚¬μš©ν•˜μ—¬ λ°œμƒν•  수 μžˆλŠ” 문제λ₯Ό ν”Όν•΄μ•Ό ν•©λ‹ˆλ‹€.

QEventLoop::exec()

논블둝킹 API의 경우 순차적으둜 일을 μ²˜λ¦¬ν•΄μ•Όν•  λ•Œ, μž‘μ—…μ€ μ—†μ§€λ§Œ 기타 이유둜 μ‹œκ°„ 지연이 ν•„μš”ν•œ κ²½μš°κ°€ μžˆμŠ΅λ‹ˆλ‹€. A 일이 λλ‚œ ν›„ B 일을 ν•΄μ•Όν•  λ•Œ, A 일이 λλ‚˜λŠ” μ‹œκ·Έλ„μ„ QEventLoop::quit() μŠ¬λ‘―μ— μ—°κ²°ν•œ ν›„ QEventLoop::exec()λ₯Ό 톡해 κ°•μ œλ‘œ 이벀트 λ£¨ν”„λ‘œ μž¬μ§„μž…ν•˜λŠ” 방법이 μžˆμŠ΅λ‹ˆλ‹€.

이 방법을 μ‚¬μš©ν•˜λ©΄ A κ°€ λλ‚˜λŠ” 것을 κΈ°λ‹€λ¦¬λŠ” λ™μ•ˆ λ“€μ–΄μ˜€λŠ” 이벀트λ₯Ό μ²˜λ¦¬ν•˜κ³  λλ‚˜λ©΄ λ°”λ‘œ Bλ₯Ό μ²˜λ¦¬ν•  수 μžˆλ„λ‘ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

QThread

Forcing event dispatching 방법을 μ μš©ν•˜κΈ° μ–΄λ €μš΄ 경우 μ“°λ ˆλ“œλ₯Ό μΆ”κ°€ν•˜μ—¬ 처리 μ‹œκ°„μ΄ κΈΈ κ²ƒμœΌλ‘œ μ˜ˆμƒλ˜λŠ” 이벀트 μ²˜λ¦¬λŠ” μΆ”κ°€λœ μ“°λ ˆλ“œμ— 맑기고, 이벀트 λ£¨ν”„λ‘œ λŒμ•„κ°€μ„œ λ‹€λ₯Έ μ΄λ²€νŠΈλ“€μ„ μ²˜λ¦¬ν•˜λŠ” 방법이 μžˆμŠ΅λ‹ˆλ‹€.

info

Python의 GIL둜 인해 메인 μ“°λ ˆλ“œ 외에 QThreadμ—μ„œ QThread.msleep 없이 연속적인 연산이 μ΄μ–΄μ§€λŠ” 경우 메인 μ“°λ ˆλ“œκ°€ 블둝킹 λ‹Ήν•  수 μžˆμŠ΅λ‹ˆλ‹€.

QThread와 QObject

Reentrant(μž¬μ§„μž…) : A class is reentrant if it's safe to use its instances from more than one thread, provided that at most one thread is accessing the same instance at the same time. A function is reentrant if it's safe to invoke it from more than one thread at the same, provided that each invocation references unique data. In other words, this means that users of that class/function must serialize all accesses to instances/shared data by means of some external locking mechanism.

Thread-safe(μ“°λ ˆλ“œ μ•ˆμ „) : A class is thread-safe if it's safe to use its instances from more than one thread at the same time. A function is thread-safe if it's safe to invoke it from more than one thread at the same time even if the invocations reference shared data.

ν•œ μ“°λ ˆλ“œμ˜ 이벀트 λ£¨ν”„λŠ” 이벀트λ₯Ό ν•΄λ‹Ή μ“°λ ˆλ“œμ— μ‚΄μ•„κ°€λŠ” λͺ¨λ“  QObject에 μ „λ‹¬ν•©λ‹ˆλ‹€. QObject의 μ“°λ ˆλ“œ μΉœν™”λ„(thread affinity)λŠ” ν•΄λ‹Ή 객체가 μ‚΄μ•„κ°€λŠ” μ“°λ ˆλ“œλ₯Ό λ§ν•©λ‹ˆλ‹€. μ“°λ ˆλ“œ μΉœν™”λ„λŠ” QObject::thread()λ₯Ό 톡해 μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. QCoreApplication 객체보닀 μ•žμ„œ μƒμ„±λœ κ°μ²΄λŠ” μ“°λ ˆλ“œ μΉœν™”λ„κ°€ μ—†μŠ΅λ‹ˆλ‹€.

QObject와 QObject의 νŒŒμƒ ν΄λž˜μŠ€λŠ” μ“°λ ˆλ“œ μ•ˆμ „ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 객체 λ‚΄λΆ€μ˜ 데이터 접근을 QMutex 등을 μ‚¬μš©ν•˜μ—¬ μ§λ ¬ν™”ν•˜μ§€ μ•ŠλŠ” λ‹€λ©΄ μ—¬λŸ¬ μ“°λ ˆλ“œμ—μ„œ ν•˜λ‚˜μ˜ QObject에 μ ‘κ·Όν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€.

QObjectκ°€ μ‚΄μ•„κ°€λŠ” μ“°λ ˆλ“œλ₯Ό μ œμ™Έν•œ λ‹€λ₯Έ μ“°λ ˆλ“œμ—μ„œ ν• λ‹Ήν•΄μ œλ₯Ό ν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€.(μ“°λ ˆλ“œ μ•ˆμ „) λ‹€λ₯Έ μ“°λ ˆλ“œμ—μ„œ ν• λ‹Ήν•΄μ œλ₯Ό ν•˜λ €λ©΄ QObject::deleteLater()λ₯Ό μ‚¬μš©ν•˜μ—¬ μ“°λ ˆλ“œ μΉœν™”λ„μ— λ§žλŠ” 이벀트 큐에 ν• λ‹Ήν•΄μ œ 이벀트λ₯Ό λ“±λ‘ν•˜κ³  ν•΄λ‹Ή 이벀트 λ£¨ν”„μ—μ„œ μ²˜λ¦¬ν•˜λ©΄ λ©λ‹ˆλ‹€.

QWidget, QWidget의 νŒŒμƒ 클래슀 λ“± GUI κ΄€λ ¨ ν΄λž˜μŠ€λŠ” μž¬μ§„μž…μ΄ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 메인 μ“°λ ˆλ“œμ—μ„œλ§Œ μ‚¬μš©λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.

QObject::moveToThread()λ₯Ό μ‚¬μš©ν•˜μ—¬ μ‚΄μ•„κ°€λŠ” μ“°λ ˆλ“œμ•ˆμ—μ„œ λ‹€λ₯Έ μ“°λ ˆλ“œλ‘œ μ“°λ ˆλ“œ μΉœν™”λ„λ₯Ό λ°”κΏ€ 수 μžˆμŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ μ“°λ ˆλ“œμ—μ„œ μ“°λ ˆλ“œ μΉœν™”λ„λ₯Ό λ°”κΏ€ μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€. λΆ€λͺ¨λ₯Ό 가진 객체에 λŒ€ν•΄ μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

info

μ•„λž˜ μ“°λ ˆλ“œκ°„μ˜ μ‹œκ·Έλ„κ³Ό μŠ¬λ‘―μ„ 읽으면 μ•Œκ² μ§€λ§Œ μ“°λ ˆλ“œμ˜ μŠ¬λ‘―μ€ μ“°λ ˆλ“œ 객체λ₯Ό μƒμ„±ν•˜λŠ” event loop에 있게 λ©λ‹ˆλ‹€. direct connection이 λ°œμƒν•  수 μžˆλŠ”λ°, 이λ₯Ό ν”Όν•˜κΈ° μœ„ν•΄ moveToThread(this)λ₯Ό μ“°κ²Œλ˜λ©΄ μ“°λ ˆλ“œ 객체λ₯Ό λ‹€λ₯Έ μ“°λ ˆλ“œμ—μ„œ μ œμ–΄ν•˜κΈ° μ–΄λ €μ›Œμ§€κΈ° λ•Œλ¬Έμ— ν”Όν•΄μ•Όν•©λ‹ˆλ‹€.

QThread μžμ‹ μ„ λΆ€λͺ¨λ‘œ ν•˜λŠ” 객체λ₯Ό QThread 내에 λ§Œλ“€ 수 μ—†μŠ΅λ‹ˆλ‹€. QThread κ°μ²΄λŠ” λ‹€λ₯Έ μ“°λ ˆλ“œμ— μ‚΄μ•„κ°€κ³  있기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

QThread 객체λ₯Ό ν• λ‹Ήν•΄μ œν•˜κΈ° 전에 내뢀에 μ‚΄μ•„κ°€λŠ” λͺ¨λ“  객체가 λ¨Όμ € 사라져야 ν•©λ‹ˆλ‹€. μ΄λŠ” QThread::run() 의 μŠ€νƒμ—μ„œ ν•΄λ‹Ή μ“°λ ˆλ“œμ— μ‚΄μ•„κ°€λŠ” λͺ¨λ“  객체λ₯Ό μƒμ„±ν•˜λ©΄ μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ“°λ ˆλ“œκ°„μ˜ μ‹œκ·Έλ„κ³Ό 슬둯

  • direct connection : μ‹œκ·Έλ„μ΄ λ°œμƒλœ μ“°λ ˆλ“œμ—μ„œ 직접 μŠ¬λ‘―μ„ 호좜
  • queued connection : μˆ˜μ‹ μΈ‘ μ“°λ ˆλ“œμ˜ 이벀트 큐에 μ΄λ²€νŠΈκ°€ μΆ”κ°€λ˜κ³ , λ‚˜μ€‘μ— 이벀트 λ£¨ν”„μ—μ„œ 빠질 λ•Œ 슬둯 호좜
  • blocking queued connection : queued connection + μ‹œκ·Έλ„ λ°œμƒ μ“°λ ˆλ“œκ°€ 슬둯의 호좜 μ’…λ£Œ μ‹œκΉŒμ§€ 블둝킹 됨
  • automatic connection : μ‹œκ·Έλ„ λ°œμƒ μ“°λ ˆλ“œμ™€ 슬둯 μ‹€ν–‰ μ“°λ ˆλ“œλ₯Ό λΉ„κ΅ν•˜μ—¬ direct or queued connection

QThread νŒŒμƒ 클래슀 Aλ₯Ό λ§Œλ“€ λ•Œ, 내뢀에 μ‹œκ·Έλ„κ³Ό μŠ¬λ‘―μ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. 그런 경우 μ‹œκ·Έλ„κ³Ό μŠ¬λ‘―μ€ μœ„ κ·Έλ¦Όμ—μ„œ 빨간색 원에 μœ„μΉ˜ν•˜κ²Œ λ©λ‹ˆλ‹€.

Main에도 μ‹œκ·Έλ„κ³Ό μŠ¬λ‘―μ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. 이 κ²½μš°μ—λŠ” μ‹œκ·Έλ„κ³Ό μŠ¬λ‘―μ€ νŒŒλž€ 원에 μœ„μΉ˜ν•˜κ²Œ λ©λ‹ˆλ‹€.

Main의 μ‹œκ·Έλ„μ„ A의 μŠ¬λ‘―μ— μ—°κ²°ν•˜λ©΄ direct connection이 λ©λ‹ˆλ‹€. A의 μŠ¬λ‘―μ€ Main event loopμ—μ„œ μ‹€ν–‰λ©λ‹ˆλ‹€. A μ“°λž˜λ“œ 객체의 λ³€μˆ˜λŠ” Main event loopμ—μ„œ μ‹€ν–‰λ˜λŠ” A의 μŠ¬λ‘―μ—μ„œ 접근이 κ°€λŠ₯ν•˜κ³ , A event loopμ—μ„œλ„ λ™μ‹œμ— 접근이 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

info

QThread의 νŒŒμƒ ν΄λž˜μŠ€μ— μŠ¬λ‘―μ„ μƒμ„±ν•˜λ©΄ direct connection에 μ˜ν•œ λ¬Έμ œκ°€ λ°œμƒν•  ν™•λ₯ μ΄ λ†’κΈ° λ•Œλ¬Έμ— μ‘°μ‹¬ν•΄μ•Όν•©λ‹ˆλ‹€.

A의 μ‹œκ·Έλ„μ΄λ‚˜ μŠ¬λ‘―μ€ Main λ˜λŠ” A event loop μ–΄λ””μ—μ„œλ‚˜ 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€. μ—°κ²° μ’…λ₯˜λ₯Ό κ²°μ •ν•˜λŠ” 것은 μ‹œκ·Έλ„κ³Ό 슬둯의 μœ„μΉ˜κ°€ μ•„λ‹ˆλΌ μ‹€ν–‰λ˜λŠ” loopκ°€ 같은지 λ‹€λ₯Έμ§€μ— 따라 κ²°μ •λ©λ‹ˆλ‹€.

Main의 슬둯과 A의 μ‹œκ·Έλ„μ„ μ—°κ²°ν–ˆμ„ λ•Œ, A의 μ‹œκ·Έλ„μ΄ νŒŒλž€ μ›μ—μ„œ λ°œμƒν•˜λ©΄ direct, μ£Όν™© μ›μ—μ„œ λ°œμƒν•˜λ©΄ queuedκ°€ λ©λ‹ˆλ‹€.

기타

QThread::terminate μ‚¬μš©μ€ ν”Όν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ½”λ“œκ°€ μ‹€ν–‰λ˜λŠ” 쀑에 μ€‘λ‹¨λ˜κΈ° λ•Œλ¬Έμ— λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. κΌ­ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©ν•  것을 ꢌμž₯ν•©λ‹ˆλ‹€.

μ“°λ ˆλ“œκ°€ ꡬ동 쀑일 λ•Œ, ν”„λ‘œκ·Έλž¨μ„ μ’…λ£Œν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€. QThread::wait을 μ‚¬μš©ν•΄ μ’…λ£Œλ₯Ό λŒ€κΈ°ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μ“°λ ˆλ“œκ°€ ꡬ동 쀑일 λ•Œ, μ“°λ ˆλ“œ 객체λ₯Ό νŒŒκ΄΄ν•˜λ©΄ μ•ˆλ©λ‹ˆλ‹€. QThread::finished() μ‹œκ·Έλ„μ„ QObject::deleteLater() 슬둯과 μ—°κ²°ν•˜μ—¬ μ“°λ ˆλ“œκ°€ 끝났을 λ•Œ, μžλ™μœΌλ‘œ νŒŒκ΄΄λ„λ„λ‘ μ„€κ³„ν•΄μ•Όν•©λ‹ˆλ‹€.

Examples

import sys
from PySide2.QtWidgets import QMainWindow, QApplication
from PySide2.QtCore import QThread, Slot, Signal
from ui_mainwindow import Ui_MainWindow
class Background_thread(QThread):
"""
QThread νŒŒμƒ 클래슀
"""
signal_in_a = Signal(str)
def __init__(self):
super().__init__()
def run(self):
while True:
self.msleep(1000)
"""
signal : A event loop -> main event loop
queued connection
"""
self.signal_in_a.emit("A event loop")
class Main_window(QMainWindow, Ui_MainWindow):
_background_thread = Background_thread()
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.push_button_clicked)
self.pushButton_2.clicked.connect(self.push_button_2_clicked)
self._background_thread.signal_in_a.connect(self.update_text_browser)
if not self._background_thread.isRunning():
self._background_thread.start()
def push_button_clicked(self):
"""
μ“°λ ˆλ“œκ°€ μ‹€ν–‰ 쀑이지 μ•Šλ‹€λ©΄ μ‹€ν–‰
"""
if not self._background_thread.isRunning():
self._background_thread.start()
def push_button_2_clicked(self):
"""
signal : main event loop -> main event loop
direct connection
"""
self._background_thread.signal_in_a.emit("main event loop")
"""
문제 terminate μ‚¬μš©μœΌλ‘œ μΈν•œ λ¬Έμ œκ°€ λ°œμƒν•  수 있음.
"""
self._background_thread.terminate()
@Slot(str)
def update_text_browser(self, text):
"""
main event loopμ—μ„œ μ‹€ν–‰λ˜λŠ” 슬둯
"""
self.textBrowser.append("signal from " + text)
if __name__ == "__main__":
app = QApplication(sys.argv)
main_window = Main_window()
main_window.show()
app_return = app.exec_()
"""
μ’…λ£Œ μ „ μ“°λ ˆλ“œ κ°•μ œ μ’…λ£Œ
"""
main_window._background_thread.terminate()
sys.exit(app_return)

Reference

Last updated on