一个C语言程序,包括2个线程。线程1是内部由一个死循环,死循环的每次循环间隔10秒中,循环体中是一次业务动作,一次业务动作可能持续几秒中,也可能持续几分钟,或者更长时间。线程2是守护线程,为了检查线程1的一次业务动作会不会执行时间超长,比如如果线程1的某次业务动作超过1小时,则线程2将杀死线程1,并重新执行线程1。

创建一个涉及两个线程的C语言程序,其中一个线程负责执行周期性任务,另一个线程作为守护线程监控任务执行时间并在必要时重启任务线程,是一个典型的多线程同步和线程管理问题。接下来,我将逐步讲解和展示如何实现这个程序。

步骤 1: 包含必要的头文件

首先,我们需要包含一些头文件,以支持多线程编程和时间管理:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>

步骤 2: 定义共享变量

我们需要一些变量来记录线程1的状态和时间信息:

pthread_t tid1, tid2;  // 线程标识符
int task_running = 0;  // 标记任务是否正在运行
time_t task_start_time; // 任务开始时间

步骤 3: 编写业务动作函数

线程1将运行此函数,执行具体的业务动作。这里用随机睡眠时间来模拟业务动作的持续时间。

void *task_function(void *arg) {
    while (1) {
        task_running = 1;
        task_start_time = time(NULL);
        printf("Task starts at %ld\n", task_start_time);

        // 模拟业务动作,睡眠随机时间
        int sleep_time = rand() % 600; // 模拟的任务持续时间在0到10分钟之间
        sleep(sleep_time);

        task_running = 0;
        printf("Task completed, sleep_time was %d seconds.\n", sleep_time);

        // 等待10秒再次执行
        sleep(10);
    }
}

步骤 4: 守护线程函数

守护线程将检查任务执行是否超时,如果超时,它将重启任务线程:

void *watchdog_function(void *arg) {
    while (1) {
        if (task_running) {
            time_t current_time = time(NULL);
            double elapsed_time = difftime(current_time, task_start_time);
            if (elapsed_time > 3600) {  // 1小时超时
                printf("Task is running too long. Terminating and restarting.\n");
                pthread_cancel(tid1);  // 终止线程1
                pthread_join(tid1, NULL);  // 确保线程1已完全终止
                pthread_create(&tid1, NULL, task_function, NULL);  // 重启线程1
            }
        }
        sleep(1);  // 每秒检查一次
    }
}

步骤 5: 主函数

在主函数中,我们初始化线程并开始执行:

int main() {
    srand(time(NULL));  // 初始化随机数生成器

    // 创建线程1和线程2
    pthread_create(&tid1, NULL, task_function, NULL);
    pthread_create(&tid2, NULL, watchdog_function, NULL);

    // 等待线程1和线程2
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}

以上代码完整地描述了如何用C语言和POSIX线程库来实现一个具有守护线程监控功能的多线程程序。

在多线程编程中,对共享资源的访问确实需要小心处理,以避免竞态条件和数据不一致的问题。在您的例子中,task_start_time 被两个线程访问:一个线程(线程1)设置它,另一个线程(守护线程)读取它。是否需要用锁来保护这个变量,取决于变量的读写方式和可能的并发冲突。

在许多操作系统和硬件架构中,对基本数据类型(如 intlong 等)的单一写操作和单一读操作是原子的。这意味着,在一个线程中写入这些类型的单个数据值,在另一个线程中读取同一数据值通常不会导致数据损坏或半写状态。time_t 通常是一个长整型(long),因此对 time_t 类型的单一赋值和单一读取在许多情况下是原子的。

然而,即便基本的读写操作是原子的,使用锁或其他同步机制仍然是一个好的做法,特别是在以下情况:

  1. 代码的可移植性:不是所有平台上对基本数据类型的读写都是原子的。使用锁可以提高代码在不同平台上的可靠性和稳定性。
  2. 代码的复杂性增加:随着程序逻辑的复杂性增加,可能需要引入更多的共享变量或更复杂的操作,此时确保线程安全就变得更加重要。

如果决定使用锁来同步对 task_start_time 的访问,可以使用互斥锁(mutex)。以下是如何在您的程序中引入互斥锁来保护 task_start_time

添加互斥锁

首先,在全局区域定义一个互斥锁:

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

修改业务动作函数

使用互斥锁来保护写操作:

void *task_function(void *arg) {
    while (1) {
        pthread_mutex_lock(&lock);
        task_start_time = time(NULL);
        task_running = 1;
        pthread_mutex_unlock(&lock);

        printf("Task starts at %ld\n", task_start_time);

        int sleep_time = rand() % 600; // 模拟的任务持续时间在0到10分钟之间
        sleep(sleep_time);

        pthread_mutex_lock(&lock);
        task_running = 0;
        pthread_mutex_unlock(&lock);

        printf("Task completed, sleep_time was %d seconds.\n", sleep_time);
        sleep(10);
    }
}

修改守护线程函数

使用互斥锁来保护读操作:

void *watchdog_function(void *arg) {
    while (1) {
        pthread_mutex_lock(&lock);
        int running = task_running;
        time_t start_time = task_start_time;
        pthread_mutex_unlock(&lock);

        if (running) {
            time_t current_time = time(NULL);
            double elapsed_time = difftime(current_time, start_time);
            if (elapsed_time > 3600) {
                printf("Task is running too long. Terminating and restarting.\n");
                pthread_cancel(tid1);
                pthread_join(tid1, NULL);
                pthread_create(&tid1, NULL, task_function, NULL);
            }
        }
        sleep(1);
    }
}

使用互斥锁可以确保即使在多核处理器上运行时,对共享资源的访问也是安全的。这样的同步策略会稍微降低性能,但可以显著提高程序的稳定性和可靠性。