为什么我的程序大部分时间都在睡眠中,但要花这么多 CPU 时间?

Why my program takes so much CPU time though most of the time in sleep?

我的程序需要一些计时器,所以我决定用 pthreads 来编写它。 我的计时器需要每 update_interval 个滴答声通过更新回调更新一些信息。

我是这样做的:

timer.h:

#include <pthread.h>

enum timer_messages
{
    TIMER_START,
    TIMER_STOP,
    TIMER_PAUSE,
    TIMER_EXIT
};

typedef void (*callback)(void *);

struct timer
{
    pthread_t thread_id;

    struct timeval *interval;
    struct timeval *update_interval;
    struct timeval *start;

    int ls;
    int wr;

    int enabled;
    int exit;

    callback update;
    callback on_time;
};

struct timer *my_timer_create();
void timer_destroy(struct timer *t);
void timer_set_update_interval(struct timer *t, int seconds, int microseconds);
void timer_set_interval(struct timer *t, int seconds, int microseconds);
void timer_set_update_func(struct timer *t, callback update);
void timer_set_ontime_func(struct timer *t, callback on_time);

void timer_stop(struct timer *t);
void timer_start(struct timer *t);
void timer_exit(struct timer *t);
void timer_pause(struct timer *t);

timer.c:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string.h>

#include "timer.h"

#define TIMEVAL_TO_MICROSECONDS(tv) ((long long)((tv).tv_sec * 1000000 + (tv).tv_usec))
#define GET_TIME_PASSED(start, now) ((TIMEVAL_TO_MICROSECONDS(now) - TIMEVAL_TO_MICROSECONDS(start)))

static int passed(struct timeval *start, struct timeval *interval);
static void fill_timeval(struct timeval *tv, int sec, int microsec);
static void timer_count(struct timer *t);
static void timer_message(struct timer *t);
static void *main_func(void *data);
static void timer_send_msg(struct timer *t, enum timer_messages message);

static struct timeval DEFAULT_TIMEOUT = { 0, 500000 };


static int passed(struct timeval *start, struct timeval *interval)
{
    struct timeval cur, sub;
    int check;

    check = gettimeofday(&cur, NULL);
    if(-1 == check)
    {
        perror("gettimeofday");
        return 0;
    }

    if(GET_TIME_PASSED(*start, cur) < TIMEVAL_TO_MICROSECONDS(*interval))
        return 0;

    return 1;
}

static void fill_timeval(struct timeval *tv, int sec, int microsec)
{
    tv->tv_sec = sec;
    tv->tv_usec = microsec;
}

static void timer_count(struct timer *t)
{
    int check;
    fd_set readfds;
    struct timeval timeout;

    check = gettimeofday(t->start, NULL);

    while(1)
    {
        if(!t->enabled)
            return;

        FD_ZERO(&readfds);
        FD_SET(t->ls, &readfds);

        if(t->update_interval)
            memcpy(&timeout, t->update_interval, sizeof(*(t->update_interval)));
        else
            memcpy(&timeout, &DEFAULT_TIMEOUT, sizeof(DEFAULT_TIMEOUT));

        check = select(t->ls + 1, &readfds, NULL, NULL, &timeout);

        if(-1 == check)
        {
            perror("select");
            return;
        }

        if(FD_ISSET(t->ls, &readfds))
            timer_message(t);
        else
            if(t->update)
                t->update(t);

        if(passed(t->start, t->interval))
        {
            t->on_time(t);
            break;
        }
    }
}

static void timer_message(struct timer *t)
{
    int read_bytes;
    char message;

    read_bytes = read(t->ls, &message, sizeof(message));
    if(-1 == read_bytes)
    {
        perror("timer_message read");
        return;
    }

    switch(message)
    {
        case TIMER_START: t->enabled = 1; break;
        case TIMER_STOP: t->enabled = 0; t->interval = NULL; t->start = NULL; break;
        case TIMER_EXIT: t->enabled = 0; t->exit = 1; break;
        case TIMER_PAUSE: break;
        default: break;
    }
}

static void *main_func(void *data)
{
    struct timer *t = data;
    fd_set readfds;
    int check;

    while(!t->exit)
    {
        if(t->enabled)
        {
            timer_count(t);
        }
        else
        {
            FD_ZERO(&readfds);
            FD_SET(t->ls, &readfds);

            check = select(t->ls + 1, &readfds, NULL, NULL, NULL);
            if(-1 == check)
            {
                perror("select");
                return NULL;
            }

            if(FD_ISSET(t->ls, &readfds))
                timer_message(t);
        }
    }

    return NULL;
}

static void timer_send_msg(struct timer *t, enum timer_messages message)
{
    int check;
    char msg;

    msg = message;

    check = write(t->wr, &msg, sizeof(msg));
    if(-1 == check)
    {
        perror("timer_send_msg write");
    }
}

struct timer *my_timer_create()
{
    int check;
    struct timer *t;
    int fd[2];

    t = malloc(sizeof(*t));
    t->interval = malloc(sizeof(*(t->interval)));
    t->update_interval = malloc(sizeof(*(t->update_interval)));
    t->start = malloc(sizeof(*(t->start)));

    check = pipe(fd);
    if(-1 == check)
    {
        perror("pipe");
        return NULL;
    }

