์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

Python multiprocessing ๊ธฐ๋ฐ˜ ๊ณ„์ธก๊ธฐ SCPI ์ œ์–ด ์‹œ ์‹œ๋ฆฌ์–ผ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ์™€ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ณ‘๋ชฉ ํ•ด๊ฒฐํ•˜๊ธฐ

๊ฒŒ์ž„๊ต์ˆ˜ 2026. 8. 25. 01:01
๋ฐ˜์‘ํ˜•

๐ŸŒ English Abstract

This article addresses critical performance bottlenecks in Python-based test and measurement automation systems utilizing SCPI (Standard Commands for Programmable Instruments) over serial interfaces. It provides an in-depth analysis of hardware RX/TX buffer overflows and inter-process communication (IPC) latency caused by multiprocessing overhead. Readers will learn practical refactoring strategies including single-producer serial architectures, non-blocking I/O ring buffers, and zero-copy shared memory synchronization.

ํ•˜๋“œ์›จ์–ด ๋ฐ ๋ฐ˜๋„์ฒด ๊ฒ€์‚ฌ ์žฅ๋น„ ์ž๋™ํ™” ๋ถ„์•ผ์—์„œ ํŒŒ์ด์ฌ(Python)์€ PyVISA, PySerial๊ณผ ๊ฐ™์€ ํ’๋ถ€ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ณ„์ธก๊ธฐ ์ œ์–ด์˜ ํ‘œ์ค€ ํ‘œ์ค€ ์–ธ์–ด๋กœ ์ž๋ฆฌ ์žก์•˜์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ์˜ค์‹ค๋กœ์Šค์ฝ”ํ”„(Oscilloscope), ์ „์› ๊ณต๊ธ‰ ์žฅ์น˜(Power Supply), ๋ฉ€ํ‹ฐ๋ฏธํ„ฐ(DMM) ๋“ฑ ์ˆ˜๋งŽ์€ ๊ณ„์ธก๊ธฐ์™€ SCPI(Standard Commands for Programmable Instruments) ํ”„๋กœํ† ์ฝœ๋กœ ํ†ต์‹ ํ•˜๋ฉฐ ๊ณ ์† ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•  ๋•Œ, ํŒŒ์ด์ฌ์˜ GIL(Global Interpreter Lock) ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•ด multiprocessing ๋ชจ๋“ˆ์„ ๋„์ž…ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๊ณ ์† ์ƒ˜ํ”Œ๋ง ํ™˜๊ฒฝ์—์„œ ํŒŒ์ด์ฌ์˜ ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ์„ ์ž˜๋ชป ์„ค๊ณ„ํ•˜๋ฉด, ์‹œ๋ฆฌ์–ผ ํฌํŠธ(Serial Port/RS-232/USB Virtual COM) ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ๋ถ€์—์„œ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ(Buffer Overflow)๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ํ”„๋ ˆ์ž„์ด ์†์‹ค๋˜๊ฑฐ๋‚˜, ํ”„๋กœ์„ธ์Šค ๊ฐ„ ํ†ต์‹ (IPC, Inter-Process Communication) ๊ณผ์ •์—์„œ ๋ฝ(Lock) ๊ฒฝํ•ฉ ๋ฐ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ณ‘๋ชฉ ํ˜„์ƒ(Bottleneck)์ด ๋ฐœ์ƒํ•˜์—ฌ ์ „์ฒด ์‹œ์Šคํ…œ์ด ๋ฉˆ์ถ”๋Š” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ์— ์ง๋ฉดํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋ณธ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์‹œ์Šคํ…œ ๋ณ‘๋ชฉ์˜ ๊ทผ๋ณธ์ ์ธ ์›์ธ์„ ํ•˜๋“œ์›จ์–ด/OS ๋ ˆ์ด์–ด๋ถ€ํ„ฐ ํŒŒ์ด์ฌ ๋Ÿฐํƒ€์ž„ ๋ ˆ์ด์–ด๊นŒ์ง€ ์ •๋ฐ€ํ•˜๊ฒŒ ๋ถ„์„ํ•˜๊ณ , ์ด๋ฅผ ์™„๋ฒฝํžˆ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์‹ค๋ฌด ์ค‘์‹ฌ์˜ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ฐ€์ด๋“œ ๋ฐ ์•„ํ‚คํ…์ฒ˜ ๋ฆฌํŒฉํ† ๋ง ๋ฐฉ์•ˆ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.



