[C/C++] C 공유 라이브러리에 뮤텍스 적용하기 (pthread mutex in shared library)

pikamon·2022년 7월 11일
0

C/C++

목록 보기
4/9

공유 라이브러리에 뮤텍스를 적용해야 하는 문제가 있었다. 그 공유 라이브러리는 I2C 통신 라이브러리였으며, 리눅스 상에 약 7~8개의 백그라운드 프로세스가 로딩하여 사용하고 있었다. 정말 드문 경우지만, 복수 개의 프로세스가 해당 라이브러리를 동시에 호출하면 하나만 실행되고 나머지는 실행되지 않는 경우가 있었다. (쫑난다고 표현함..)

라이브러리의 동작이 완벽함을 보장하기 위해서는 뮤텍스를 적용할 필요가 있었다. 해당 라이브러리는 C로 구현되어 있었고, 문득 pthread lock의 사용법이 궁금해졌다.

인터넷에 나와있던 pthread 예제들은 대부분 하나의 application 내에서 공유 자원을 전역 변수로 설정하고, main문에서 여러 개의 thread를 생성하여 동기화 결과를 확인하는 내용이었다.
필자의 경우는 리눅스 서비스들이 하나의 application에서 fork되는 애들이 아니었기 때문에, 태생이 서로 다른 프로세스 간의 공유 자원 상호 배타 점유가 가능한지 직접 확인해보고 싶었다.


아래는 필자가 구현한 내용이다.

1. Shared Library 구현

공유 라이브러리 내에 임계 영역을 마련하고, 해당 영역 위아래로 pthread lock을 걸도록 하였다.

  • shared_library.h
#ifndef __SHARED_LIBRARY__
#define __SHARED_LIBRARY__

int critical_section(const char *name);

#endif /*__SHARED_LIBRARY__*/
  • shared_library.c
#include "shared_library.h"

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define FALSE 0
#define TRUE  1

int critical_section(const char *name)
{
	int count = 0;
	static pthread_mutex_t mutex;
	static unsigned char is_mutex_inited = FALSE;

	if (is_mutex_inited == FALSE) // 최초 1번만 수행됨.
	{
		pthread_mutex_init(&mutex, NULL);
		is_mutex_inited = TRUE;
		printf("mutex = %p\n", &mutex);
	}

	pthread_mutex_lock(&mutex);

	/*---------------- Critical Section ----------------*/
	for (int i = 0; i < 5; i++)
	{
		printf("[%s:%d] %s count = %d\n", __func__, __LINE__, name, count++);
		usleep(1);
	}
	/*---------------- Critical Section ----------------*/

	pthread_mutex_unlock(&mutex);

	return 0;
}

critical_section 이란 이름의 API를 만들고, API 내부에 mutex 구조체를 static으로 선언하였다. for문을 5회 돌면서 count를 1씩 증가시키며 로그를 출력한다.

궁금해서 mutex 자료구조의 주소값을 찍어봤는데, 커널 메모리에 할당된 것처럼 보인다.

mutex = 0x7f244f945e90

2. Application 구현

Application에서 쓰레드를 여러 개 생성하여 각 쓰레드가 공유 라이브러리를 호출하도록 하였다.

공유 자원이 Application 내에 있지 않고 공유 라이브러리에 있다는 점에서 실제 필자의 환경과 동일하다고 판단했다.

  • process.c
#include "shared_library.h"

#include <pthread.h>

void *task(void *arg)
{
	critical_section((char*)arg);
}

int main()
{
	pthread_t thread1;
	pthread_t thread2;
	pthread_t thread3;
	pthread_t thread4;

	pthread_create(&thread1, NULL, task, (void*)"thread1");
	pthread_create(&thread2, NULL, task, (void*)"thread2");
	pthread_create(&thread3, NULL, task, (void*)"thread3");
	pthread_create(&thread4, NULL, task, (void*)"thread4");

	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);
	pthread_join(thread3, NULL);
	pthread_join(thread4, NULL);

	return 0;
}

3. Makefile 작성

전체 빌드를 쉽게 하기 위해 빌드 스크립트를 Makefile로 작성하였다.

  • Makefile
all: shared_library
	gcc -o process process.c libshared_library.so.1.0.0 -lpthread


shared_library:
	gcc -fPIC -c -o libshared_library.o shared_library.c
	gcc -shared -Wl,-soname,libshared_library.so.1 -o libshared_library.so.1.0.0 libshared_library.o -lc
	ln -s libshared_library.so.1.0.0 libshared_library.so.1
	ln -s libshared_library.so.1.0.0 libshared_library.so

clean:
	rm -f libshared_library.o libshared_library.so libshared_library.so.1 libshared_library.so.1.0.0
	rm -f process

4. 실행

make를 입력하여 빌드한다.

make

그리고 리눅스 환경변수에 라이브러리 경로를 추가해준다.

export LD_LIBRARY_PATH=`echo $LD_LIBRARY_PATH`:`pwd`

그리고 process를 실행한다.

./process

그러면 아래와 같이 출력된다.

mutex = 0x7ff949a6d0a0
[critical_section:28] thread4 count = 0
[critical_section:28] thread4 count = 1
[critical_section:28] thread4 count = 2
[critical_section:28] thread4 count = 3
[critical_section:28] thread4 count = 4
[critical_section:28] thread3 count = 0
[critical_section:28] thread3 count = 1
[critical_section:28] thread3 count = 2
[critical_section:28] thread3 count = 3
[critical_section:28] thread3 count = 4
[critical_section:28] thread2 count = 0
[critical_section:28] thread2 count = 1
[critical_section:28] thread2 count = 2
[critical_section:28] thread2 count = 3
[critical_section:28] thread2 count = 4
[critical_section:28] thread1 count = 0
[critical_section:28] thread1 count = 1
[critical_section:28] thread1 count = 2
[critical_section:28] thread1 count = 3
[critical_section:28] thread1 count = 4

각 쓰레드가 완전히 실행을 끝마친 후에 다른 쓰레드의 동작이 수행되는 것을 볼 수 있다.

비교를 위해 lock을 거는 코드를 주석 처리하고 실행해보자.

  • shared_library.c
...
	// pthread_mutex_lock(&mutex);
...
	// pthread_mutex_unlock(&mutex);
...

그리고 재빌드 후 실행하면

make clean && make

아래와 같이 출력된다.

mutex = 0x7fb00bf6f080
[critical_section:28] thread4 count = 0
[critical_section:28] thread4 count = 1
[critical_section:28] thread3 count = 0
[critical_section:28] thread4 count = 2
[critical_section:28] thread3 count = 1
[critical_section:28] thread3 count = 2
[critical_section:28] thread4 count = 3
[critical_section:28] thread3 count = 3
[critical_section:28] thread4 count = 4
[critical_section:28] thread3 count = 4
[critical_section:28] thread2 count = 0
[critical_section:28] thread2 count = 1
[critical_section:28] thread1 count = 0
[critical_section:28] thread2 count = 2
[critical_section:28] thread1 count = 1
[critical_section:28] thread2 count = 3
[critical_section:28] thread1 count = 2
[critical_section:28] thread2 count = 4
[critical_section:28] thread1 count = 3
[critical_section:28] thread1 count = 4

쓰레드의 실행 도중에 다른 쓰레드가 난입해서 실행되는 것을 볼 수 있다.

결국 원하는 lock 동작이 정상적으로 되었음을 알 수 있다.

profile
개발자입니당 *^^* 깃허브 https://github.com/pikamonvvs

0개의 댓글