동시에 백그라운드로 프로그램을 수행했을시 간헐적인 에러발생

kluster86의 이미지

안녕하세요~

프로그래밍을 하다가 도저히 원인을 알 수 없어서 질문드립니다ㅠ_ㅠ

단순한 코드인데요, 대용량(?)문서를 처리하면 간헐적으로 이상증세를 보이고 있습니다.

간단히 요약하면 아래 코드는 A라는 문서를 읽어서 A.step1에 쓰도록 구현이 되어있습니다.

이 바이너리를 통하여 문서를 테스트하면, 별탈없이 결과를 출력합니다.

그런데 이것을 동시에 백그라운드로 여러개를 돌렸을때, 문제가 발생합니다. 그것도 간헐적으로요 ㅠㅠ

수행은 아래와 같이 합니다. 전부 백그라운드로 동시에 수행합니다.

*아래 괄호속에 있는것은 각 파일의 용량입니다.
A(5G) => 바이너리 => A.step1
B(5G) => 바이너리 => B.step1
C(3G) => 바이너리 => C.step1
D(3G) => 바이너리 => D.step1
E(1.5G) => 바이너리 => E.step1
F(1.5G) => 바이너리 => F.step1
G(2.5G) => 바이너리 => G.step1
H(2.5G) => 바이너리 => H.step1

이렇게 수행을 하고 나면 A라는 문서의 일부분(한 10라인정도?)가... 간헐적으로 B결과에 박혀있는 상황이 발생하곤 합니다(정말 간헐적으로 생깁니다)

하나의 파일을 가지고만 수행을 했을경우 문제는 없는데 저렇게 대량의 파일을 처리했을때 뭔가 메모리를 잘못 읽는지

서로다른문서인데 그 일부분이 다른 문서에 박히는 경우가 생깁니다ㅠㅠ

혹시 위와 같은 원인을 아시는 분 계신가요? 정말 간헐적으로 발생하는것은 추적하기도 어렵네요. 메모릭leak같은것도 체크해봤는데 특별한 문제는 발견되지 않았습니다.

아래는 코드 입니다.

===코드==================================
#define BUF_SIZE 4096

#include
#include
#include