1. ์‹œ๋ฆฌ์–ผ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ์™€ IPC ๋ณ‘๋ชฉ์˜ ์›์ธ ๋ถ„์„

ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „, ๋ฌธ์ œ์˜ ์›์ธ์ด ๋ฌผ๋ฆฌ์  ์ธํ„ฐํŽ˜์ด์Šค ๋ฒ„ํผ์—์„œ ๋ฐœ์ƒํ•˜๋Š”์ง€, ์•„๋‹ˆ๋ฉด ํŒŒ์ด์ฌ ํ”„๋กœ์„ธ์Šค ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€. ๋ฌผ๋ฆฌ์ /OS ์‹œ๋ฆฌ์–ผ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ (Hardware & OS UART Buffer Overflow)

๊ณ„์ธก๊ธฐ์—์„œ ์ „์†ก๋˜๋Š” SCPI ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ(์˜ˆ: :MEASure:VOLTage:ALL?) ๋˜๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ธ”๋ก ๋ฐ์ดํ„ฐ๋Š” ์ปค๋„์˜ UART ๋“œ๋ผ์ด๋ฒ„ ์ˆ˜์‹  FIFO(First-In-First-Out) ๋ฒ„ํผ์— ๋จผ์ € ์Œ“์ž…๋‹ˆ๋‹ค.

  • ๋“œ๋ผ์ด๋ฒ„ ๋ ˆ์ดํ„ด์‹œ ํƒ€์ž„(Latency Timer): FTDI ๋“ฑ USB-to-Serial ์นฉ์…‹์˜ ๊ธฐ๋ณธ Latency Timer ์„ค์ •๊ฐ’(๊ธฐ๋ณธ๊ฐ’ ๋ณดํ†ต 16ms)์œผ๋กœ ์ธํ•ด ๋ฐ์ดํ„ฐ ์ˆ˜์‹  ๋ธ”๋ก์ด ์ง€์—ฐ๋˜๋‹ค๊ฐ€ ํ•œ ๋ฒˆ์— ๋ชฐ๋ ค ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋ฅผ ์œ ๋ฐœํ•ฉ๋‹ˆ๋‹ค.
  • ๋…์ ์  ํŒŒ์ผ ํ•ธ๋“ค ์•ก์„ธ์Šค ๊ฒฝ์Ÿ: ์—ฌ๋Ÿฌ ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค(Child Processes)๊ฐ€ ๋™์ผํ•œ ์‹œ๋ฆฌ์–ผ ํ•ธ๋“ค(COM Port)์— ๋™์‹œ์— ์ ‘๊ทผ์„ ์‹œ๋„ํ•˜๊ฑฐ๋‚˜, ์ฝ๊ธฐ ์ž‘์—… ์‹œ ๋ถˆํ•„์š”ํ•œ ๋ธ”๋กœํ‚น I/O(Blocking I/O)๋ฅผ ์ˆ˜ํ–‰ํ•  ๊ฒฝ์šฐ ์ปค๋„ ๋ฒ„ํผ ๋น„์šฐ๊ธฐ(Flush) ์†๋„๊ฐ€ ๋ฐ์ดํ„ฐ ์œ ์ž… ์†๋„๋ฅผ ๋”ฐ๋ผ๊ฐ€์ง€ ๋ชปํ•ด Overrun Error๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๋‚˜. ํ”„๋กœ์„ธ์Šค ๊ฐ„ ํ†ต์‹ (IPC) ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ณ‘๋ชฉ

