[ BBB - yocto (7) ] c에서의 멀티 스레드, 세마포어

1) 멀티 스레드


프로그램을 돌리면 프로세스가 되고, 프로세스 내에서 돌아가는 작은 실행 단위를 스레드라고 하는건 다들 알고 있는 사실일거야


1. 프로그램

컴파일을 통해 만들어진 바이너리 파일. 아직 실행되지 않았으면 프로그램

이것을 "./[바이너리 파일 이름]"으로 실행했으면 프로세스가 된다.


2. 프로세스

실행된 프로그램


3. 스레드

프로세스 내에서 돌아가고 있는 작은 실행단위


멀티 스레드는 스레드가 여러개인 환경을 의미해


여러개의 스레드를 c로 돌리는 방법은 pthread_create와 pthread_detach를 주로 사용해




1. pthread_create

int pthread_create(pthread_t *__restrict__ __newthread, const pthread_attr_t *__restrict__ __attr, void *(*__start_routine)(void *), void *__restrict__ __arg)

pthread_create는 pthread_create로 스레드 컨트롤 블록을 동적할당해


pthread_detach를 사용하면 스레드를 활성화 시켜서 멀티 스레드로 동작하게 만들 수 있어.


__newthread : pthread_t의 주소를 넘겨주면 스레드 식별자 번호를 반환해줘.

__attr : thread 속성을 지정해, 사용을 안할때는 NULL로 채워둬... 거의 사용을 안해

__start_routine : 스레드로 실행할 함수의 주소

__arg : 스레드에 넘겨줄 인자 값


반환 값 : 실패시 -1, 성공시 0


2. phread_detach

int pthread_detach(pthread_t __th)

스레드를 분리하는 하는 함수 pthread_create로 생성된 스레드 컨트롤 블럭의 자원을 스레드 종료시 자동으로 해제하게 한다.

__th : 스레드 식별자 번호


반환값 : 실패시 errno, 성공시 0


간단히 동작을 살펴보도록 하자

//main.c
#include "gpio.h"

int main()
{
    start_gpio_thread();
    while(1); //부모 프로세스가 끝나면, 자식 스레드가 종료됨에 주의
}



//gpio.h
#ifndef __GPIO__
#define __GPIO__

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>

#define MAX_THREAD_NUM 2

void start_gpio_thread();

#endif



//gpio.c
#include "gpio.h"

void thread_gpio(void *arg)
{
    int *argument_address = (int *)arg;
    printf("argument address : %p, \n", argument_address);

    while (1)
    {
        printf("hellow world!\n");
        sleep(1);
    }
}

void thread_gpio2(void *arg)
{
    int *argument_address = (int *)arg;
    printf("argument address : %p, \n", argument_address);

    while (1)
    {
        printf("bye world!\n");
        sleep(1);
    }
}

void start_gpio_thread()
{
    pthread_t tid[2] = {0, };
    void (*function_pointer[2]) = {&thread_gpio, &thread_gpio2};
    static int argument = 0; //정적 지역변수로, c의 메모리 영역중 데이터 영역에 저장됨. 즉 함수가 끝나도 메모리 값이 반환되지 않음
    //함수가 끝나면 지역변수는 메모리영역이 유효하지 않게 되니 정적 변수로 선언 해줘야함.
    int ret = 0;
    int i = 0;
    printf("argument address : %p, \n", &argument);

    for (i = 0; i < MAX_THREAD_NUM; i++)
    {
        ret = pthread_create(&tid[i], NULL, (void *)function_pointer[i], &argument);  //스레드 만들고 gpio, gpio2번 순서대로 스레드를 만듦, argument 주소를 넘겨주면 void *arg 에서 받음
        if (ret < 0)
        {
            printf("[%d]", i);
            perror("create thread gpio fail");
            return;
        }

        ret = pthread_detach(tid[i]); //스레드를 활성화함
        if (ret < 0)
        {
            printf("[%d]", i);
            perror("detach thread gpio fail");
            return;
        }
    }
}

함수 포인터를 사용해서 스레드를 만든 모습이야


함수 포인터는 함수를 가르키는 포인터 정도로 생각해줘


컴파일 할 때 컴파일 옵션으로 -lpthread를 LDFLAGS에 추가해야 pthread_ 함수를 찾을 수 없음 오류가 안난다.


저렇게 짜면 thread_gpio와 thread_gpio2가 동시에 수행돼서 hellow world와 bye world가 동시에 찍힐거야


그리고 start_gpio_thread의 argument 인자의 주소를 넘겨줬기 때문에, start_gpio와 thread_gpio, thread_gpio2의 주소 값이 같게 찍히겠지?







2) 세마포어와 뮤텍스

세마포어와 뮤텍스는 임계영역을 동시에 접근하고자 할 때 생길 수 있는 문제들을 방지하기 위해서 사용하지. 여기서 pthread_mutex와 semaphore를 사용해서 할 수 있어


여기서는 뮤텍스 보다는 좀 더 범용적으로 사용할 수 있는 semaphore를 사용할거야


//gpio.c
#include "gpio.h"

void thread_gpio(void *arg)
{
    int *argument_address = (int *)arg;
    printf("argument address : %p, \n", argument_address);

    while (1)
    {
        *argument_address=5;
        printf("hellow world!, arg : %d\n", *argument_address);
    }
}