int main(int argc, char **argv)
{
FILE *inFile = NULL;
FILE *outFile = NULL;

char *line = NULL;
size_t len = 0;
ssize_t read = 0;

char name[BUF_SIZE]="\0";

inFile = fopen(argv[1], "r");
outFile = fopen(argv[2], "w");

while ((read = getline(&line, &len, inFile)) != -1)
{

line[read - 1] = '\0';

if (strncmp(line, "#TITLE:", 6) == 0)
{
strcpy(title, line + 6);
}
else if (strncmp(line, "#END:",5) == 0) // end of doc
{
fprintf(outFile, "#TITLE_ALIAS:%s\n", title);
}

fprintf(outFile, "%s\n", line);

}

if (line) free(line);
if (inFile) fclose(inFile);
if (outFile) fclose(outFile);
=====================================================

의견 주시면 감사하겠습니다~

zalhae의 이미지

말씀하신 대로 독립프로그램으로 동작했다면 문제가 없었을 것입니다.
제 생각에는 님께서 멀티 쓰레드 프로그램의 일부를 독립 프로그램으로 따로 뽑아서 문의하신 것 같은데,
만약 멀티 쓰레드 프로그램이였다면 getline() 때문에 말씀하신 문제가 발생합니다.
이유는 getline이 반환하는 &line은 getline 안에 있는 static 변수의 포인터를 받아 오는 것이기 때문입니다.
순간에 서로 다른 스레드 함수에서 getline()을 동시에 호출할 경우 읽어온 데이터의 내용이 엉키게 됩니다. (쉽게 재현됨)

따라서 이러한 상황을 회피하기 위해서는 배열일 미리 만들어 놓고 fgets() 함수를 이용하여 읽어오거나,
getline() 앞뒤에 mutex_lock을 사용해야 합니다. 개인적으로는 fgets()를 쓰시면 간단히 해결될 문제로 보입니다.

chanik의 이미지

http://man7.org/linux/man-pages/man3/getline.3.html

       ssize_t getline(char **lineptr, size_t *n, FILE *stream);
          .
          .
          .
       If *lineptr is NULL, then getline() will allocate a buffer for
       storing the line, which should be freed by the user program.  (In
       this case, the value in *n is ignored.)
 
       Alternatively, before calling getline(), *lineptr can contain a
       pointer to a malloc(3)-allocated buffer *n bytes in size.  If the
       buffer is not large enough to hold the line, getline() resizes it
       with realloc(3), updating *lineptr and *n as necessary.

매뉴얼에는 getline()이 malloc/realloc을 이용해 버퍼를 힙에 할당해서 반환하는 것으로 나옵니다.
glibc의 getline()이 이렇게 동작하고 newlib이나 uclibc의 getline()도 마찬가지이며
코드를 찾아봐도 그렇게 구현되어 있더군요.

getline()이 내부의 static 변수 포인터를 반환한다고 나오는 자료가 있는지요?
(만약 정말 static 변수 포인터를 반환한다면 문제가 잘 설명되기는 하네요)

zalhae의 이미지

뒤늦게 답글을 확인했네요. 잘못된 답변을 드려서 죄송합니다.
chanik 님이 답변 주신 내용이 맞습니다.

getline()의 내부 동작에서는 현재 할당된 메모리 용량을 확인하여 복사될 저장소의 공간이 부족한 경우는 reallocate하는 구조를 가지고 있네요. 매뉴얼을 확인하지 않고 습관적으로 알고 있다고 생각했는데(inet_ntoa() 등과 착각했습니다.),
... 덕분에 잘 알게 되었습니다.

이전에 답변드린 내용 중에서 fgets()로 변경해도 문제가 계속 발생된다면 아래와 같은 원인일 수도 있습니다.
1. multi-thread 생성시 설정된 스택 사이즈가 넘치는 경우 (코드 상에 문제가 없어도 스레드에서 처리하던 메모리가 다른 스레드의 메모리 영역을 침범하여 예상치 못한 동작을 할 수 있음)
2. 특히 함수 내에 사이즈가 큰 배열을 사용하던지 또는 재귀호출 함수 등을 사용하면 문제가 되는 상황에 도달할 수 있습니다.
3. 전역 변수를 서로 다른 thread에서 같이 사용하는 경우
4. 사용중인 title 변수의 용량이 strcpy(title, line+6); 에서 넘쳐서 다른 메모리 오버플로우 발생 가능성, strcpy는 될 수 있으면 사용하지 않는 것이 좋습니다.
5. title 변수 초기화를 하지 않은 상태에서 TITLE을 찾지 못했는데, #END에서 출력해 주는 경우
기타 등등 많은 원인이 있을 수 있네요.

감사합니다.

mirheekl의 이미지


질문상으로 보면 프로세스 자체를 여러 개 사용하시는 듯한데요.. 맞나요?

상황을 잡아내려면 체크섬같은걸 사용하면 되지 않을까 합니다. 입력을 가공할 수 있으면 굉장히 쉽겠죠. 라인별로 "나는 A파일이다"라는걸 심으면 되니까요. 그리고 저장 루틴에서 저부분이 예상된것과 다르면 프로세스를 일시정지해놓고 발견시 디버거를 붙여서 상황을 보시면 되겠죠. 또는 그게 아니더라도 내용상 어떤 공통점같은게 있다면 출력할때 이걸 비교해볼 수 있겠고요.

그리고 strcpy는 strcpy_s등으로 대체하시는 게 좋을듯 합니다. getline의 경우 혹시 5G나 되는 파일에 뉴라인 캐릭터가 전혀 없으면 어떤 동작을 하나요? 이부분도 체크해볼 필요가 있을듯 합니다.

--

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.