ํŒŒ์ด์ฌ์˜ multiprocessing.Queue๋‚˜ Pipe๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด๋ถ€์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์ง๋ ฌํ™”(Pickling) ๋ฐ ์—ญ์ง๋ ฌํ™”(Unpickling)ํ•ฉ๋‹ˆ๋‹ค.

  • Pickle ์ง๋ ฌํ™” ์˜ค๋ฒ„ํ—ค๋“œ: ๊ณ„์ธก๊ธฐ๋กœ๋ถ€ํ„ฐ ์ดˆ๋‹น ์ˆ˜์ฒœ ๋ฒˆ ์ˆ˜์ง‘๋˜๋Š” SCPI ์‘๋‹ต ์ŠคํŠธ๋ง ๋ฐ์ดํ„ฐ๋ฅผ ๋งค๋ฒˆ Queue.put()์œผ๋กœ ์ „๋‹ฌํ•˜๋ฉด ๋Œ€๋Ÿ‰์˜ CPU ์—ฐ์‚ฐ ์ž์›์ด Pickling์— ์†Œ๋ชจ๋ฉ๋‹ˆ๋‹ค.
  • ํŒŒ์ดํ”„ ๋ฒ„ํผ ์ถฉ์ „(Pipe Buffer Full): ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ํ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์†๋„๊ฐ€ ์ƒ์‚ฐ ํ”„๋กœ์„ธ์Šค๋ณด๋‹ค ๋А๋ฆฌ๋ฉด OS ํŒŒ์ดํ”„ ๋ฒ„ํผ๊ฐ€ ๊ฐ€๋“ ์ฐจ๋ฉด์„œ ์ƒ์‚ฐ์ž ํ”„๋กœ์„ธ์Šค๊ฐ€ Queue.put()์—์„œ ๋ฌดํ•œ ๋ธ”๋กœํ‚น(Deadlock) ์ƒํƒœ์— ๋น ์ง‘๋‹ˆ๋‹ค.

2. ์‹œ๋ฆฌ์–ผ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ํ•˜๋“œ์›จ์–ด ๋ฐ ๋“œ๋ผ์ด๋ฒ„ ์ตœ์ ํ™”

๊ฐ€์žฅ ๋จผ์ € ๋ฌผ๋ฆฌ ๋ ˆ์ด์–ด์™€ OS ์ปค๋„ ๋ ˆ๋ฒจ์—์„œ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •์„ ์ตœ์ ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€. FTDI ๋“œ๋ผ์ด๋ฒ„ Latency Timer ๋ณ€๊ฒฝ

USB-to-Serial ๋ณ€ํ™˜๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, Windows ์žฅ์น˜ ๊ด€๋ฆฌ์ž ๋˜๋Š” Linux sysfs๋ฅผ ํ†ตํ•ด Latency Timer๋ฅผ 16ms์—์„œ 1ms๋กœ ์ถ•์†Œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

# Linux ํ™˜๊ฒฝ์—์„œ FTDI Latency Timer๋ฅผ 1ms๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ํ„ฐ๋ฏธ๋„ ๋ช…๋ น
echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer

๋‚˜. ํ•˜๋“œ์›จ์–ด ํ๋ฆ„ ์ œ์–ด(RTS/CTS Flow Control) ์ ์šฉ

์†Œํ”„ํŠธ์›จ์–ด์ ์ธ XON/XOFF ๋ฐฉ์‹์€ SCPI ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ ๋‚ด๋ถ€ ๋ฌธ์ž์—ด๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋ฐ˜๋“œ์‹œ ํ•˜๋“œ์›จ์–ด RTS/CTS(Request to Send / Clear to Send) ๋ผ์ธ์„ ํ™œ์„ฑํ™”ํ•˜์—ฌ ์ˆ˜์‹  ๋ฒ„ํผ ๊ฐ€๋“ ์ฐธ ์ƒํƒœ๋ฅผ ๊ณ„์ธก๊ธฐ ํ•˜๋“œ์›จ์–ด์— ํ†ต๋ณดํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

import serial

# RTS/CTS ํ•˜๋“œ์›จ์–ด ํ๋ฆ„ ์ œ์–ด๊ฐ€ ์ ์šฉ๋œ ์‹œ๋ฆฌ์–ผ ํฌํŠธ ์„ค์ • ์˜ˆ์‹œ
ser = serial.Serial(
    port='COM3',
    baudrate=115200,
    timeout=0.1,
    rtscts=True  # RTS/CTS Hardware Flow Control ํ™œ์„ฑํ™”
)


3. ์†Œํ”„ํŠธ์›จ์–ด ์•„ํ‚คํ…์ฒ˜ ๋ฆฌํŒฉํ† ๋ง: ๋‹จ์ผ ์ƒ์‚ฐ์ž ํŒจํ„ด๊ณผ Zero-Copy ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ

์†Œํ”„ํŠธ์›จ์–ด ๋ ˆ์ด์–ด์—์„œ๋Š” ๋‹จ์ผ ์ƒ์‚ฐ์ž(Single Producer - Dedicated Reader) ํŒจํ„ด์„ ๋„์ž…ํ•˜๊ณ , IPC ์ง๋ ฌํ™” ๋ณ‘๋ชฉ์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด multiprocessing.shared_memory๋ฅผ ํ™œ์šฉํ•œ Zero-Copy ๋ง ๋ฒ„ํผ(Circular Ring Buffer) ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€. Dedicated Serial Reader ํ”„๋กœ์„ธ์Šค ๋ถ„๋ฆฌ

์‹œ๋ฆฌ์–ผ ํฌํŠธ ํ†ต์‹  ๊ถŒํ•œ์€ ์˜ค์ง ๋‹จ ํ•˜๋‚˜์˜ dedicated ํ”„๋กœ์„ธ์Šค๋งŒ ๋ณด์œ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋Š” ์‹œ๋ฆฌ์–ผ ํฌํŠธ์— ์ง์ ‘ ์ ‘๊ทผํ•˜์ง€ ์•Š๊ณ , ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด๊ฐ‘๋‹ˆ๋‹ค.

๋‚˜. SharedMemory์™€ Ring Buffer๋ฅผ ํ™œ์šฉํ•œ IPC ์ตœ์ ํ™”

ํŒŒ์ด์ฌ 3.8 ์ด์ƒ์—์„œ ์ง€์›ํ•˜๋Š” multiprocessing.shared_memory.SharedMemory๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Queue์˜ Pickle ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import multiprocessing as mp
from multiprocessing import shared_memory
import numpy as np
import time
import serial

class SharedRingBuffer:
    """
    Multiprocessing ๊ฐ„ Zero-Copy ๋ฐ์ดํ„ฐ ์ „์†ก์„ ์œ„ํ•œ Ring Buffer ํด๋ž˜์Šค
    """
    def __init__(self, size=1000, item_size=8, shm_name=None):
        self.size = size
        self.item_size = item_size
        self.buffer_bytes = size * item_size
        
        if shm_name is None:
            # ์ƒ์‚ฐ์ž ํ”„๋กœ์„ธ์Šค: ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ์ƒ์„ฑ
            self.shm = shared_memory.SharedMemory(create=True, size=self.buffer_bytes + 16) # ํ—ค๋” 16๋ฐ”์ดํŠธ ์ถ”๊ฐ€
            self.is_owner = True
        else:
            # ์†Œ๋น„์ž ํ”„๋กœ์„ธ์Šค: ๊ธฐ์กด ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ์—ฐ๊ฒฐ
            self.shm = shared_memory.SharedMemory(name=shm_name)
            self.is_owner = False
            
        # ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ๋ฒ„ํผ๋ฅผ NumPy ๋ฐฐ์—ด๋กœ ๋งคํ•‘ (Zero-Copy)
        self.data_arr = np.ndarray((size,), dtype=np.float64, buffer=self.shm.buf[16:])
        # ํ—ค๋”: [0~7] write_index, [8~15] read_index (uint64)
        self.header = np.ndarray((2,), dtype=np.uint64, buffer=self.shm.buf[:16])

    def write(self, val):
        w_idx = self.header[0]
        self.data_arr[w_idx % self.size] = val
        self.header[0] = w_idx + 1  # Atomic-like index update

    def read_available(self):
        w_idx = self.header[0]
        r_idx = self.header[1]
        if r_idx >= w_idx:
            return np.array([])
        
        # ์ฝ์„ ๋ฐ์ดํ„ฐ ์Šฌ๋ผ์ด์‹ฑ
        count = min(w_idx - r_idx, self.size)
        start = r_idx % self.size
        end = (r_idx + count) % self.size
        
        if start < end:
            data = np.copy(self.data_arr[start:end])
        else:
            data = np.concatenate((self.data_arr[start:], self.data_arr[:end]))
            
        self.header[1] = r_idx + count
        return data

    def close(self):
        self.shm.close()
        if self.is_owner:
            self.shm.unlink()

