Run Infinite Loop Without Frying the CPU in C

Nov 25, 2024  │  m. Dec 6, 2024 by khatibomar  │  #c   #concurrency   #threading  
Disclaimer: Views expressed in this software engineering blog are personal and do not represent my employer. Readers are encouraged to verify information independently.

Introduction

In this article, I will show you how to run an infinite loop without frying the CPU in C.
In Go it’s easy to achieve this by using channels and goroutines.
I am new to C and I wanted to see how I can achieve the same thing in C.
So if you are an experienced C developer, please let me know if there is a better way to do this.

First download the source code from here demo.zip

The Problem

let’s say we have the following scenario, where I need to wait for someone to push a work before I can process it.
the trivial method is to create an infinite loop and check if we can process the work.

void Process() {
  for (;;) {
    while(atomic_load(&pushed_count) == 0) {
      printf("nothing to process\n");
    }

    printf("Processor: Doing some work...\n");
    atomic_fetch_add(&processed_count, 1);
    atomic_fetch_sub(&pushed_count, 1);
  }
}

that is my processing function, it will keep checking if there is work to process, if there is it will process it.
the problem with this code is that it will keep the CPU busy and will consume a lot of power.
to verify that, let’s run the code and see how much CPU it consumes, we can use any tool like htop or top to see the CPU usage.

./build.sh 
./demo1

to monitor CPU usage I will run in a split

top -p $(pgrep demo1)
image 1

in the image we can see that CPU usage is 99.7% and the process is consuming a lot of power.

The Solution

Of course, we can use a sleep function to give the CPU a break, but this is not a good solution because we don’t know how long we should sleep.
So we need a way to signal the processor to process the work.

The solution is to use pthread_cond_wait to wait for a signal to process the work.
pthread_cond_wait will block the thread until a signal is received.

void Process() {
  for (;;) {
    printf("Processor: Waiting for a signal...\n");
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);
    printf("Processor: Signal received\n");

    printf("Processor: Doing some work...\n");
    atomic_fetch_add(&processed_count, 1);
    atomic_fetch_sub(&pushed_count, 1);
  }
}

here the process will wait for a signal to process the work, and it will not consume a lot of power.
in the pusher code we need to send a signal to the processor to process the work.

printf("Thread %ld is pushing...\n", thread_index);
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
atomic_fetch_add(&pushed_count, 1);

to verify that, let’s run the code and see how much CPU it consumes.

./demo2

and in a split terminal

top -p $(pgrep demo2)

and we can see that the CPU usage is 0.0% and the process is not consuming a lot of power.

image 2

Conclusion

As I said earlier, I am new to C and I wanted to see how I can achieve the same thing in C.
So if you are an experienced C developer, please let me know if there is a better way to do this.
It’s always good to learn new things and see how things work under the hood.
This can be very useful in many scenarios where you need to give the CPU a break.
Also there are many improvements to do in this code of course and use memory barriers to make sure that the data is consistent across threads.