라이프로그


신호등 지표 카테고리 개설 신호등 지표

아래에 글을 다 쓰고 보니 스팀잇이 더 좋겠다는 생각을 가지게 되었습니다.



---

하지만 아래 글은 일단 남겨두겠습니다.
이글에 댓글을 달기보다, 위 스팀잇 포스트에 댓글을 달아주시면 제가 더 빨리 답글하겠습니다.

---

신호등 지표와 관련된 댓글은 여기 카테고리의 포스트에 달아주시면 좋겠습니다.

현재 트뷰 링크에 댓글은 너무 많아서 해당 지표 페이지가 너무 무거워젔습니다.

그리고 디시갤러리에 유료화 준비중인 지표에 대한 글이 자주 올라오는 것도 좋지 않다고 보입니다.

일단 임시로 여기 카테고리를 개설하였는데

저 외에는 댓글만 달 수 있다는 단점이 있네요.

적당한 게시판을 하나 개설할 수 있다면 참 좋겠는데 어디가 좋을지 모르겠습니다.



c++이야기. 멀티스레드와 파일(fstream) 다루다가 빡친 경험 일반 IT

하나의 파일을 처리하는 하나의 객체가 있다. 

더 빨리 처리하기위해 파일을 a라고 하자, a파일을 n개의 영역으로 나누고, n개의 스레드에 각각 객체를 만들어서 영역을 배정했다.
(편의상 a파일을 그냥 파일이라고 부르기도 하겠다.)

각 스레드는 하나의 객체를 가지며, 그 객체는 그 스레드에서 자기 영역을 처리하면 된다. 
추가 스레드에서 파일에 직접 접근하는 것은 아니다.
내가 아는한 a파일에 접근하는 것은 메인 스레드 뿐이다.
메인스레드가 a파일의 내용을 변수로 읽어 들이면, 객체들이 그 변수를 처리한다. 
(a파일에 대한 읽기나 쓰기 권한을 가지는 것은 오직 메인스레드 뿐이다.)


우선 스레드를 생성하고, 스레드안에서 객체를 생성했다.

모든 스레드가 초기화가 끝나면, 메인스레드가 파일을 열고 읽어들여서 작업배정을 한다.

문제가 여기서 터졌다. 스레드를 한 20개정도 만들면 파일이 안열리는 것이다.

fstream이 파일을 열려고하면 Too many open files 에러를 출력한다. (ifstream 이든 ofstream 이든)

도무지 이해가 안갔다. 왜냐면 각 스레드에서 생성된 각각의 객체들은 내가 아는 바로는 (a가 아닌)파일을 열더라도 생성과정(초기화)이 끝나면 그 파일들을 닫기 때문이다.

----------------------------

너무 혼란스러워서 다양한 실험을 해보았다.

#우선 용어정리. 하나의 추가스레드와 그 안에서 생성된 객체를 묶어서 워커(작업자)라고 한다.

1. 워커를 8명 생성한 뒤에 a파일을 열면 잘 열린다. 쓰기용이든 읽기용이든 잘 열린다. (즉, 워커 자체가 그 파일에 대한 권한을 가지려고 하는 것은 아님이 증명됨)

2. 워커를 20명 생성한 뒤에 파일을 열려고하면 too many open files 에러

3. a파일을 미리 열고, 워커를 대량(20명이상) 생성한 다음에 파일을 닫는다. 그러면 워커들이 살아있는 동안에 다시 파일을 열 수 있다. (ifstream이든 ofstream이든)
워커를 생성하기 직전에, 그 파일을 열고 닫아버리면 워커 생성후에 열수 없다. 즉, 중요한 것은 워커들을 생성하기전에 열어두고, 생성이 완료될 때 까지 파일을 연 상태로 가지고 있어야 한다. 여기까지 도달하면 a파일을 닫고 다시 열 수도 있다.

4. 워커는 100명도 생성이 가능하다. 죽였다가 다시 100명을 생성할 수 있으며, 이 워커들은 모두 정상이다. 워커 생성은 언제든지 가능했으며, 얼마든지 가능했고, 항상 정상적인 워커들이였다.

5. 워커8명을 생성하고 파일을 열고 처리하고 닫고 워커를 죽인다. 즉 실험1번의 내용인데, 이 과정을 프로그램이 3번 반복하면 3번째부터 파일을 열지 못한다. 프로그램을 종료하고 다시 시작해야 가능해진다. (2번까지는 반복이 가능)