4. ์‹ค์ „ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌํ˜„: SCPI ๋น„๋™๊ธฐ ํŒŒ์‹ฑ ๋ฐ ์›Œ์ปค ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ

์•„๋ž˜ ์˜ˆ์ œ๋Š” ๊ณ„์ธก๊ธฐ๋กœ๋ถ€ํ„ฐ SCPI ์ „์•• ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์ฝ์–ด ๋“ค์—ฌ ๋ง ๋ฒ„ํผ์— ๊ธฐ๋กํ•˜๋Š” ์ƒ์‚ฐ์ž ํ”„๋กœ์„ธ์Šค์™€, ์ด๋ฅผ ์ˆ˜์‹ ํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์†Œ๋น„ ํ”„๋กœ์„ธ์Šค์˜ ์™„์„ฑํ˜• ์•„ํ‚คํ…์ฒ˜ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

import multiprocessing as mp
import time
import numpy as np
import re

def scpi_producer_process(shm_name, stop_event, port_name):
    """
    ๋‹จ์ผ ์‹œ๋ฆฌ์–ผ ์ฝ๊ธฐ ๋ฐ SharedMemory ๊ธฐ๋ก ์ „์šฉ ํ”„๋กœ์„ธ์Šค
    """
    # ๋ง ๋ฒ„ํผ ์—ฐ๊ฒฐ
    ring_buf = SharedRingBuffer(size=5000, shm_name=shm_name)
    
    # ์‹œ๋ฆฌ์–ผ ๊ฐ€์ƒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฐ ํฌํŠธ ์—ฐ๊ฒฐ ์„ค์ •
    # try:
    #     ser = serial.Serial(port_name, 115200, timeout=0.01, rtscts=True)
    # except Exception as e:
    #     print(f"Port Open Fail: {e}")
    #     return

    # ์ •๊ทœํ‘œํ˜„์‹ ์‚ฌ์ „ ์ปดํŒŒ์ผ (SCPI ASCII ํŒŒ์‹ฑ ์†๋„ ์ตœ์ ํ™”)
    scpi_float_pattern = re.compile(rb'[-+]?\d*\.\d+|\d+')
    
    print("[Producer] Started reading SCPI stream...")
    
    # ๊ฐ€์ƒ SCPI ์ŠคํŠธ๋ฆผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ฃจํ”„
    counter = 0
    while not stop_event.is_set():
        # ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” ser.read_until(b'\n') ๋˜๋Š” ser.read(ser.in_waiting) ์‚ฌ์šฉ
        # dummy SCPI response: b":VOLT 12.345 V\n"
        raw_bytes = f":VOLT {10.0 + (counter % 50) * 0.1:.3f} V\n".encode('ascii')
        counter += 1
        
        # 1. ๋น ๋ฅธ SCPI ์ˆซ์ž๋ฅผ ์ถ”์ถœ
        match = scpi_float_pattern.search(raw_bytes)
        if match:
            try:
                val = float(match.group())
                # 2. Shared Memory ๋ง ๋ฒ„ํผ์— ๊ธฐ๋ก (Lock Free Concept)
                ring_buf.write(val)
            except ValueError:
                pass
                
        time.sleep(0.001) # 1kHz ๊ณ„์ธก ์ƒ˜ํ”Œ๋ง ์†๋„ ํ‰๋‚ด

    ring_buf.close()
    print("[Producer] Terminated cleanly.")

def consumer_worker_process(shm_name, stop_event):
    """
    ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๊ณ ์†์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ๋ถ„์„ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ”„๋กœ์„ธ์Šค
    """
    ring_buf = SharedRingBuffer(size=5000, shm_name=shm_name)
    print("[Consumer] Worker process started...")
    
    processed_count = 0
    
    while not stop_event.is_set():
        data = ring_buf.read_available()
        if len(data) > 0:
            processed_count += len(data)
            # ์ˆ˜์ง‘ ๋ฐ์ดํ„ฐ ๋ฐฐ์น˜ ์—ฐ์‚ฐ (์˜ˆ: ์‹ค์‹œ๊ฐ„ ์ด๋™ ํ‰๊ท  ๋˜๋Š” FFT)
            avg_val = np.mean(data)
            # print(f"[Consumer] Batch read: {len(data)} items, Avg: {avg_val:.2f}")
        else:
            time.sleep(0.005) # CPU idle ๋ฐฉ์ง€
            
    ring_buf.close()
    print(f"[Consumer] Total processed points: {processed_count}")

