PyO3를 활용한 파이썬 모듈의 멀티스레딩 지원 방법

파이썬은 멀티스레딩을 통해 동시에 여러 작업을 수행할 수 있는 강력한 언어입니다. 하지만, GIL(Global Interpreter Lock)이라는 개념 때문에 파이썬에서는 실제로 동시에 여러 CPU 코어를 활용하여 병렬 처리를 할 수 없습니다. 따라서, 파이썬에서의 멀티스레딩은 I/O 바운드 작업을 처리하거나 CPU 바운드 작업을 분할하여 병렬로 처리하는 등의 용도로 활용됩니다.

만약 C/C++로 작성된 라이브러리나 모듈을 파이썬에서 사용하고자 할 때, 파이썬 모듈을 작성하기 위해 PyO3라는 도구를 사용할 수 있습니다. PyO3는 파이썬의 C API를 편리하게 사용할 수 있도록 도와주는 도구이며, C/C++ 코드를 사용하는 고성능 파이썬 모듈을 작성할 수 있도록 지원합니다.

PyO3를 활용하여 작성한 파이썬 모듈에서 멀티스레딩을 지원하기 위해서는 threading 라이브러리를 사용할 수 있습니다. threading 라이브러리는 파이썬에서 멀티스레딩을 구현하는 데 사용되는 기본 라이브러리이며, 여러 개의 스레드를 생성하고 조작하는 기능을 제공합니다.

다음은 PyO3를 활용하여 작성된 파이썬 모듈에서 멀티스레딩을 구현하는 예시 코드입니다.

use pyo3::prelude::*;
use pyo3::types::IntoPyDict;

fn calculate_square(num: u32) -> u32 {
    num * num
}

#[pyfunction]
fn calculate_squares(numbers: Vec<u32>) -> PyResult<Vec<u32>> {
    let gil = Python::acquire_gil();
    let py = gil.python();

    let mut result = vec![];

    py.allow_threads(|| {
        let n = numbers.len();
        let threads = 4; // Number of threads to use
        
        let chunk_size = (n + threads - 1) / threads;
        let chunks = numbers.chunks(chunk_size);

        pyo3::ff::threading::Scope::new(py)
            .launch(|scope| {
                for chunk in chunks {
                    scope.spawn(move |_| {
                        let squares: Vec<u32> = chunk.iter()
                            .map(|&num| calculate_square(num))
                            .collect();
                        result.extend(squares);
                    });
                }
            })
            .unwrap();
    });

    Ok(result)
}

#[pymodule]
fn my_module(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(calculate_squares, m)?)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use pyo3::types::IntoPyDict;
    use pyo3::types::PyList;

    #[test]
    fn test_calculate_squares() {
        let gil = Python::acquire_gil();
        let py = gil.python();

        my_module(py, pyo3::types::PyModule::new(py, "my_module").unwrap()).unwrap();
        let locals = [("my_module", py.import("my_module")?)].into_py_dict(py);

        let input_data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        let expected_output = vec![1, 4, 9, 16, 25, 36, 49, 64, 81, 100];

        let result = py
            .eval("my_module.calculate_squares([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])", Some(locals), None)
            .unwrap();

        let result_vec: Vec<u32> = result.extract().unwrap();
        assert_eq!(result_vec, expected_output);
    }
}

위의 예시 코드에서는 calculate_squares라는 함수가 있는 파이썬 모듈을 작성하고 있습니다. 이 함수는 numbers라는 입력 리스트를 받아 각 요소의 제곱을 계산하여 결과 리스트를 반환합니다. 이 때, threading 라이브러리를 사용하여 멀티스레딩을 구현하고 있습니다. 입력 리스트를 여러 개의 청크로 나누고, 각 청크를 병렬로 처리하여 결과를 합치는 방식으로 동작합니다.

모듈 작성이 완료되면, Rust 코드를 빌드하여 파이썬 모듈을 생성할 수 있습니다. 이후, 파이썬에서 해당 모듈을 가져와 멀티스레딩을 활용할 수 있습니다.

이와 같이 PyO3를 활용하여 파이썬 모듈을 작성하고 멀티스레딩을 지원하는 방법을 알아보았습니다. 파이썬의 멀티스레딩 기능을 활용하면 병렬 처리가 필요한 작업을 효율적으로 처리할 수 있습니다.

더 자세한 내용을 알고 싶다면 PyO3 공식 문서를 참고해주세요. #파이썬 #멀티스레딩