LinuxでSuspendThread的なAPIが無かったのでmutexで無理やりスレッドをsuspend/resumeしてみる。
とりあえず実装2回やってるけどOSのAPI実装についての知識がなさすぎて指摘2回食らっていてヤバなので、3度目の正直となるのか2度あることは3度あるのか。
これまでに貰った指摘事項概要
@drab さんからの指摘事項
1.「pthread_cond_wait」とか「fifoかメッセージキュー」使えばいいじゃん
2.キー入力イベントに対して何かしらの処理を行うコードでpthread_cond_wait使うのはアウトの可能性が高い
@angel_p_57 さんからの指摘事項
1.絶対mutexを他のスレッドから解除するのはダメ。
2.pthread_cond_* の使い方に誤解がある(こういうイベントを待機するだけのために使うのはNG!)
これら指摘を踏まえて、そもそもやりたかったこと。
メッセージ処理とかそういうのを実装する時、
キューに入れてからキューに入っているメッセージを連続処理(要はNW側に送信など非同期処理)したい時。
メッセージがない時無駄にループさせてるのリソースもったいないからスレッドを特定箇所で一時停止させたい。
要はイベント待ちをしたい。
指摘事項とやりたいことを見比べてみる
指摘:キー入力イベントに対して何かしらの処理を行うコードでpthread_cond_wait使うのはアウトの可能性が高い
やりたいこと:イベント待ちをしたい。
アウトやんけ。
そんなときにMutexを使って一時停止させるメモ。
2017/04/17 指摘事項に従って、正しい実装方法で書きました。(記事下部参照)
2017/04/21 再度修正
最初に書いてた間違いだらけのウンコード
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <stdarg.h>
#include<stdint.h>
#include<assert.h>
// コンソールに文字列を出す
int consoleWrite(const char *fmt, ...)
{
va_list list;
va_start(list, fmt);
int writed = vprintf(fmt, list);
fflush(stdout);
va_end(list);
return writed;
}
// キーボードから文字列を受け付ける
size_t input(char *buf, size_t bufLen)
{
#define GabageBufSize 0x100
uint8_t bin[GabageBufSize];
char *savePtr = NULL;
char *token = NULL;
static_assert(sizeof(bin) == GabageBufSize, "Gabage buffer size not 255");
fgets(buf, bufLen, stdin);
token = strchr(buf, '\n');
if (token != NULL)
{
*token = '\0';
}
else
{
buf[bufLen - 1] = '\0';
while (token == NULL)
{
fgets(bin, GabageBufSize, stdin);
token = strchr(bin, '\n');
if (token != NULL)
{
break;
}
}
}
return strlen(buf);
}
// これダメな実装
void suspend(pthread_mutex_t* lock)
{
pthread_mutex_trylock(lock);
// 1回もロックされてないときはここでロックされる
pthread_mutex_lock(lock);
pthread_mutex_unlock(lock);
}
void *messageSender(void *lock)
{
int count = 1;
while (true)
{
consoleWrite(".");
sleep(1);
if ((count % 3) == 0)
{
//ここで、イベント発生まで待つ。
suspend((pthread_mutex_t*)lock);
}
count++;
}
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_mutex_t lock;
char buf[32];
pthread_mutex_init(&lock, NULL);
pthread_create(&tid, NULL, messageSender, &lock);
while (true)
{
input(buf, 32);
pthread_mutex_unlock(&lock); // 他のスレッドからunlockの動作は保証されない。
consoleWrite("unlocked.\n");
}
pthread_join(tid, NULL);
}
キューにデータが入ってきたら、コールバック使ってpthread_mutex_unlock叩いて
Mutexオブジェクトをアンロックするとスレッドが再開される。
上記デモでは、そのキューにデータが入る部分をキーボードからの入力に置き換えている。
パフォーマンス面とか考えずにとりあえずできればいいやで実装してしまったので、
これ以外に他にいい案があったらぜひ。
pthread_cond_wait使った例を後日別の投稿で書きます。
ここからさらに間違った実装
参考:Section: Man page C Library Functions (3) PTHREAD_COND
- pthread_cond_wait は cond(= condition:条件変数)とmutexを引数に取る
- pthread_cond_wait に渡すmutexオブジェクトはロック済みである必要がある
- pthread_cond_wait で止めたスレッドはpthread_cond_signalで再開
// 修正差分のみ
typedef struct Suspend
{
pthread_mutex_t lockMutex;
pthread_cond_t lockCond;
} Suspend;
void suspend(Suspend *lock)
{
// 使い方が間違ってる
pthread_mutex_trylock(&lock->lockMutex);
pthread_cond_wait(&lock->lockCond,&lock->lockMutex);
pthread_mutex_unlock(&lock->lockMutex);
}
void SuspendInit(Suspend *lock)
{
pthread_cond_init(&lock->lockCond, NULL);
pthread_mutex_init(&lock->lockMutex, NULL);
}
int main(int argc, char *argv[])
{
pthread_t tid;
Suspend lock;
char buf[32];
SuspendInit(&lock);
pthread_create(&tid, NULL, messageSender, &lock);
while (true)
{
input(buf, 32);
pthread_cond_signal(&lock.lockCond);
consoleWrite("unlocked.\n");
}
pthread_join(tid, NULL);
}
まずは指摘された点については直せた気がする。
直せた気がする←気のせいだった。パイプで再実装(名前をSuspendなんちゃらからEventInfoなんちゃらに変更)
typedef struct EventInfo
{
int state;
int read;
int write;
pthread_mutex_t mutex;
} EventInfo;
#define EventInfo_WAIT_BUSY 0
#define EventInfo_WAIT_READY 1
// イベント発生まで待つ
void EventInfoWait(EventInfo *lock)
{
uint8_t msg;
pthread_mutex_lock(&lock->mutex);
lock->state = EventInfo_WAIT_READY;
pthread_mutex_unlock(&lock->mutex);
int r = read(lock->read, &msg, sizeof(uint8_t));
}
// 何もしない
void EventInfoRaisingEvent_None(EventInfo *lock)
{
}
// 通知だけする。
void EventInfoRaisingEvent_Send(EventInfo *lock)
{
static uint8_t msg = 0xdeadbeef;
write(lock->write, &msg, sizeof(uint8_t));
}
// イベントを発生させる
void EventInfoRaisingEvent(EventInfo *lock)
{
static void (*EventInfoWakeupSendMessage[2])(EventInfo * lock) =
{
EventInfoWakeupSendMessage_None,
EventInfoWakeupSendMessage_Send};
pthread_mutex_lock(&lock->mutex);
EventInfoWakeupSendMessage[lock->state](lock);
lock->state = EventInfo_WAIT_BUSY;
pthread_mutex_unlock(&lock->mutex);
}
void *messageSender(void *lock)
{
int count = 1;
while (true)
{
consoleWrite(".");
sleep(1);
if ((count % 3) == 0)
{
//ここで、イベント発生まで待つ。
EventInfoWait((EventInfo *)lock);
}
count++;
}
}
int EventInfoInit(EventInfo *lock)
{
int pfd[2];
int r = pipe(pfd);
if (r != 0)
{
return -1;
}
lock->state = EventInfo_WAIT_BUSY;
fcntl(pfd[1], F_SETFL, O_NONBLOCK);
lock->read = pfd[0];
lock->write = pfd[1];
pthread_mutex_init(&lock->mutex, NULL);
return 0;
}
int main(int argc, char *argv[])
{
pthread_t tid;
EventInfo lock;
char buf[32];
EventInfoInit(&lock);
pthread_create(&tid, NULL, messageSender, &lock);
while (true)
{
input(buf, 32);
EventInfoRaisingEvent(&lock);
consoleWrite("unlocked.\n");
}
pthread_join(tid, NULL);
return 0;
}
修正、多分これで当初の目的は達成できるので大丈夫なはず。