if __name__ == "__main__":
    # 1. ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ๋ฉ”์ธ ๊ด€๋ฆฌ์ž ์ดˆ๊ธฐํ™”
    main_buf = SharedRingBuffer(size=5000)
    shm_name = main_buf.shm.name
    
    stop_event = mp.Event()
    
    # 2. ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ ๋ฐ ์‹คํ–‰
    producer = mp.Process(target=scpi_producer_process, args=(shm_name, stop_event, "COM3"))
    consumer = mp.Process(target=consumer_worker_process, args=(shm_name, stop_event))
    
    producer.start()
    consumer.start()
    
    try:
        # 5์ดˆ๊ฐ„ ๋ฉ”์ธ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹คํ–‰
        time.sleep(5)
    finally:
        print("[Main] Stopping all processes...")
        stop_event.set()
        producer.join()
        consumer.join()
        main_buf.close()
        print("[Main] System successfully shut down.")


5. ํ˜„์—… ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ์—ฃ์ง€ ์ผ€์ด์Šค ๋ฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์‹ค์ œ ์žฅ๋น„ ๊ฒ€์‚ฌ ์ž๋™ํ™” ๋ผ์ธ(24/7 ์—ฐ๋™)์— ์ ์šฉํ•  ๋•Œ ์ž์ฃผ ๋งŒ๋‚˜๋Š” ์—ฃ์ง€ ์ผ€์ด์Šค์™€ ํ•ด๊ฒฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

๊ฐ€. USB ์‹œ๋ฆฌ์–ผ ๋ฆฌ์—๋‰ด๋จธ๋ ˆ์ด์…˜(Re-enumeration) ๋ฐ ํฌํŠธ ์ดํƒˆ

๊ณ ์ „์••/๋Œ€์ „๋ฅ˜ ๊ณ„์ธก๊ธฐ ์ œ์–ด ์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๋…ธ์ด์ฆˆ(EMI/ESD)๋กœ ์ธํ•ด USB ๊ฐ€์ƒ COM ํฌํŠธ์˜ ์—ฐ๊ฒฐ์ด ์ˆœ๊ฐ„์ ์œผ๋กœ ๋Š๊ฒผ๋‹ค ์žฌ์—ฐ๊ฒฐ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

  • ํ•ด๊ฒฐ์ฑ…: Producer ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์— PySerial.SerialException ๋ฐœ์ƒ ์‹œ ์ž๋™ ์žฌ์—ฐ๊ฒฐ(Reconnection Retry Loop) ๋กœ์ง์„ ๊ตฌ๋น„ํ•˜๊ณ , ๋ณต๊ตฌ๋˜๋Š” ๋™์•ˆ Shared Memory์˜ ์ธ๋ฑ์Šค๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฆฌ์…‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜. SCPI ์ข…๋‹จ ๋ฌธ์ž(Termination Character) ํ˜ผ์„ 

๊ณ„์ธก๊ธฐ ์ œ์กฐ์‚ฌ(Agilent/Keysight, Tektronix, Rohde & Schwarz)๋งˆ๋‹ค ๊ธฐ๋ณธ ์ข…๋‹จ ๋ฌธ์ž๊ฐ€ \n(LF), \r\n(CRLF), ๋˜๋Š” EOI(End or Identify) ๋ผ์ธ ํ™œ์„ฑํ™” ์—ฌ๋ถ€๋กœ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

  • ํ•ด๊ฒฐ์ฑ…: ์‹œ๋ฆฌ์–ผ ์ฝ๊ธฐ ์‹œ read_until() ํ•จ์ˆ˜์— ์‚ฌ์šฉํ•  ์ข…๋‹จ ๋ฌธ์ž๋ฅผ ๋ช…ํ™•ํžˆ ์ง€์ •ํ•˜๊ณ , ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ธ”๋ก ๋ฐ์ดํ„ฐ ์ „์†ก ๋ชจ๋“œ(FORM:DATA REAL,32) ์‚ฌ์šฉ ์‹œ์—๋Š” ํ—ค๋”์˜ ๋ฐ์ดํ„ฐ ๊ธธ์ด๋ฅผ ๋จผ์ € ๊ณ„์‚ฐํ•œ ํ›„ ์ •ํ™•ํ•œ ๋ฐ”์ดํŠธ ์ˆ˜๋งŒํผ read(bytes_to_read)๋กœ ์ฝ์–ด ๋“ค์—ฌ์•ผ ๋ฒ„ํผ ์˜ค๋ฒ„๋Ÿฐ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค. ํŒŒ์ด์ฌ ํ”„๋กœ์„ธ์Šค ๊ณ ์‚ฌ(Zombie Process) ๋ฐ SharedMemory ๋ˆ„์ˆ˜

