c - 用 C 语言实现多线程按键事件

标签 c multithreading ncurses termios conio

我的目标:线程将等待(忙循环而不是 sleep ),直到按下特定的键(比方说 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 主:

      //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

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

最佳答案

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

要测试这一点,请将文件另存为,比方说,dispatch.c

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

示例输出:

$ ./dispatch
Key 'a' pressed...
... Thread T3 pulled key 'a' from queue
... Thread T1 pulled key 'a' from queue
... Thread T2 pulled key 'a' from queue
Key 'b' pressed...
... Thread T2 pulled key 'b' from queue
... Thread T1 pulled key 'b' from queue
Key 'c' pressed...
... Thread T3 pulled key 'c' from queue
... Thread T1 pulled key 'c' from queue
Key 'd' pressed...
... Thread T2 pulled key 'd' from queue
... Thread T3 pulled key 'd' from queue
Key 'z' pressed...
... Thread T2 pulled key 'z' from queue
... Thread T1 pulled key 'z' from queue
... Thread T3 pulled key 'z' from queue

#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' 分别只有一个线程在监听。

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

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

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

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

运行它并查看它的实际效果非常有趣。

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

关于c - 用 C 语言实现多线程按键事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41928673/

相关文章:

c++ - c程序内存布局,关于局部变量和全局变量的位置

c - 如何针对细微错误设计软件测试

java - 如何从 cassandraexecuteAsync() 方法调用方法抛出异常

java - 有可能发现操作系统在线程上进行上下文切换吗?

诅咒向后移动以解释之前的行

c - fftw 与 complex.h 在 c 中的接口(interface)

python - asyncore 回调启动线程...可以吗?

c++ - Qt 宏关键字导致名称冲突

ncurses- KEY_ENTER 失败

c++ - 调用 AddUser 时持续出现 87 错误