void thread_gpio2(void *arg)
{
    int *argument_address = (int *)arg;
    printf("argument address : %p, \n", argument_address);

    while (1)
    {
        *argument_address=10;
        printf("bye world!, arg : %d\n", *argument_address);
    }
}

void start_gpio_thread()
{
    pthread_t tid[2] = {0, };
    void (*function_pointer[2]) = {&thread_gpio, &thread_gpio2};
    static int argument = 0; //정적 지역변수로, c의 메모리 영역중 데이터 영역에 저장됨. 즉 함수가 끝나도 메모리 값이 반환되지 않음
    int ret = 0;
    int i = 0;
    printf("argument address : %p, \n", &argument);

    for (i = 0; i < MAX_THREAD_NUM; i++)
    {
        ret = pthread_create(&tid[i], NULL, (void *)function_pointer[i], &argument);  //스레드 만들고 gpio, gpio2번 순서대로 스레드를 만듦, argument 주소를 넘겨주면 void *arg 에서 받음
        if (ret < 0)
        {
            printf("[%d]", i);
            perror("create thread gpio fail");
            return;
        }

        ret = pthread_detach(tid[i]); //스레드를 활성화함함
        if (ret < 0)
        {
            printf("[%d]", i);
            perror("detach thread gpio fail");
            return;
        }
    }
}

이를태면 이렇게 짠 경우 hellow world에서는 5가 bye world에서 10이 나오게 하고 싶지만 프린트 


하기직전에 컨택스트 스위칭이 일어나면 원하는대로 동작이 안되버리지


이를 정확하게 확인해 보고 싶으면 grep명령어로 확인해 보면 돼

$ ./main | grep "hellow world" | grep 10
$ ./main | grep "bye world" | grep 5



start_gpio_thread의 argument를 동시에 접근하여 사용하기 때문이야 이를 방지하기 위해 세마포어를 주로 써.


1. sem_init

int sem_init(sem_t *__sem, int __pshared, unsigned int __value)

세마포어 초기화 함수


__sem : (세마포어 구조체)에 대한 정보가 담겨있는 sem_t 변수의 주소

__pshared : 다른 스레드와 공유 할 것인지 여부

__value : 세마포어의 숫자


리턴값 : 실패시 -1, 성공시 0


2. sem_post

int sem_post(sem_t *__sem)

세마포어의 수를 증가시키는 함수, 단 sem_init에서 설정한 세마포어 숫자를 넘을 수 없음


__sem : 초기화한 sem_t 주소를 넘겨주면 됨


3. sem_wait

int sem_wait(sem_t *__sem)

세마포어의 수를 감소시키는 함수, 단 0보다 작게 하는건 안됨.


세마포어의 수가 0이면 세마포어 숫자가 1 이상이 될때까지 대기


__sem : 초기화한 sem_t 주소를 넘겨주면 됨


이를 사용해 다시 코드를짜면


//gpio.c
#include "gpio.h"
#include <semaphore.h>

sem_t gpio_sem;

void thread_gpio(void *arg)
{
    int *argument_address = (int *)arg;
    printf("argument address : %p, \n", argument_address);

    while (1)
    {
        sem_wait(&gpio_sem);
        *argument_address=5;
        printf("hellow world!, arg : %d\n", *argument_address);
        sem_post(&gpio_sem);
    }
}

void thread_gpio2(void *arg)
{
    int *argument_address = (int *)arg;
    printf("argument address : %p, \n", argument_address);

    while (1)
    {
        sem_wait(&gpio_sem);
        *argument_address=10;
        printf("bye world!, arg : %d\n", *argument_address);
        sem_post(&gpio_sem);
    }
}

void start_gpio_thread()
{
    pthread_t tid[2] = {0, };
    void (*function_pointer[2]) = {&thread_gpio, &thread_gpio2};
    static int argument = 0; //정적 지역변수로, c의 메모리 영역중 데이터 영역에 저장됨. 즉 함수가 끝나도 메모리 값이 반환되지 않음
    int ret = 0;
    int i = 0;
    printf("argument address : %p, \n", &argument);

    ret = sem_init(&gpio_sem, 0, 1);

    for (i = 0; i < MAX_THREAD_NUM; i++)
    {
        ret = pthread_create(&tid[i], NULL, (void *)function_pointer[i], &argument);  //스레드 만들고 gpio, gpio2번 순서대로 스레드를 만듦, argument 주소를 넘겨주면 void *arg 에서 받음
        if (ret < 0)
        {
            printf("[%d]", i);
            perror("create thread gpio fail");
            return;
        }

        ret = pthread_detach(tid[i]); //스레드를 활성화함함
        if (ret < 0)
        {
            printf("[%d]", i);
            perror("detach thread gpio fail");
            return;
        }
    }
}


다시 bye world에서 5가 출력되는 부분을 찍어보면 아무것도 안찍히지

이렇게 동시 접근 문제를 해결할 수 있어

댓글

이 블로그의 인기 게시물

[ BBB - yocto (9) ] 장치트리(DEVICE TREE)

[ BBB - yocto (11) ] uart

[ BBB - yocto (5) ] makefile 작성법과 컴파일 자동화