Creative Commons License

공유메모리에 40바이트 읽고 쓰는데 얼마나 충돌날까? IT



가끔씩 공유메모리에 있는 자료를 n개 프로세스/쓰레드가 읽고 쓰기 위해 접근할 때, 정말 충돌이 나긴할까? 물론 이론은 반드시 나긴 나는데, 이론은 이론이고... 임계영역이 I/O wait이 안 일어날 정도로 짧은 구간이더라도 충돌이 날까?

결론은 충돌난다. ㅡ_-) 덴장... 이래서 잠금장치가 여러개 있는 것이지...


아래 소스를 대충 컴파일해서 돌려보고 Ctrl+C를 눌러보면...

#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>

enum
{
    SHM_STR_LEN = 32
};

typedef struct str_t
{
    int len;
    char buf[SHM_STR_LEN+1];
} str_t;

volatile size_t g_total_count(0);
volatile size_t g_crash_count(0);

int g_shmfd(-1);
str_t* g_buf(NULL);

//        make random string...
void
makeRandStr(str_t& buf)
{
    memset(&buf, 0x00, sizeof(buf));

    int i, max(rand()%SHM_STR_LEN);
    for ( i = 0; i < max; i++ )
    {
        buf.buf[i] = (rand()%10) + '0';
    }

    buf.len = max;
    buf.buf[SHM_STR_LEN] = 0x00;
}

//        check str_t structure...
bool
chkBuffer(const str_t& buf)
{
    ++ g_total_count;

    int reallen(strlen(buf.buf));

    bool res(reallen == buf.len);
    if ( res )
    {
        //printf("success!\n");
        return true;
    }

    //printf("crash! len=%d real=%d\nbuf=%s\n",
    //    buf.len, reallen, buf.buf);
    ++ g_crash_count;

    return false;
}

//        signal procedure for productor.
void
productor_sigint(int)
{
    shmctl(g_shmfd, IPC_RMID, NULL);
    exit(0);
}

//        productor makes random-string and memcpy to shared-memory.
void
productor(void)
{
    signal(SIGINT, productor_sigint);
    str_t buf;
    while (true)
    {
        makeRandStr(buf);
        memcpy(g_buf, &buf, sizeof(buf));
        //printf("rand: len=%d\n", g_buf->len, g_buf->buf);
    }
}

//        signal procedure for consumer.
void
consumer_sigint(int)
{
    printf("\ntotal: %d\ncrash: %d\nfail-rate: %2.02f%%\n",
        g_total_count, g_crash_count,
        g_crash_count*100.0/g_total_count);
    shmctl(g_shmfd, IPC_RMID, NULL);
    exit(0);
}

//        consumer reads memcpy from shared-memory and check.
void
consumer(void)
{
    signal(SIGINT, consumer_sigint);
    fprintf(stderr, "Test size: %d\n", sizeof(str_t));
    fprintf(stderr, "Press Ctrl+C...");
    str_t buf;
    while (true)
    {
        memcpy(&buf, g_buf, sizeof(buf));
        chkBuffer(buf);
    }
}

//        main function. make shared-memory and fork!
int
main(int argc, char* argv[])
{
    key_t shmkey(ftok("/dev/null", 1));
    if ( -1 == shmkey )
    {
        printf("%d %s\n", __LINE__, strerror(errno));
        return 0;
    }

    g_shmfd = (shmget(shmkey, sizeof(str_t), 0700|IPC_CREAT));
    if ( -1 == g_shmfd )
    {
        printf("%d %s\n", __LINE__, strerror(errno));
        return 0;
    }

    g_buf = ((str_t*)shmat(g_shmfd, NULL, SHM_RND));
    memset(g_buf, 0x00, sizeof(str_t));

    if ( fork() )
    {
        productor();
    }
    else
    {
        consumer();
    }
}


$ g++ -o shmtest -O3 shmtest.cpp

아주 정확한 방법은 아니지만, 대충 충돌하는 것을 알 수 있을 것이다. 여러 시스템에서 돌렸을 때, 몇 퍼센트가 나올지 궁금하다. 누가 돌려주면 좋겠지만, 아쉽게도 아무도 안 돌릴 것 같다. orz OTL

참고로 아래는 CentOS 5.2에서 살포시 돌려본 결과이다.

$ ./shmtest
Test size: 40
Press Ctrl+C...
total: 186202017
crash: 985010
fail-rate: 0.53%



덧글: 혹시나 오해할까봐... MT/MP 환경에서 임계영역이 있다면 락을 걸어주는 것이 상식. 이건 그냥 가끔씩 이론은 이론이고 실제는 얼마나 충돌할까라는 맛뵈기 실험.

