我在评论中提到的多线程方法,它有一个单独的线程来获取和排队键,被设计为不丢弃键,这不是微不足道的。它需要一些 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 *
字段keys
。keys
是一个字符串,包含线程“感兴趣”的字符(键)。
字符串中列出的键盘键在线程字符串中重复,因此在某些情况下,一个键可以被多个线程使用。例如,所有线程都听'a'和'z',两个线程听'b',另外两个听'c',另一对线程对'd'感兴趣,最后是'e','f ', 和 'g' 分别只有一个线程监听。
主循环读取没有回显的键并立即捕获键(例如,用户不必按回车键)。当输入一个键时,它会循环遍历线程信息以找出哪些线程对按下的键感兴趣,并将键(在数据包中)排入各个线程。
线程在它们自己的循环中,在循环之间休眠一秒钟。当他们醒来时,他们检查他们的队列以查看是否有任何键排队。如果有,他们将其从队列中拉出并说他们从队列中拉出该密钥。
由于每个线程的轮询/工作循环中的延迟(例如,在线程唤醒并检查它们各自的队列之前),您有时间在键盘上输入多个内容以排队到线程,然后线程将出列他们以 1 秒的间隔一次一个的入队键。
在现实生活中,该程序将使用更短的睡眠时间,但会在其中放置一些东西以防止每个线程不必要地占用大量 CPU 时间。
运行它并查看它的运行情况很有趣。
*注:calloc()
用于代替,malloc()
因为不一样malloc()
,calloc()
初始化返回全0的内存。这是一个不错的技巧。