2

我的目标:线程将等待(忙循环而不是睡眠),直到按下特定键(比如说 0)。每个线程都有一个不同的键,它将触发该线程退出等待并继续执行等待之后的命令。

我尝试了以下方法来实现这一点:

使用 conio.h 和 getch() 但这是旧的,不再适用于 gcc。来源:为什么我在 Linux 上找不到 <conio.h>?

使用 ncurses.h 和 getch() 但这会在等待键盘按下时停止执行。我使用的代码:http: //tldp.org/HOWTO/NCURSES-Programming-HOWTO/scanw.html#GETCHCLASS

我当前使用 termios.h 的实现:

主要内容:

      //Keypress Event Handler
   struct termios info;
   tcgetattr(0, &info);          /* get current terminal attirbutes; 0 is the file descriptor for stdin */
   info.c_lflag &= ~ICANON;      /* disable canonical mode */
   info.c_cc[VMIN] = 1;          /* wait until at least one keystroke available */
   info.c_cc[VTIME] = 0;         /* no timeout */
   tcsetattr(0, TCSANOW, &info); /* set immediately */

线程调用的内部函数(对不起缩进):

while(stop_wait != 1) 
      {
         //printf("%d\n", temp->currentID);
         ch = getchar();

         if(ch < 0) {
            if (ferror(stdin)) {
               clearerr(stdin);
            }
         }

         switch (ch)
         {
         case 48 :
            if(temp->event == 0) stop_wait = 1;
            break;
         case 49 :
            if(temp->event == 1) stop_wait = 1;
            break;
         case 50 :
            if(temp->event == 2) stop_wait = 1;
            break;
         case 51 :
            if(temp->event == 3) stop_wait = 1;
            break;
         case 52 :
            if(temp->event == 4) stop_wait = 1;
            break;
         }
      }

主线结束:

tcgetattr(0, &info);
info.c_lflag |= ICANON;
tcsetattr(0, TCSANOW, &info);

上面的代码与此处的代码非常相似:Implementing a KeyPress Event in C

但是,这不是我想要的正确方式。我有一个输入文件,指定哪些键将触发 stop_wait 更改为 1。线程 1 将通过按键盘上的 1 触发(在 ascii 中为 49),线程 2 将通过按键盘上的 2 触发(在 ascii 中为 50 )。当前实现的问题是,如果不触发 1,则 2 不会触发。如下所示(Main() 语句显示执行结束忽略它所说的内容): 在此处输入图像描述

我可以就这个问题获得任何建议/帮助吗?

4

1 回答 1

2

我在评论中提到的多线程方法,它有一个单独的线程来获取和排队键,被设计为不丢弃键,这不是微不足道的。它需要一些 C 技能和一些 UNIX 知识。我实现了一个可以运行的工作骨架,因此您可以看到其中涉及的内容。

为了测试这一点,将文件另存为,比如说,dispatch.c

$ cc -o dispatch dispatch.c
$ ./dispatch

样本输出:

$ ./dispatch
Key 'a' pressed...
... 线程 T3 从队列中拉出键 'a'
... 线程 T1 从队列中拉出键 'a'
... 线程 T2 从队列中拉出键 'a'
Key ' b'被按下...
...线程T2从队列中拉出键'b'
...线程T1从队列中拉出键'b'
键'c'被按下...
...线程T3从队列中拉出键'c'
... 线程 T1 从队列中拉出键 'c'
按下键 'd' ...
... 线程 T2 从队列中拉出键 'd'
... 线程 T3 从队列中拉出键 'd
' 按下键 'z'。 ..
... 线程 T2 从队列中拉出键 'z'
...线程 T1 从队列中拉出键“z”
...线程 T3 从队列中拉出键“z”

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <strings.h>
#include <string.h>
#include <termios.h>
#include <sys/types.h>

typedef struct keyQueue {
    struct keyQueue *next;
    char key;
} keyQueue_t;

typedef struct ThreadInfo {
    pthread_t tid;           /* thread id */
    pthread_mutex_t kqmutex; /* protects key queue from race condition between threads */
    keyQueue_t kqhead;       /* input keys queued to this thread */
    char *keys;              /* keys this thread responds to */
    char *name;              /* name of this thread */
} threadInfo_t;

static struct termios origtc, newtc;

