处理两个线程,一个正在休眠,另一个等待输入。

3

问题: 创建一个程序,接收用户输入但在一定时间后超时(暂定为2秒)。

方法: 我创建了两个线程,一个等待用户输入(inputThread,使用tid[0]),另一个休眠2秒钟(sleepThread,使用tid[1])。我通过另一个线程的例程来取消一个线程,如下所示:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_t tid[2];

void* inputThread()
{   
    int n;
    printf("Enter number:");

    // wait for input; if input is given then go ahead and cancel the sleeper thread.
    scanf("%d",&n); 

    // cancel the sleeper thread
    pthread_cancel(tid[1]); 

    printf("Got value:%d\n",n);
}

void* sleepThread()
{
    // sleep for 2 seconds and cancel the input thread.
    sleep(2); 

    // cancel the input thread
    pthread_cancel(tid[0]); 

    printf("\nNo value entered!\n");
}

int main(int argc, char const *argv[])
{
    int r1,r2,r3,r4;

    // input taking thread 
    r1 = pthread_create(&tid[0],NULL,inputThread,NULL); 

    // sleeping thread
    r2 = pthread_create(&tid[1],NULL,sleepThread,NULL);

    r3 = pthread_join(tid[0],NULL);
    r4 = pthread_join(tid[1],NULL);

    return 0;
}

目前为止,该程序按预期运行。但我的朋友说它不保证能够工作,因为它取决于线程如何调度。他试图向我解释同样的问题,但我无法理解。他还说pthread_cancel仅是取消线程的请求,可能不会成功。
因此,有人能指出潜在的错误和避免相同情况的最佳实践吗?如果需要更改以确保程序的工作,也将不胜感激。
3个回答

3
你的朋友说得没错,pthread_cancel 是请求取消线程并不总是成功的。
关于调度部分,你需要提供他有哪些参数。
为了解决你的问题,你可以完全放弃使用线程(除非这是你的任务要求),并使用 selectread
这将使解决方案不可移植(只符合 POSIX 标准),但 pthreads 也不可移植。 select 允许你等待文件描述符(在本例中为 0)可用于读取数据。它还允许你设置超时。
fd_set set;
FD_ZERO(&set);
FD_SET(0, &set);

struct timeval timeout;
timeout.tv_sec = 2;
timeout.tv_usec = 0;

int ret = select(1, &set, NULL, NULL, &timeout);

if (ret == 1) {
    char buffer[20];
    int n;
    read(0, buffer, 20);
    sscanf(buffer, "%d", &n);
    printf("Got value:%d\n",n);
} else {
    printf("Time out\n");
}

谢谢你提供的解决方案。使用线程不是必需的,但我们正在使用线程来学习线程库。如果只使用线程,我该如何处理?是否可能仅在一个线程中完成? - Rahul Bharadwaj
@RahulBharadwaj 我提供的解决方案是使用单个线程。如果您想使用多个线程来完成,正确的方法是拥有一个休眠线程(就像您现在拥有的一样)。超时后,它将设置一个volatile标志为0。主线程将stdin(0)fd设置为非阻塞模式,并继续尝试读取(适当的睡眠时间为10毫秒)。每次重复之前都要检查易失性变量。这样您就可以获得相同的行为。 - Ajay Brahmakshatriya
好的,我明白了。我会尝试使用除了“主”线程之外的一个额外线程来实现。 - Rahul Bharadwaj
1
@RahulBharadwaj 你可以查看这个链接来了解如何将0 fd设置为非阻塞模式。虽然我建议先使用 dup 复制fd,然后在其上进行读取。如果将0 fd 设置为非阻塞模式,我不确定标准库代码(例如scanf、gets)会如何行事。 - Ajay Brahmakshatriya
如果用户不按回车键,所示代码将不会超时。 - alk
@alk,我在我的系统上尝试了它并且成功了。因为终端通常使用行缓冲的方式对待stdin,所以只有在按下Enter键后,fd 0 才能进行读取。如果我说错了,请纠正我。 - Ajay Brahmakshatriya

1
你的朋友是正确的,我认为他的意思是你不能假定哪个线程先运行/完成。 但在两秒内,两个线程肯定都会被调度,所以线程1有时间向用户请求输入并等待一段时间等待输入。
我没有看到大问题,如果没有问题,那就不用太担心。
而不是使用sleep,你可以使用SIGALRM信号,但它涉及掩码,需要更多时间来理解 (我正在做this project: 检查task2函数和它使用的掩码,如果你想要一个例子)。

我已经使用了 sigprocmasksignal。从来没有想过,将尝试使用它们来实现,谢谢! - Rahul Bharadwaj

0

pthread_cancel 函数在线程上调用线程取消处理程序,一旦它们返回,就会调用线程的析构函数,进而终止线程。

由于它是一个请求,可能会失败,这通过 pthread_cancel 返回非零整数来表示。

如果您使用超过两个线程来执行此操作,或者更多线程正在访问同一进程中的标准输入流,则可能会出现线程等待访问标准输入流,导致所需行为被延迟。


只有一个线程正在访问 stdin。你有什么替代 pthread_cancel 的建议吗? - Rahul Bharadwaj
@RahulBharadwaj 是的,你可以使用pthread_exit,它的返回类型是void,且永远不会失败。 - Josh Weinstein
1
pthread_exit函数用于终止调用线程。因此,它必须从等待线程中调用,而这是不可能的。没有可靠的方法可以从另一个线程中终止一个线程。 - Ajay Brahmakshatriya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接