덧글

  • uriel 2008/12/03 16:04 #

    dual-core ubuntu 8.10에서 돌린 결과는 다음과 같네요.

    Test size: 40
    Press Ctrl+C...^C
    total: 245458967
    crash: 655341
    fail-rate: 0.27%
  • 샘이 2008/12/03 16:06 #

    헛!! 감사합니다. :-D
  • 허진영 2008/12/03 16:46 #

    저도 같은 사양. Intel(R) Core(TM)2 Duo CPU T7500 @ 2.20GHz, 우분투 8.10 입니다.

    Test size: 40
    Press Ctrl+C...^C
    total: 310261644
    crash: 863755
    fail-rate: 0.28%

    메모리상의 공유 자원에 대한 접근 시 충돌이 안 발생하면 그건 운이 초대박 좋은겁니다(물론 충분한 실행횟수가 있어야겠지요). 아마 그 운으로 로또사시면 1등될걸요. ㅎㅎㅎ
  • 샘이 2008/12/03 17:32 #

    프로그램을 조작해서 충돌 안 하게 한 뒤에 로또를 산다면... 후후후후후.... 테스트 감사합니다.
  • chadr 2008/12/03 16:54 # 삭제

    사실 이론상 반드시 충돌이 일어나고 아무리 작은 블럭이라도 시간의 동일선상에 놓이게 되면 반드시 충돌이 날수밖에 없죠.
    그런데 보면 또 재미난게.. 실패확율이 매우 낮다는거..

    사실 이론상 반드시 충돌은 나지만 극히 미비한 확률로 난다는거..

    그래서 저 확률만 제거하면 사실 락이라는게 필요가 없죠.

    그래서 나온게 트랜젝션 메모리라는거... 알지도 모르겠지만..
  • 샘이 2008/12/03 17:33 #

    트랜젝션 메모리 그건 먹는 것? 그것보단 rollback/commit이 있긴 하겠지만, 어차피 할꺼면 rw-lock 훨 나아보임.
  • object 2008/12/03 21:57 #

    간단하게 두 스레드가 int 타입의 shared_variable 하나를 ++ 하는 것만 해도 백만번 돌리면 10번 정도 삑사리가 납니다. 지금 위 결과는 굉장히 위험한 경우죠. 0.5%나 충돌이 나는건... 물론 이런 레이스가 조용히 묻히는 경우도 있지만 경우에 따라 굉장히 위험합니다. 극히 미비한 확률로 난다고 무시할 수 있는게 아니죠. 더욱 문제는 이게 몇 달에 한 번 정도 발생해서 시스템의 큰 장애를 초래하는 것이죠. 트랜잭션 메모리는 DB에서 쓰이는 트랜잭션 개념을 도입해 크리티컬 섹션을 optimistic하게 바꿔 락 대신에 쓸 수 있도록 제안된 방법입니다만 아직 제대로 구현되지는 않았습니다. 물론 정밀도가 높을 필요 없는 통계 값 정도야 레이스를 놔두어도 되지만 중요한 공유 자원은 아시다시피 싱글 코어에서도 얼마든지 충돌이 일어나고 요즘 같은 멀티코어에서는 매우 빈번히 일어납니다... 이론이 아니라 실제로 이거때문에 사람도 죽고 대형 정전 사태도 벌어지고.... 심각한 문제에요.
  • 샘이 2008/12/04 11:48 #

    당연히 심각한 문제이지요. 다만 실제로 얼마나 일어나는 것을 알아보는 것도 괜찮은 학습이라 생각합니다.
  • FromNil 2008/12/05 02:23 #

    모두 듀얼 코어 장비에서 돌렸습니다. ^^

    [Red Hat Enterprise Linux WS release 3 (Taroon Update 3)]
    Test size: 40
    Press Ctrl+C...
    total: 183368810
    crash: 713819
    fail-rate: 0.39%

    [Red Hat Enterprise Linux AS release 4 (Nahant Update 4)]
    Test size: 40
    Press Ctrl+C...
    total: 109623401
    crash: 664087
    fail-rate: 0.61%

    [CentOS release 5.2 (Final)]
    Test size: 40
    Press Ctrl+C...
    total: 276318389
    crash: 4154647
    fail-rate: 1.50%

    재밌는 실험이네요 ^^
  • 샘이 2008/12/05 11:40 #

    ^^ 실험 감사합니다. 다양한 빨간모자를 운영하고 계시는군요!
  • FromNil 2008/12/05 17:58 #

    회사 시험장비에서 해봤답니다. ^^ 좋은 주말되세요~ㅎ
  • 샘이 2008/12/05 17:59 #

    :-D 오홋! 그렇군요! 즐거운 주말 보내세요~
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.

Google Adsense

Google Adsense

Google Analytics



C로그팬박스