6. 파일을 열어두고 워커100명을 생성하고 파일을 닫고 다시 열어서 처리한다음 다시 닫는다. 그리고 워커를 다 죽인다. 이 과정을 여러번 반복하는 것은 가능하다. (이것은 실험3번을 반복하는 것) 과거에 아무리 많은 워커를 사용했더라도 다 죽어있기만 하면 파일을 열 수 있다는 것이다.
이것이 증명하는 것은, 워커가 생성될 때 혹여나 다른 파일들을 열었다 하더라도, 워커가 파괴될 때는 자기가 연 파일을 모두 닫는다는 것이다. 
그렇지 않다면 워커 생성파괴를 수백번 반복한 뒤에는 그 어떤 파일도 열 수가 없게 될 것이다. 
이걸 생각해보면, 5번 실험결과는 정말 이해하기 어렵다.


--------------------------------

여기서 밝히지만 그 워커...그러니까 그 객체...그 클래스 자체를 내가 코딩했다. 너무 방대한 소스코드여서 다 확신할 수는 없지만, 내가 알기로 추가 스레드에서 생성되는 객체는 따로 파일을 열지 않는다. 

-------------------------------

그래서 파일을 열지 못하는 정확한 조건을 적자면 제법 복잡한데 ...아무튼 이렇다. 아래 조건을 모두 만족해야한다.
1. 이전에 워커를 생성했던 적이 대략 20번 정도 되어야 한다. 소량 생산하고 죽이기를 반복해도 되고, 한번에 대량 생산해도 이 조건을 만족할 수 있다. (16번 이하에서는 파일 열기 가능함)

2. 현재 살아있는 워커가 존재해야한다. (워커를 아무리 많이 생성했더라도 다 죽여서 현재 살아있는 워커가 없다면 파일을 열 수 있다.)

3. 그 파일을 미리 열어두고 워커를 생성한 것은 아니여야 한다. (살아있는 워커가 아무리 많아도, 그 파일이 이미 열려있었다면 언제든지 다시 닫고 열기가 가능하다. )

이렇게 3가지 조건을 모두 만족하면 파일을 열지 못한다.


내가 확신하는 것

1. 해당 파일에 대한 권한 문제는 아니다. 워커들은 결고 크 파일에 대한 권한에 관여하지 않는다.

2. 워커는 개별적으로 a가 아닌 다른 파일을 가질지는 몰라도 죽을 때는 분명히 다 닫는다. 스레드 또한 분명히 종료되고 join된다. 스레드간 자원 관리를 위한 뮤텍스 또한 분명히 해제된다.


내가 최종적으로 내린 문제의 원인
- 가질 수 있는 핸들(윈도우에서 사용하는 그것) 수의 문제인듯. 파일핸들은 스레드핸들이 너무 많이 생성되면 얻기가 힘든 것 같다. 스레드핸들이 많이 생성되었던 적이 있는 상태에서, 살아있는 추가 스레드가 존재한다면 새로운 파일핸들을 얻기가 힘들다는 것이다.



그러니까 내 나름의 결론은 내 소스코드의 잘못이라기 보다 운영체제에 뭔가 작동상 특이점이 있다는 것이다.
그냥 내가 알아서 잘 피해야겠다고 생각중....



근 몇일간 이 문제를 해결하기위해 수많은 노력을 기울였는데 참 허망하다.....그리고 아직도 나는 내가 코딩한 이 객체에 대한 믿음이 확실하지 않아서 답답하다. 솔직히 운영체제에 문제가 있다고 판단하다니..좀 이상하자나??? 그러나 실험결과들을 보면 아무리 생각해도 운영체제 문제같아....




울산에도 가우리가 생겼네! 게임


요기에도 글이 있네요. http://bbs.ruliweb.com/hobby/board/600001/read/71

친구들이 피규어랑 건담 이런것들 보러 가자고해서 같이 가봤는데 기대했던 것 보다 훨씬 물건 수준이 높고 많네요.

저랑 친구들은 특히 엑스맨이나 스타워즈 같은 계열보다는 일본쪽에서 나온 것들을 좋아하는데 그런 부분에서 취향이 맞았나 봅니다.

저는 피규어는 취미가 없지만 친구들 따라 갔다가 이것저것 신기한 것들을 보고 왔네요.

개인적으로는 요츠바랑에 후카가 마음에 들었는데...음..구매할지는 다시 생각좀 해봐야겠어요.
후카는 다른 여성캐릭에 비해 몸매가 그다지 좋지 않고 현실적이랄까... 그런걸 그대로 구현했더군요.
아마 이런거였던걸로 기억 : http://pds6.egloos.com/pds/200712/21/90/e0012190_476b63824e0c4.jpg

