C#의 Parallel API를 이용하여 CPU 100% 활용하기

CPU는 여러 개의 Core로 구성되어 있고 각 Core 단위로 동시에 연산을 처리할 수 있습니다. C#에서 CPU를 최대한 이용하기 위해서 Parallel API를 이용한 코드를 정리합니다.

Task.Factory.StartNew(() => {
    Parallel.ForEach(addressData, new ParallelOptions { MaxDegreeOfParallelism = cntCores },
        (task) => {
            int iAddress = task.Index;
            string Address = task.Address;
            
            /* 
                시간이 많이 걸리는 연산을 처리하는 스코프
            */

            Invoke(new Action(() => {
                // UI 처리가 가능한 스코프
            }));

            //Application.DoEvents(); -> 더 이상 필요치 않음
        }
    );
});

중요한 점은 Parallel에서 만들어진 스레드는 Main 스레드에서 구동되면 안됩니다. 그래서 Task.Factory.StartNew를 통해 별도의 스레드를 하나 만들고.. 만들어진 스레드에서 Parallel의 스레드를 구동하게 합니다. Task.Factory.StartNew를 사용한 이유는 스레드를 간단하게 만들 수 있기 때문으로 다른 스레드를 만드는 코드도 유효합니다. addressData는 스레드를 통해 처리해야할 데이터가 담긴 컨테이너입니다. 예를 들어 다음과 같습니다.

List<ADDRESS_DATA> addressData = new List<ADDRESS_DATA>();

ADDRESS_DATA는 다음과 같구요. (올바른 캡슐화를 적용하지 않은 코드입니다)

private class ADDRESS_DATA
{
    public int Index;
    public String Address;

    public ADDRESS_DATA(int Index, String Address)
    {
        this.Index = Index;
        this.Address = Address;
    }
}

Paralleld의 ForEach 매서드에서 task를 통해 ADDRESS_DATA의 필드값에 접근할 수 있습니다. 그리고 cntCores는 CPU의 코어 수인데, 동시에 실행할 수 있는 스레드의 개수로 지정하기 적당한 값입니다. 다음처럼 얻을 수 있습니다.

int cntCores = Environment.ProcessorCount;

pyQGIS를 이용한 벡터 데이터 처리 10 : 버퍼(Buffer) 연산

지오메트리에 대한 공간 연산을 공간 분석을 위해 활용할 수 있는데 그 연산 중 버퍼 연산에 대한 코드를 설명합니다. 먼저 레이어를 추가하고 RN이라는 필드의 값이 “로”로 끝나는 피쳐를 선택하고 선택된 피쳐의 지오메트리에 대해 버퍼 연산을 수행한 뒤 그 결과를 다른 SHP 파일에 저장하는 코드를 작성해 보겠습니다.

먼저 레이어를 추가하고 RN 필드값이 “로”로 끝나는 피쳐를 선택하는 코드를 다음처럼 작성합니다.

QgsProject.instance().removeAllMapLayers()
layer = QgsVectorLayer("D:/__Data__/세종특별자치시_36000/TL_SPRD_MANAGE.shp", "TL_SPRD_MANAGE")
QgsProject.instance().addMapLayers([layer])
layer.selectByExpression('"RN" like \'%로\'')

버퍼 연산 결과를 저장할 SHP 파일 작성자(Writer)를 생성합니다.

fields = layer.fields()
fileName = "D:/__Data__/buffer.shp"
writer = QgsVectorFileWriter(
    fileName,
    "utf-8", 
    fields,
    QgsWkbTypes.Polygon,
    layer.sourceCrs(),
    "ESRI Shapefile"
)

이제 선택된 피쳐를 하나씩 순회하면서 버퍼 연산을 수행하고 그 결과를 새로운 SHP 파일에 기록합니다.

dist = 100
features = layer.selectedFeatures()
for feat in features:
    geom = feat.geometry()
    buff = geom.buffer(dist, 8)
    feat.setGeometry(buff)
    writer.addFeature(feat)

del(writer) # 새로운 SHP 파일 닫기

새로운 SHP 파일을 레이어로 추가하는 코드는 다음과 같습니다.

layer = QgsVectorLayer(fileName, "새로운 레이어", "ogr")
QgsProject.instance().addMapLayers([layer])

실행 결과는 다음과 같습니다.