threadInfo_t threads[] = { 
    { 0, PTHREAD_MUTEX_INITIALIZER, { NULL, '\0' }, "abcez", "Thread T1" },
    { 0, PTHREAD_MUTEX_INITIALIZER, { NULL, '\0' }, "abdfz", "Thread T2" },
    { 0, PTHREAD_MUTEX_INITIALIZER, { NULL, '\0' }, "acdgz", "Thread T3" }
};

void *service(void *arg) {
    char key;
    threadInfo_t *t = &threads[(int)arg];    // get pointer to thread
    for(;;) {
        pthread_mutex_lock(&t->kqmutex);     // lock other threads out while we tamper 
        key = '\0';                          // initialize key to NULL
        if (t->kqhead.next != NULL) {        // Anything queued up for us?
            keyQueue_t *kq = t->kqhead.next; // if so get ptr to key pkt
            key = kq->key;                   // fetch key from pkt
            t->kqhead.next = kq->next;       // Point to next key in queue (or NULL if no more queued up).
            free(kq);
        }  
        pthread_mutex_unlock(&t->kqmutex);   // unlock key queue
        if (key != '\0') {                   // if we got a key, log it
            printf("... %s pulled key '%c' from queue\n", t->name, key);
        }
        // ⇓ usleep() probably more practical as 1-sec too long for most cases
        sleep(1);                            // sleep so we don't loop too fast eating CPU
    }
    return NULL;
}

int main() {

    /* Fire up threads */
    for (long i = 0; i < sizeof (threads) / sizeof (threadInfo_t); i++) {
        if (pthread_create(&threads[i].tid, NULL, service, (void *)i) < 0) {
            perror("pthread_create()");
            exit(-1);
        }
    }

    tcgetattr(0, &origtc);                         // get orig tty settings
    newtc = origtc;                                // copy them
    newtc.c_lflag &= ~ICANON;                      // put in '1 key mode'
    newtc.c_lflag &= ~ECHO;                        // turn off echo

    for(;;) {
        tcsetattr(0, TCSANOW, &newtc);             // echo off 1-key read mode
        char c = getchar();                        // get single key immed.
        tcsetattr(0, TCSANOW, &origtc);            // settings back to normal
        printf("Key '%c' pressed...\n", c);        // show user what we got
        for (int i = 0; i < sizeof (threads) / sizeof (threadInfo_t); i++) {
            threadInfo_t *t = &threads[i];         // get shorthand ptr to thread
            if (strchr(t->keys, c) != NULL) {      // this thread listens for this key
                pthread_mutex_lock(&t->kqmutex);   // lock other threads out while we tamper 
                keyQueue_t *kq = calloc(sizeof (struct keyQueue), 1); // allocate pkt
                kq->key = c;                       // stash key there
                keyQueue_t *kptr = &t->kqhead;     // get pointer to queue head
                while(kptr->next != NULL)          // find first empty slot
                    kptr = kptr->next;
                kptr->next = kq;                   // enqueue key packet to thread
                pthread_mutex_unlock(&t->kqmutex); // unlock key queue
            }
        }
    }
}

这段代码启动了三个线程,t1、t2、t3,每个线程上都有一个“键队列”结构,以及一个char *字段keyskeys是一个字符串,包含线程“感兴趣”的字符(键)。

字符串中列出的键盘键在线程字符串中重复,因此在某些情况下,一个键可以被多个线程使用。例如,所有线程都听'a'和'z',两个线程听'b',另外两个听'c',另一对线程对'd'感兴趣,最后是'e','f ', 和 'g' 分别只有一个线程监听。

主循环读取没有回显的键并立即捕获键(例如,用户不必按回车键)。当输入一个键时,它会循环遍历线程信息以找出哪些线程对按下的键感兴趣,并将键(在数据包中)排入各个线程。

线程在它们自己的循环中,在循环之间休眠一秒钟。当他们醒来时,他们检查他们的队列以查看是否有任何键排队。如果有,他们将其从队列中拉出并说他们从队列中拉出该密钥。

由于每个线程的轮询/工作循环中的延迟(例如,在线程唤醒并检查它们各自的队列之前),您有时间在键盘上输入多个内容以排队到线程,然后线程将出列他们以 1 秒的间隔一次一个的入队键。

在现实生活中,该程序将使用更短的睡眠时间,但会在其中放置一些东西以防止每个线程不必要地占用大量 CPU 时间。

运行它并查看它的运行情况很有趣。

*注:calloc()用于代替,malloc()因为不一样malloc()calloc()初始化返回全0的内存。这是一个不错的技巧。

于 2017-01-30T10:09:19.537 回答