Linuxのソケットに関する質問
Accept()呼び出しでブロックされるワーカースレッドがあります。着信ネットワーク接続を待機して処理し、次の接続のリッスンに戻ります。
プログラムを終了するときは、このネットワークワーカースレッド(メインスレッドから)にaccept()呼び出しから戻るように通知し、ループを正常に終了してクリーンアップコードを処理するにはどうすればよいですか。
私が試したいくつかのこと:
シグナルを送信するためのpthread_kill。これを行うのは厄介だと感じます。さらに、スレッドがシャットダウンロジックを実行できるようにすることはできません。また、プログラムも終了します。なるべく信号を避けたい。
pthread_cancel。同上。それはスレッドの厳しい殺害です。それと、スレッドが何か他のことをしている可能性があります。
Accept()を中止するために、メインスレッドからリッスンソケットを閉じます。これは確実に機能しません。
いくつかの制約:
解決策がリッスンソケットを非ブロッキングにすることを含む場合、それは問題ありません。しかし、終了条件をチェックするために数秒ごとにselect呼び出しを介してスレッドがウェイクアップするという解決策を受け入れたくありません。
終了するスレッド条件は、終了するプロセスに関連付けられていない可能性があります。
基本的に、私が求めているロジックは次のようになります。
void* WorkerThread(void* args)
{
DoSomeImportantInitialization(); // initialize listen socket and some thread specific stuff
while (HasExitConditionBeenSet()==false)
{
listensize = sizeof(listenaddr);
int sock = accept(listensocket, &listenaddr, &listensize);
// check if exit condition has been set using thread safe semantics
if (HasExitConditionBeenSet())
{
break;
}
if (sock < 0)
{
printf("accept returned %d (errno==%d)\n", sock, errno);
}
else
{
HandleNewNetworkCondition(sock, &listenaddr);
}
}
DoSomeImportantCleanup(); // close listen socket, close connections, cleanup etc..
return NULL;
}
void SignalHandler(int sig)
{
printf("Caught CTRL-C\n");
}
void NotifyWorkerThreadToExit(pthread_t thread_handle)
{
// signal thread to exit
}
int main()
{
void* ptr_ret= NULL;
pthread_t workerthread_handle = 0;
pthread_create(&workerthread, NULL, WorkerThread, NULL);
signal(SIGINT, SignalHandler);
sleep((unsigned int)-1); // sleep until the user hits ctrl-c
printf("Returned from sleep call...\n");
SetThreadExitCondition(); // sets global variable with barrier that worker thread checks on
// this is the function I'm stalled on writing
NotifyWorkerThreadToExit(workerthread_handle);
// wait for thread to exit cleanly
pthread_join(workerthread_handle, &ptr_ret);
DoProcessCleanupStuff();
}
パイプを使用して、スレッドを終了することをスレッドに通知できます。次に、パイプとリスニングソケットの両方を選択するselect()
呼び出しを行うことができます。
例(コンパイルされますが、完全にはテストされていません):
_// NotifyPipe.h
#ifndef NOTIFYPIPE_H_INCLUDED
#define NOTIFYPIPE_H_INCLUDED
class NotifyPipe
{
int m_receiveFd;
int m_sendFd;
public:
NotifyPipe();
virtual ~NotifyPipe();
int receiverFd();
void notify();
};
#endif // NOTIFYPIPE_H_INCLUDED
// NotifyPipe.cpp
#include "NotifyPipe.h"
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
NotifyPipe::NotifyPipe()
{
int pipefd[2];
int ret = pipe(pipefd);
assert(ret == 0); // For real usage put proper check here
m_receiveFd = pipefd[0];
m_sendFd = pipefd[1];
fcntl(m_sendFd,F_SETFL,O_NONBLOCK);
}
NotifyPipe::~NotifyPipe()
{
close(m_sendFd);
close(m_receiveFd);
}
int NotifyPipe::receiverFd()
{
return m_receiveFd;
}
void NotifyPipe::notify()
{
write(m_sendFd,"1",1);
}
_
次に、select
とreceiverFd()
を組み合わせ、notify()
を使用して終了を通知します。
shutdown()
呼び出しを使用してソケットを閉じます。これにより、ファイル記述子を有効に保ちながら、ブロックされているスレッドがすべてウェイクアップされます。
別のスレッドBが使用している記述子のclose()
は本質的に危険です。別のスレッドCが新しいファイル記述子を開き、閉じたものの代わりにスレッドBが使用する場合があります。 dup2()
a _/dev/null
_はその問題を回避しますが、ブロックされたスレッドを確実にウェイクアップしません。
shutdown()
はソケットでのみ機能することに注意してください。他の種類の記述子の場合は、select + pipe-to-selfまたはキャンセルのアプローチが必要になる可能性があります。
accept()でブロックされたスレッドをキャンセルするpthread_cancelは、pthread実装がキャンセルを適切に実装しない場合、つまり、スレッドがコードに戻る直前にソケットを作成した場合、pthread_cancel()が呼び出され、スレッドはキャンセルされ、新しく作成されたソケットがリークされます。 FreeBSD 9.0以降にはこのような競合状態の問題はありませんが、最初にOSを確認する必要があります。
リスニングソケットを閉じて、acceptはエラーを返します。
これで確実に機能しないものは何ですか?あなたが直面している問題を説明してください。