使用多个线程在C中实现KeyPress事件

时间:2017-01-30 03:35:59

标签: c multithreading ncurses termios conio

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

我已尝试以下方法来实现此目的:

使用conio.h和getch(),但这已经过时了,不再使用gcc了。资料来源:Why can't I find <conio.h> on Linux?

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

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

int main:

      //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()语句显示执行结束时忽略它所说的内容): enter image description here

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

1 个答案:

答案 0 :(得分:2)

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

要对此进行测试,请将文件另存为dispatch.c

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

示例输出:

  

$ ./dispatch
  关键&#39; a&#39;按下...
  ......线程T3拉出关键&#39; a&#39;从队列中   ......线程T1拉出键&#39; a&#39;从队列中   ......线程T2拉出键&#39; a&#39;从队列中   关键&#39; b&#39;按下...
  ......线程T2拔出键&#39; b&#39;从队列中   ......线程T1拔出键&#39; b&#39;从队列中   关键&#39; c&#39;按下...
  ......线程T3拉出关键&#39; c&#39;从队列中   ......线程T1拔出键&#39; c&#39;从队列中   关键&#39; d&#39;按下...
  ......线程T2拔出键&#39; d&#39;从队列中   ......线程T3拉出关键&#39; d&#39;从队列中   关键&#39; z&#39;按下...
  ...线程T2拉出键&#39; z&#39;从队列中   ......线程T1拉出键&#39; z&#39;从队列中   ......线程T3拉出关键&#39; z&#39;从队列中   功能

#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,每个线程都有一个&#39;密钥队列&#39;它们上面的结构,以及char *字段keyskeys是一个字符串,其中包含线程对&#39;感兴趣的字符(键)。

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

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

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

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

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

运行它并看到它的实际效果很有趣。

*注意:使用calloc()代替malloc(),因为与malloc()不同,calloc()初始化返回到所有0的内存。这是一个很好的技巧。