김명신님께서 또 아주 좋은 지적을 해주셨습니다~~ List<T>도 ConcurrentBag<T>이든 하나의 컬렉션을 이용하는 건 바람직하지 않은 것 같다는 의견이셨는데요, 동감합니다 :) 그래서 제 나름대로 해결책을 강구해봤는데욤. 스레드 로컬 변수를 이용해서 스레드 별로 작업을 시키고, 나중에 하나의 리스트로 모으는 것입니다. 코드를 볼까욤~?

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication1
{
    class Program
    {
        static readonly object _sync = new object();
 
        static IEnumerable<long> GetPrimeNumber(long num)
        {
            List<long> primeList = new List<long>();
 
            Parallel.For<List<long>>(0, num + 1, 
                () => new List<long>(), //스레드 로컬 변수 초기화
                (i, outerLoopState, subList) =>
            {
                bool isPrime = true;
                Parallel.For(2, i, (j, loopState) =>
                {
                    if (i % j == 0)
                    {
                        isPrime = false;
                        loopState.Break();
                    }
                });
 
                if (isPrime)
                {
                    subList.Add(i); //스레드 로컬 리스트에 추가
                }
 
                return subList;
            },
            (subList) => //각 스레드 종료 후에 취합
            {
                lock (_sync)
                {
                    primeList.AddRange(subList);
                }
            });
 
            return primeList;
        }
 
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            IEnumerable<long> primeList = GetPrimeNumber(99999);
            sw.Stop();
 
            Console.WriteLine("Elapsed : {0}, Found prime counts : {1}",
                sw.Elapsed.ToString(),
                primeList.Count());
            //뭔가 한다.
        }
    }
}

위 코드를 보면, Parallel.For 루프에서 스레드 로컬 변수를 사용하도록 밑줄 친 부분들이 추가된 것을 볼 수 있습니다. 즉 이 Parallel.For는 List<long>타입의 스레드 지역 변수를 사용하며, 그 지역변수를 어떻게 초기화 할 것인지를 명시하고, 각 스레드가 종료할 때 지역 변수를 리턴하도록 한 것이죠. 그리고 Parallel.For의 마지막 매개변수로, 각 스레드가 종료할 때 어떤 동작을 취할 것인가를 명시하는데, 스레드가 끝날 때 리턴한 지역 변수가 매개변수로 들어오게 됩니다. 이렇게 하면 동기화를 좀 더 줄일 수 있는 것이죠. 그래서, 이왕 코드를 고친 김에 시간을 재봤습니다.

   ConcurrentBag<T>사용(초)  스레드 지역 변수 사용(초)
 GetPrimeNumber(99999)  2.54  2.52 
 GetPrimeNumber(199999)  9.32  9.25
 GetPrimeNumber(299999)  20.03  19.96

미세하게 스레드 지역 변수를 사용한 쪽이 빠르긴 한데요, 위 코드의 경우 하나의 리스트로 값을 합치는 데 시간이 좀 소요되어서 큰 속도 향상이 없는 것으로 보입니다. 동기화해서 수행해야 하는 작업이 간단할 수록 속도차이는 벌어질 것으로 보이는데요, 가능하면 각자 작업 후에 하나로 합치는 쪽이 더 효율적일 것 같네요.

부족한 실력이다 보니 이 이상 뭐가 떠오르지 않네요 -_-;; 더 나은 방법이 있으면 알려주세용~ :)

저작자 표시 비영리 변경 금지
신고