위치는 : 중구 태화동 난곡록 32-1번길 1층

저는 사진을 하나도 안찍어서 사진은 없습니다. :( 링크타고가면 뭔가 많네요.

울산에는 이런 매장이 없었는데 하나 생겨서 참 좋네요.

요런거 구경하려면 최소 부산, 아니면 대구까지 가곤 했는데 이젠 가까운 곳에서도 !!

아 그리고 공방도 운영할 계획이라고 합니다. 매장 자체는 작지만 공방까지 합치면 제법 ㅎㅎ
그리고 사장님이 참 친절하네요.



java와 c++ 를 위한 JNA에서 wstring 사용하기 프로그래밍

java에서 c++함수를 어떻게든 써보려면 JNI를 사용하게 되는데 JNI가 얼마나 구린지 당하고 나면 JNA가 써보고 싶어진다.
그런데 JNA라고 마냥 쉬운건 아니다. 일단 JNA가 C++이 아닌 C를 대상으로 설계되어서 string과 wstring을 바로 쓸 수 없다.
당연히 const wchar_t* 를 이용해야 한다.

과정이 상당히 긴데 하나씩 기록을 위해 남겨본다.

1. cpp에 함수가 존재한다고 가정한다. 예로
wstring func(wstring s)
{
return s+ L" hello";
}

2. func를 c스타일로 랩핑해야한다. 대충 하자면
wstring tmp;
const wchar_t* func_c(const wchar_t *p)
{
tmp = func(p);
return tmp.c_str();
}

주의할 점은 tmp이다. 전역변수로 존재하는데 이것의 이름은 알아서 적당히 짓자.
왜 이것이 필요하냐면 자바에서 func_c를 호출해서 받은 리턴 스트링은 레퍼런스 타입이기 때문이다.
즉, c++에서는 함수가 종료되어도 그 wstring의 내용은 살아있어야 한다. 만약 tmp가 func_c의 종료와함게 사라지는 지역함수라면 자바쪽에서 리턴된 스트링을 읽을 기회가 없게된다. 
그래서 라이프사이클이 함수보다 긴 전역으로 해둔 것이다.

변수의 라이프사이클을 자바가 관리하게 하려면 동적할당을 하는 방법이 있다. 조금 귀찮아도 이 방법을 권장한다.

void jna_wstring(wchar_t* str, wchar_t** pp)
{
wstring ret = func(str);

*pp = new wchar_t[ret.length() + 1];
wcscpy_s(*pp, ret.length() + 1, ret.c_str());
}
void cleanup(wchar_t* p)
{
//free(p);
delete[] p;
}

주의할 점은 pp와 cleanup이다. pp는 출력용 인자로, 동적할당을 받아준다.
cleanup은 java가 pp로부터 string복사가 끝나면 사용할 것이다.

3. func_c를 위한 해더파일을 만든다. 내용은 대강 이렇다. (제법 어렵다.)
#ifdef DLL_EXPORT
#define DLL_EX_IN  __declspec(dllexport)
#else
#ifdef DLL_IMPORT
#define DLL_EX_IN __declspec( dllimport )
#else
#define DLL_EX_IN
#endif
#endif

#ifdef __cplusplus
extern "C" 
{
#endif 

DLL_EX_IN const wchar_t* func_c(const wchar_t* p);

DLL_EX_IN void jna_wstring(wchar_t* str, wchar_t** pp);
DLL_EX_IN void cleanup(wchar_t* p);

#ifdef __cplusplus
}
#endif 


4. 이 cpp를 컴파일 할 때에는 dll생성으로 설정해야 한다. 그리고 DLL_EXPORT를 정의해야한다.
visual studio 2013~2015 라면 프로젝트 속성에서 (영문 기준으로)
Configuration Properties > C/C++ > Preprocessor > Preprocessor Definitions 에 DLL_EXPORT를 적는다. 요소마다 사이에 ; 를 넣는다.
그리고
Configuration Properties > General > Project Defaults > Configuration Type > Dynamic Library (.dll)


5. 릴리즈로 컴파일 하는 것이 좋다고 하는데 큰 의미가 없는 것 같다. 내가 다 테스트해보니 디버그모드로 컴파일해도 괜찮다. 아무튼 dll파일을 만든다.

6. jna.jar를 받아야 한다. 구글링해서 받자.
내가 받은 링크 https://github.com/java-native-access/jna/blob/master/dist/jna.jar

7. 자바는 이클립스를 기준으로 설명한다. 이클립스에서 프로젝트 하나를 만들자. 
프로젝트 폴더에 jna.jar를 복사한다.
프로젝트 속성에서
Java Build Path > Libraries > Add JARs를 해서 jan.jar를 추가하자.
아니면 어쩌면 add external jars 하면 직접 폴더에 복사 안해도 되려나?


8. java소스를 작성한다. (드디어!) 파일이름이 CMain.java라고 하자. 그럼 클래스 이름이 CMain이다.
그리고 dll파일이 프로젝트 파일의 상위에서 bin볼더안에 fun.dll로 있다고 가정한다.

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.WString;
import java.util.Scanner;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.Pointer;

interface CLibrary extends Library {
WString func_c(WString str);

void jna_wstring(WString str, PointerByReference pbr);
void cleanup(Pointer p);
}

public class CMain{
public static void main(String[] args){
final CLibrary ut = (CLibrary)Native.loadLibrary( ("../bin/fun.dll"), CLibrary.class);
string str = ut.func_c(new WString("Joon")).toString();
System.out.println(str);

PointerByReference pbr = new PointerByReference();
clib.jna_wstring( new WString("자바"), pbr);
String s = str_and_rel(pbr, clib);
System.out.println(s);
System.out.println("good bye");

}

public static String str_and_rel(PointerByReference pbr, CLibrary clib)
{
Pointer p = pbr.getValue();
String s = p.getWideString(0);
clib.cleanup(p);
return s;
}
}


9. 실행시켜서 성공했는지 확인한다.
자바 씨뿔뿔 이라고 나올 것이다. 그러면 자바쪽에서의 한글이 c++로 잘 넘어간 것이고, 
c++에서의 한글도 자바로 잘 넘어온 것이다.


10. 내 컴퓨터에서는 잘 되는데, 다른 컴퓨터에 설치해주면 작동이 안된다?
이건 아마 자바문제가 아니라 c/c++로 만든 dll이 다른 dll을 필요로해서 그렇다.
물론 개발자는 다른 dll을 직접 부른 기억이 없을 수 있는데, 비쥬얼스튜디오에서 바이너리를 만들 때 공통적인 부분을 외부 dll을 사용하게 하는 것이 있다. 아무튼 이것은 "재배포 패키지"라는 것을 설치하면 된다.
예를 들어 vs2013에서 개발한 dll이면 "vs2013 재배포 패키지" 라는 것을 설치해야 한다.
구글에서 검색하면 나오고 파일 이름은 보통 vcredist 로 시작한다.


==========================================================
이클립스가 아닌 cmd에서 하는 방법을 포함하여 파일로 올린다.
cmd 명령어 2개(컴파일, 실행)는 java_source/jna설명.txt 안에 있다.
핵심 java소스는 java_source/HelloWorld.java 이다
cpp소스는 jna_test/Source.cpp 이다.

===========================================================
후기

일단 JNA가 C++이 아닌 C를 대상으로 하는 바람에 메모리 관리에서 큰 불편함이 있다.
그래서 위에서는 전역변수 tmp를 이용하고 있는데, 실제로 나는 어떤 클래스의 객체의 맴버변수이다.
나는 그 객체의 라이프사이클을 관리하는 방법과, 그 객체의 맴버함수를 랩핑한 뒤에, 그 특정 객체의 맴버함수가 호출되도록 하는 여러가지 방법들을 구현해놔서 이렇게 했지만....
아무튼 라이프사이클에 주의가 많이 필요하다는 것은 찝찝하다. 

그러나 싱글스래드로 간단한 프로그램을 만드려는 사람에겐 이 방법(func_c)이 정말정말 유용할 수 있다.

다른 방법으로는 PointerByReference(이하 pbr)와 Point 그리고 getWideString과 cleanup..(필요한게 많다...) 이것들을 사용하는 것이다.
이러면 자바쪽에서 직접 메모리해제를 결정할 수 있다. 복잡한것 같아도 객체나 전역으로 라이프사이클을 관리하는 것이 더 힘들 수도 있다.
그리고 pbr에서 String을 얻고는 바로 해제까지 해주는 과정을 하나의 함수로 만들어봤는데 제법 편할 것이다.
정석을 고르라면 pbr을 사용하여 자바에서 string복사가 끝나자마자 동적메모리를 직접 해제해주는 것이다.



JNI에 비하면 굉장히 간단하다고 해놓고 결국 JNA도 wstring을 쓰려면 제법 귀찮다는 점에선 비슷하다.
그래도 이게 성공하면 c++과 java가 서로 unicode 유니코드를 쉽게 다룰 수 있게 된다. 정말 기쁘지 아니한가? >.<

1 2 3 4 5 6 7 8 9 10 다음