    t->ls = fd[0];
    t->wr = fd[1];

    t->enabled = 0;
    t->exit = 0;

    t->update = NULL;
    t->on_time = NULL;

    check = pthread_create(&(t->thread_id), NULL, main_func, t);
    if(-1 == check)
    {
        perror("pthread_create");
        return NULL;
    }

    return t;
}

void timer_destroy(struct timer *t)
{
    free(t->interval);
    free(t->update_interval);
    free(t->start);
    close(t->ls);
    close(t->wr);
    free(t);
}

void timer_set_update_interval(struct timer *t, int seconds, int microseconds)
{
    fill_timeval(t->update_interval, seconds, microseconds);
}

void timer_set_interval(struct timer *t, int seconds, int microseconds)
{
    fill_timeval(t->interval, seconds, microseconds);
}

void timer_set_update_func(struct timer *t, callback update)
{
    t->update = update;
}

void timer_set_ontime_func(struct timer *t, callback on_time)
{
    t->on_time = on_time;
}

void timer_stop(struct timer *t)
{
    timer_send_msg(t, TIMER_STOP);
}

void timer_start(struct timer *t)
{
    timer_send_msg(t, TIMER_START);
}

void timer_exit(struct timer *t)
{
    timer_send_msg(t, TIMER_EXIT);
}

void timer_pause(struct timer *t)
{
    timer_send_msg(t, TIMER_PAUSE);
}

然后在主文件中这样调用它:

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <sys/time.h>

#include "../timer.h"
#define BUF_SIZE 4096
#define TIMEVAL_TO_MICROSECONDS(tv) ((long long)((tv).tv_sec * 1000000 + (tv).tv_usec))
#define GET_TIME_PASSED(start, now) ((TIMEVAL_TO_MICROSECONDS(now) - TIMEVAL_TO_MICROSECONDS(start)))

void progress_bar(int percent, int bar_len)
{
    char buf[BUF_SIZE];
    int inside = bar_len - 2;
    int filled = inside * percent / 100;
    int not_filled = inside - filled;

    assert(percent <= 100);
    assert(bar_len < BUF_SIZE);

    buf[0] = '[';
    memset(buf + 1, '#', filled);
    memset(buf + 1 + filled, '-', not_filled);
    buf[bar_len - 1] = ']';
    buf[bar_len] = 0;

    printf("\r%s %d%%", buf, percent);
    fflush(stdout);
}

void timer_ontime(void *data)
{
    struct timer *t = data;
    puts("");
    puts("That's all folks!");
    timer_exit(t);
}

void timer_update(void *data)
{
    struct timer *t = data;
    struct timeval now;
    long long passed;
    int percent;
    gettimeofday(&now, NULL);
    passed = GET_TIME_PASSED(*(t->start), now);
    percent = passed * 100 / (t->interval->tv_sec * 1000000);
    progress_bar(percent, 50);
}

int main(int argc, char **argv)
{
    struct timer *t;
    int seconds;
    int check;

    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s <seconds>\n", argv[0]);
        return 1;
    }

    check = sscanf(argv[1], "%d", &seconds);
    if(check != 1)
    {
        fprintf(stderr, "Couldn't parse number of seconds\n");
        return 1;
    }

    t = my_timer_create();
    if(t == NULL)
    {
        fprintf(stderr, "Couldn't create timer\n");
        return 1;
    }

    timer_set_interval(t, seconds, 0);
    timer_set_ontime_func(t, timer_ontime);
    timer_set_update_func(t, timer_update);
    timer_start(t);
    printf("Started timer(%d seconds)\n", seconds);
    pthread_join(t->thread_id, NULL);
}

然后我 运行 它与:

[udalny@bulba test]$ time ./timer_check 3
Started timer(3 seconds)
[###############################################-] 99%
That's all folks!
./timer_check 3  0.48s user 1.22s system 56% cpu 3.002 total

如您所见,它需要 56% CPU 的时间。为什么这么多? 它每秒只更新两次(DEFAULT_CALLBACK 是 500000 微秒)。和所有 其他时间它在睡觉。

我怎样才能改变它以减少花费?

此外,如果您对代码有任何提示,我将不胜感激。

您的程序大部分时间都花在 timer_count 上,忙着循环 - 如果您在 select:

之前添加一个简单的 printf
printf("?\n");
check = select(t->ls + 1, &readfds, NULL, NULL, &timeout);

和 运行 ./timer_check 3 | wc -l 你应该得到 百万 行 - 这意味着此循环上的 CPU 硬循环。这是因为您初始化 timeout:

的方式
if(t->update_interval)
    memcpy(&timeout, t->update_interval, sizeof(*(t->update_interval)));

这实际上将您的 timeout 设置为零 - 因为您从未在 main 中初始化您的 t->update_interval。这有效地将您的循环变成了一个繁忙的循环。


将以下行添加到您的主函数以解决此问题:

timer_set_update_interval(t, seconds, 0);

之后你会得到你想要的行为:

Started timer(3 seconds)
[################################################] 100%
That's all folks!
0.00user 0.00system 0:03.00elapsed 0%CPU (0avgtext+0avgdata 1932maxresident)k
0inputs+0outputs (0major+77minor)pagefaults 0swaps