C++ 멀티스레딩


멀티스레딩은 컴퓨터가 동시에 두 개 이상의 프로그램을 실행할 수 있는 특별한 형태의 멀티태스킹입니다. 일반적으로 멀티태스킹에는 프로세스 기반과 스레드 기반이라는 두 가지 유형이 있습니다.

  • 프로세스 기반 멀티태스킹은 프로그램의 동시 실행입니다.

  • 스레드 기반 멀티태스킹은 동일한 프로그램 조각을 동시에 실행하는 것입니다.

멀티 스레드 프로그램은 동시에 실행될 수 있는 두 개 이상의 부분으로 구성됩니다. 이러한 프로그램의 각 부분을 스레드라고 하며, 각 스레드는 별도의 실행 경로를 정의합니다.

C++에는 멀티스레드 애플리케이션에 대한 기본 지원이 포함되어 있지 않습니다. 대신, 이 기능을 제공하는 것은 운영 체제에 전적으로 의존합니다.

이 튜토리얼에서는 Linux 운영 체제를 사용하고 있으며 POSIX를 사용하여 멀티스레드 C++ 프로그램을 작성한다고 가정합니다. POSIX 스레드 또는 Pthread는 FreeBSD, NetBSD, GNU/Linux, Mac OS X 및 Solaris와 같은 다양한 Unix 계열 POSIX 시스템에서 사용할 수 있는 API를 제공합니다.

Create Thread

POSIX 스레드를 생성하는 데 사용할 수 있는 다음 프로그램은 다음과 같습니다.

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)

여기서 pthread_create는 새 스레드를 생성하고 실행 가능하게 만듭니다. 다음은 매개변수에 대한 설명입니다.

매개변수 설명
thread 스레드 식별자 포인터를 가리킵니다.
attr스레드 속성을 설정하는 데 사용할 수 있는 불투명 속성 개체입니다. 스레드 속성 개체를 지정하거나 기본값인 NULL을 사용할 수 있습니다.
start_routine스레드가 생성되면 실행될 스레드 실행 함수의 시작 주소입니다.
arg함수 실행을 위한 인수입니다. 참조를 void 유형에 대한 포인터로 캐스팅하여 전달해야 합니다. 매개변수가 전달되지 않으면 NULL이 사용됩니다.

스레드가 성공적으로 생성되면 함수는 0을 반환합니다. 반환 값이 0이 아닌 경우 스레드 생성에 실패한 것입니다.

스레드 종료

다음 프로그램을 사용하면 POSIX 스레드를 종료하는 데 사용할 수 있습니다.

#include <pthread.h>
pthread_exit (status)

여기서 pthread_exit는 스레드를 명시적으로 종료하는 데 사용됩니다. 일반적으로 pthread_exit() 함수는 스레드가 작업을 완료하고 더 이상 존재할 필요가 없을 때 호출됩니다.

main()이 생성된 스레드보다 먼저 종료되고 pthread_exit()를 통해 종료되면 다른 스레드가 계속 실행됩니다. 그렇지 않으면 main()이 끝날 때 자동으로 종료됩니다.

예제

다음의 간단한 예제 코드는 pthread_create() 함수를 사용하여 5개의 스레드를 생성하고 각 스레드는 "Hello php!"를 출력합니다.

#include <iostream>
// 必须的头文件是
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

// 线程的运行函数
void* say_hello(void* args)
{
    cout << "Hello php!" << endl;
}

int main()
{
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];
    for(int i = 0; i < NUM_THREADS; ++i)
    {
        //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
        if (ret != 0)
        {
           cout << "pthread_create error: error_code=" << ret << endl;
        }
    }
    //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
    pthread_exit(NULL);
}

-lpthread 라이브러리를 사용하여 다음 프로그램을 컴파일합니다.

$ g++ test.cpp -lpthread -o test.o

이제 실행해 보세요. 프로그램은 다음과 같은 결과를 생성합니다.

$ ./test.o
Hello php!
Hello php!
Hello php!
Hello php!
Hello php!

