MSDN에 있는 글을 옮겨와 봤습니다^^

 

다른 프로세서에서 사용되고 있는 복수의 동시실행 태스크가 같은 캐시라인에 배치되어 있는 변수에 쓰기를 하면 거짓 공유가 발생한다. 하나의 태스크가 어떤 변수에 쓰기를 하면 양쪽 변수의 캐시라인이 무효화된다. 캐시라인이 무효화 될 때마다 재 로딩이 필요하게 된다. 즉 거짓 공유가 발생하면 애플리케이션의 성능이 저하할 위험이 크다.

 

long count1 = 0L;

long count2 = 0L;

concurrency::parallel_invoke(

   [&count1] {

      for(int i = 0; i < 100000000; ++i)

         ++count1;

   },

   [&count2] {

      for(int i = 0; i < 100000000; ++i)

         ++count2;

   }

);

long count = count1 + count2;

위의 코드는 태스크에 공유 변수를 사용하지 않기 위해 count1, count2로 변수를 나누어서 사용하고 있다. 그래서 일견 보기에는 서로 독립적으로 실행될 것이라고 생각할 수 있다. 그러나 위에 설명했듯이 count1 count2는 서로 같은 캐시라인에 배치될 확률이 높아서 결과적으로 거짓 공유가 발생한다.

 

이 문제를 해결하기 위해서는 아래처럼 메모리 정렬에 의해 서로 다른 캐시라인에 배치되도록 한다.

__declspec(align(64)) long count1 = 0L;     

__declspec(align(64)) long count2 = 0L;     

concurrency::parallel_invoke(

   [&count1] {

      for(int i = 0; i < 100000000; ++i)

         ++count1;

   },

   [&count2] {

      for(int i = 0; i < 100000000; ++i)

         ++count2;

   }

);

long count = count1 + count2;

핵심은 __declspec(align(64))으로 메모리 캐시 사이즈가 64바이트 이하인 경우 같은 캐시라인을 사용하지 않도록 해준다.

 

 

__declspec 설명

http://blog.naver.com/riverrun27/40126498899

 

 

저작자 표시
신고
by 흥배 2014.02.03 08:00

잘못된 SqlConnection

 

// 코드 1

using (var connection = new SqlConnection("접속문자열"))

{

    connection.Open();

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}

  • <코드 1>을 실행하면 크래쉬가 발생

  • 이유는 간단 SqlConnection가 스레드 세이프 하지 않기 때문
  • 위의 코드를 올바르게 수정하려면 아래와 같이 변경
     

    // 코드 2

    Parallel.For(1, 1000, x =>

    {

        using (var connection = new SqlConnection("접속문자열"))

        {

            connection.Open();

            var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

        }

    });

  • <코드 2>는 안전하지만 아마 위 방식은 원하는 방식이 아닐 것이다.

 

 

ThreadLocal

  • 위의 문제를 ThreadLocal을 사용하여 해결해 보자
     

    // 코드 3

    using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

    {

        Parallel.For(1, 1000, x =>

        {

            var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

        });

    }

  • 성능 테스트를 해보면 싱글 스레드를 사용하면 16초, <코드 2>는 5초, <코드 3>은 2초

 

 

<코드 3>은 안전한가?

  • <코드 3>은 위험한 코드
    • 이유는 연결한 후 Dispose 하지 않기 때문.
    • ThreadLocal의 Dispose는 어디까지는 ThreadLocal의 Dispose이기 때문에 그 안의 객체를 Dispose 해주지 않는다.
  • 이 문제는 trackAllValues 라는 옵션을 사용하여 간단하게 해결 가능!

 

// 코드 4

using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }

    , trackAllValues: true)) // ThreadLocalの.Values 프로퍼티의 참조를 유효화 한다

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

    

    // 생성된 모든 Connection을 일괄적으로 Dispose

    foreach (var item in connection.Values.OfType<IDisposable>()) item.Dispose();

}

  • 단 trackAllValues는 닷넷프레임워크 4.5 부터 사용 가능

 

 

trackAllValues 랩퍼 클래스 사용

 

public static class DisposableThreadLocal

{

    public static DisposableThreadLocal<T> Create<T>(Func<T> valueFactory)

        where T : IDisposable

    {

        return new DisposableThreadLocal<T>(valueFactory);

    }

}

    

public class DisposableThreadLocal<T> : ThreadLocal<T>

    where T : IDisposable

{

    public DisposableThreadLocal(Func<T> valueFactory)

        : base(valueFactory, trackAllValues: true)

    {

    

    }

    

    protected override void Dispose(bool disposing)

    {

        var exceptions = new List<Exception>();

        foreach (var item in this.Values.OfType<IDisposable>())

        {

            try

            {

                item.Dispose();

            }

            catch (Exception e)

            {

                exceptions.Add(e);

            }

        }

    

        base.Dispose(disposing);

    

        if (exceptions.Any()) throw new AggregateException(exceptions);

    }

}

   

// 사용

using (var connection = DisposableThreadLocal.Create(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}

 

 

 

출처: http://neue.cc/2013/03/09_400.html

신고
by 흥배 2013.10.11 08:00
| 1 |