ํ”„๋กœ์„ธ์Šค๊ฐ€ ์˜ˆ์™ธ(Exception)๋กœ ๋น„์ •์ƒ ์ข…๋ฃŒ๋˜๋ฉด SharedMemory๊ฐ€ OS ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์•„ ๋ฉ”์ธํ”„๋ ˆ์ž„ ์žฌ์‹œ์ž‘ ์ „๊นŒ์ง€ RAM ๋ˆ„์ˆ˜๋ฅผ ์ผ์œผํ‚ต๋‹ˆ๋‹ค.

  • ํ•ด๊ฒฐ์ฑ…: ๋ฉ”์ธ ํ”„๋กœ์„ธ์Šค์˜ try...finally ๊ตฌ๋ฌธ ๋‚ด์—์„œ shm.close() ๋ฐ shm.unlink()๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜, atexit ๋ชจ๋“ˆ์— ํ•ด์ œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

6. ๊ฒฐ๋ก : ์•ˆ์ •์ ์ธ ๊ณ„์ธก ์ž๋™ํ™” ์‹œ์Šคํ…œ ๊ตฌ์ถ•์„ ์œ„ํ•œ ํ•ต์‹ฌ ์ œ์–ธ

ํŒŒ์ด์ฌ์„ ํ™œ์šฉํ•œ ๊ณ ์† SCPI ๊ณ„์ธก๊ธฐ ์ž๋™ํ™” ์ œ์–ด ์‹œ ๋ฐœ์ƒํ•˜๋Š” ์‹œ๋ฆฌ์–ผ ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ ๋ฐ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋ณ‘๋ชฉ์€ ๋‹จ์ˆœํ•œ ์ฝ”๋“œ ๊ตฌ์กฐ ๋ณ€๊ฒฝ๋งŒ์œผ๋กœ๋Š” ์™„๋ฒฝํžˆ ํ•ด๊ฒฐํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

  1. ํ•˜๋“œ์›จ์–ด ๋ ˆ์ด์–ด: USB ๋“œ๋ผ์ด๋ฒ„ Latency ๊ฐ์†Œ(1ms) ๋ฐ RTS/CTS ํ๋ฆ„ ์ œ์–ด๋ฅผ ์ ์ ˆํžˆ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ์•„ํ‚คํ…์ฒ˜ ๋ ˆ์ด์–ด: ์‹œ๋ฆฌ์–ผ ํฌํŠธ ์ฝ๊ธฐ๋Š” ๋‹จ ํ•˜๋‚˜ ์˜ Dedicated Producer Process๋งŒ ๋‹ด๋‹นํ•˜๋„๋ก ๋‹จ์ผํ™”ํ•ฉ๋‹ˆ๋‹ค.
  3. IPC ๋ ˆ์ด์–ด: Heavyํ•œ Queue ๋Œ€์‹  Zero-Copy SharedMemory ๋ง ๋ฒ„ํผ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ง๋ ฌํ™” ๋ณ‘๋ชฉ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ตœ์ ํ™” ํŒจํ„ด์„ ์„ค๊ณ„์— ์ ์šฉํ•˜๋ฉด ์ดˆ๋‹น ์ˆ˜๋งŒ ๊ฑด ์ด์ƒ์˜ ๊ณ„์ธก ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ ์†์‹ค ์—†์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„ํ•˜๋Š” ๊ณ ์„ฑ๋Šฅ ์ž๋™ํ™” ๊ฒ€์‚ฌ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•