다음의 간단한 예제 코드는 pthread_create() 함수를 사용하여 5개의 스레드를 생성하고 들어오는 매개변수를 받습니다. 각 스레드는 "Hello php!" 메시지를 인쇄하고 수신된 매개변수를 출력한 다음 pthread_exit()를 호출하여 스레드를 종료합니다.

//文件名:test.cpp

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS     5

void *PrintHello(void *threadid)
{  
   // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
   int tid = *((int*)threadid);
   cout << "Hello php! 线程 ID, " << tid << endl;
   pthread_exit(NULL);
}

int main ()
{
   pthread_t threads[NUM_THREADS];
   int indexes[NUM_THREADS];// 用数组来保存i的值
   int rc;
   int i;
   for( i=0; i < NUM_THREADS; i++ ){      
      cout << "main() : 创建线程, " << i << endl;
      indexes[i] = i; //先保存i的值
      // 传入的时候必须强制转换为void* 类型,即无类型指针        
      rc = pthread_create(&threads[i], NULL, 
                          PrintHello, (void *)&(indexes[i]));
      if (rc){
         cout << "Error:无法创建线程," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

이제 프로그램을 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.

$ g++ test.cpp -lpthread -o test.o
$ ./test.o
main() : 创建线程, 0
main() : 创建线程, 1
main() : 创建线程, 2
main() : 创建线程, 3
main() : 创建线程, 4
Hello php! 线程 ID, 4
Hello php! 线程 ID, 3
Hello php! 线程 ID, 2
Hello php! 线程 ID, 1
Hello php! 线程 ID, 0

스레드에 매개변수 전달

이 예에서는 구조를 통해 여러 매개변수를 전달하는 방법을 보여줍니다. 다음 예와 같이 스레드 콜백이 void를 가리키므로 임의의 데이터 유형을 전달할 수 있습니다.

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS     5

struct thread_data{
   int  thread_id;
   char *message;
};

void *PrintHello(void *threadarg)
{
   struct thread_data *my_data;

   my_data = (struct thread_data *) threadarg;

   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;

   pthread_exit(NULL);
}

int main ()
{
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;

   for( i=0; i < NUM_THREADS; i++ ){
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = "This is message";
      rc = pthread_create(&threads[i], NULL,
                          PrintHello, (void *)&td[i]);
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

위 코드를 컴파일하고 실행하면 다음과 같은 결과가 생성됩니다.

$ g++ -Wno-write-strings test.cpp -lpthread -o test.o
$ ./test.o
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 2 Message : This is message
Thread ID : 0 Message : This is message
Thread ID : 1 Message : This is message
Thread ID : 4 Message : This is message

스레드 연결 및 분리

다음 두 함수를 사용하여 스레드를 결합하거나 분리할 수 있습니다.

pthread_join (threadid, status) 
pthread_detach (threadid)

pthread_join() 서브루틴은 지정된 threadid 스레드가 종료될 때까지 호출 프로그램을 차단합니다. 스레드가 생성되면 해당 속성 중 하나가 스레드가 결합 가능한지 또는 분리되는지 여부를 정의합니다. 생성 시 연결 가능으로 정의된 스레드만 연결할 수 있습니다. 스레드가 생성될 때 분리 가능으로 정의되면 절대로 연결될 수 없습니다.

이 예제에서는 pthread_join() 함수를 사용하여 스레드가 완료될 때까지 기다리는 방법을 보여줍니다.

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS     5

void *wait(void *t)
{
   int i;
   long tid;

   tid = (long)t;

   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}

int main ()
{
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;

   // 初始化并设置线程为可连接的(joinable)
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   for( i=0; i < NUM_THREADS; i++ ){
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, wait, (void *)i );
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }

   // 删除属性,并等待其他线程
   pthread_attr_destroy(&attr);
   for( i=0; i < NUM_THREADS; i++ ){
      rc = pthread_join(threads[i], &status);
      if (rc){
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }

   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

위 코드를 컴파일하고 실행하면 다음과 같은 결과가 나옵니다.

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread 
Thread with id : 4  ...exiting 
Sleeping in thread 
Thread with id : 3  ...exiting 
Sleeping in thread 
Thread with id : 2  ...exiting 
Sleeping in thread 
Thread with id : 1  ...exiting 
Sleeping in thread 
Thread with id : 0  ...exiting 
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.