web-dev-qa-db-ja.com

ループの外側から、無限ループにあるpthreadを強制終了するにはどうすればよいですか?

スレッドを作成し、それを無限ループに入れます。 valgrindでコードをチェックすると、メモリリークが発生します。これが私のコードです:

_#include <pthread.h>
#include <time.h>

void thread_do(void){
    while(1){}
}

int main(){
    pthread_t th;   
    pthread_create(&th, NULL, (void *)thread_do, NULL);

    sleep(2);
    /* I want to kill thread here */
    sleep(2);
    return 0;
}
_

したがって、スレッドはmainで作成され、常にthread_do()を実行するだけです。 2秒後に内部からそれを殺す方法はありますかmainpthread_detach(th)pthread_cancel(th)の両方を試しましたが、それでもリークが発生します。

24
Pithikos

@sarnoldが指摘したように、デフォルトでは、スレッドはキャンセルポイントである関数を呼び出さずにpthread_cancel()でキャンセルすることはできません...しかし、これはpthread_setcanceltype()を使用して変更できます。スレッドのキャンセルタイプを遅延ではなく非同期に変更します。そのためには、ループを開始する前に、スレッド関数の開始近くにpthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);のようなものを追加します。その後、pthread_cancel(th)からmain()を呼び出してスレッドを終了できます。

ただし、この方法でスレッドをキャンセルしても(非同期かどうかに関係なく)、スレッド関数に割り当てられたリソースはクリーンアップされないことに注意してください(Kevinのコメントに記載されています)。これをきれいに行うには、次の方法があります。

  • スレッドが終了前にクリーンアップする必要があることを何も実行していないことを確認してください(例:malloc()を使用してバッファを割り当てる)
  • スレッドが終了した後、別の場所にあるスレッドの後をクリーンアップする方法があることを確認してください
  • pthread_cleanup_Push()およびpthread_cleanup_pop()を使用して、スレッドがキャンセルされたときにリソースをクリーンアップするクリーンアップハンドラーを追加します。リソースの割り当てとクリーンアップハンドラーの追加の間にスレッドがキャンセルされる可能性があるため、キャンセルタイプが非同期の場合でも、これは危険であることに注意してください。
  • pthread_cancel()の使用を避け、スレッドが何らかの条件をチェックして、いつ終了するかを判断するようにします(実行時間の長いループでチェックされます)。その後、スレッドは終了自体をチェックするため、チェック後に必要なクリーンアップを実行できます。

最後のオプションを実装する1つの方法は、ミューテックスをフラグとして使用し、ループテストで使用する関数にラップされたpthread_mutex_trylock()でテストすることです。

_#include <pthread.h>
#include <unistd.h>
#include <errno.h>

/* Returns 1 (true) if the mutex is unlocked, which is the
 * thread's signal to terminate. 
 */
int needQuit(pthread_mutex_t *mtx)
{
  switch(pthread_mutex_trylock(mtx)) {
    case 0: /* if we got the lock, unlock and return 1 (true) */
      pthread_mutex_unlock(mtx);
      return 1;
    case EBUSY: /* return 0 (false) if the mutex was locked */
      return 0;
  }
  return 1;
}

/* Thread function, containing a loop that's infinite except that it checks for
 * termination with needQuit() 
 */
void *thread_do(void *arg)
{
  pthread_mutex_t *mx = arg;
  while( !needQuit(mx) ) {}
  return NULL;
}

int main(int argc, char *argv[])
{
  pthread_t th;
  pthread_mutex_t mxq; /* mutex used as quit flag */

  /* init and lock the mutex before creating the thread.  As long as the
     mutex stays locked, the thread should keep running.  A pointer to the
     mutex is passed as the argument to the thread function. */
  pthread_mutex_init(&mxq,NULL);
  pthread_mutex_lock(&mxq);
  pthread_create(&th,NULL,thread_do,&mxq);

  sleep(2);

  /* unlock mxq to tell the thread to terminate, then join the thread */
  pthread_mutex_unlock(&mxq); 
  pthread_join(th,NULL);

  sleep(2);
  return 0;
}
_

スレッドが切り離されていない場合(通常、デフォルトではそうではありません)、スレッドを停止した後でpthread_join()を呼び出す必要があります。スレッドが切り離されている場合、スレッドを結合する必要はありませんが、スレッドが終了するタイミング(または、終了を示す別の方法を追加しない限り、およそ)は正確にはわかりません。

37
Dmitri

いくつかの小さな考え:

  1. スレッドをキャンセルしようとしていますが、所定のキャンセルポリシーが遅延キャンセルの場合、thread_do()はキャンセルポイントになりません。キャンセルポイントである関数は呼び出されないためです。
_    A thread's cancellation type, determined by
    pthread_setcanceltype(3), may be either asynchronous or
    deferred (the default for new threads).  Asynchronous
    cancelability means that the thread can be canceled at any
    time (usually immediately, but the system does not guarantee
    this).  Deferred cancelability means that cancellation will
    be delayed until the thread next calls a function that is a
    cancellation point.  A list of functions that are or may be
    cancellation points is provided in pthreads(7).
_
  1. 単純なサンプルコードではスレッドに参加していません。プログラムが終了する前にpthread_join(3)を呼び出します。
_    After a canceled thread has terminated, a join with that
    thread using pthread_join(3) obtains PTHREAD_CANCELED as the
    thread's exit status.  (Joining with a thread is the only way
    to know that cancellation has completed.)
_
6
sarnold