Test multi-thread program on one CPU

Today, I tested a multi-thread program on one CPU. The testbed is a FreeBSD virtual machine, and from lscpu command, it has indeed one CPU:

$ lscpu
Architecture:            aarch64
Byte Order:              Little Endian
Total CPU(s):            1
Model name:              Apple Unknown CPU r0p0 (midr: 610f0000)

The multi-thread program is simple too, just 4 threads add one global variable, and the correct result should be 400000 in every run. If the result is not 400000, exit the program:

#include <pthread.h>
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>

#define THREAD_NUM 4
#define SUM_LOOP_SIZE 100000

uint64_t sum;

void *
thread(void *arg)
{
    for (int i = 0; i < SUM_LOOP_SIZE; i++) {
        sum++;
    }
    return NULL;
}

int
main()
{
    pthread_t tid[THREAD_NUM];
    uint64_t counter = 0;
    while (1) {
        counter++;
        for (int i = 0; i < THREAD_NUM; i++) {
            int ret = pthread_create(&tid[i], NULL, thread, NULL);
            if (ret != 0) {
                fprintf(stderr, "Create thread error: %s", strerror(ret));
                return 1;
            }
        }

        for (int i = 0; i < THREAD_NUM; i++) {
            int ret = pthread_join(tid[i], NULL);
            if (ret != 0) {
                fprintf(stderr, "Join thread error: %s", strerror(ret));
                return 1;
            }
        }

        if (sum != THREAD_NUM * SUM_LOOP_SIZE) {
            fprintf(stderr, "Exit after running %" PRIu64 " times, sum=%" PRIu64 "\n", counter, sum);
            return 1;
        }

        sum = 0;
    }

    return 0;
}

Built and run the program:

$ ./multi_thread_one_cpu
Exit after running 17273076 times, sum=200000
$ ./multi_thread_one_cpu
Exit after running 1539708 times, sum=100000

Change “uint64_t sum;” to “volatile uint64_t sum;“, compile and run again:

$ ./multi_thread_one_cpu
Exit after running 20 times, sum=200000
$ ./multi_thread_one_cpu
Exit after running 50 times, sum=200000

Exit much faster.

In summary, when there are multiple threads access same variable, always use lock. P.S., the code can be found here.

The caveat of thread name length in glibc

Recently, our team met an interesting bug: the process is configured to spawn 16 threads, but only spawns 10 threads in reality. The thread code is like this:

static void *
stat_consumer_thread_run(void *data)
{
    stat_consumer_thread_t *thread = data;
    char thread_name[64];
    snprintf(thread_name, sizeof(thread_name), "stat.consumer.%d",
        thread->id);
    int rc = pthread_setname_np(pthread_self(), thread_name);
    if (rc != 0) {
        return NULL;
    }

    ......
    return NULL;
}

After checking pthread_setname_np manual, we found:

The thread name is a meaningful C language string, whose length is restricted to 16 characters, including the terminating null byte (’\0’).

So thread name is restricted to 16 characters, “stat.consumer.0” ~ “stat.consumer.9” are set successfully, but “stat.consumer.10” ~ “stat.consumer.15” are not, and the corresponding threads are failed to run.

Exit main thread and keep other threads running

In C programming, if using return in main function, the whole process will terminate. To only let main thread gone, and keep other threads live, you can use thrd_exit in main function. Check following code:

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

int
print_thread(void *s)
{
    thrd_detach(thrd_current());
    for (size_t i = 0; i < 5; i++)
    {
        sleep(1);
        printf("i=%zu\n", i);
    }
    thrd_exit(0);
}

int
main(void)
{
    thrd_t tid;
    if (thrd_success != thrd_create(&tid, print_thread, NULL)) {
        fprintf(stderr, "Create thread error\n");
        return 1;
    }
    thrd_exit(0);
}

Run it:

$ ./main
i=0
i=1
i=2
i=3
i=4

You can see even main thread exited, the other thread still worked.

P.S., the code can be downloaded here.

First taste of C thread APIs

Since C11, C provides standard thread APIs like what C++ does. It means technically, you should use C‘s standard thread APIs to do multi-thread stuff, not pthread APIs. Below is a simple example:

#include <threads.h>
#include <stdio.h>

int print_thread(void* s)
{
    printf("%s\n", (char*)s);
    thrd_exit(0);
}
int main()
{
    thrd_t tid;
    if (thrd_success != thrd_create(&tid, print_thread, "Hello world"))
    {
        fprintf(stderr, "Create thread error\n");
        return 1;
    }
    thrd_join(tid, NULL);
    return 0;
}

Check thrd_create implementation in glibc:

#include "thrd_priv.h"

int
thrd_create (thrd_t *thr, thrd_start_t func, void *arg)
{
  _Static_assert (sizeof (thr) == sizeof (pthread_t),
          "sizeof (thr) != sizeof (pthread_t)");

  int err_code = __pthread_create_2_1 (thr, ATTR_C11_THREAD,
                       (void* (*) (void*))func, arg);
  return thrd_err_map (err_code);
}

You can see thrd_create just encapsulates __pthread_create_2_1, so you can guess in glibc, the standard C thread APIs are just wrappers of pthreadimplementation. It means you need to link pthread library during compiling. Otherwise you will meet following errors:

main.c:(.text+0x1e): undefined reference to `thrd_exit'
......
main.c:(.text+0x4f): undefined reference to `thrd_create'
/usr/bin/ld: main.c:(.text+0x60): undefined reference to `thrd_join'

P.S., the full code is here.

Explore Dragonfly BSD thread model

From Dragonfly BSD‘s official document:

A user process contains one or more LWP (Light Weight Process) entities. Each entity represents a user thread under that process.

I want to explore the thread model myself. So I write a simple program which launches 2 threads in main function, and print process ID, LWP ID and thread id from C++‘s get_id function:

void output_thread_id(const std::string& prefix)
{
    std::stringstream ss;
    ss <<"Process ID is " << getpid() <<
        ", lwp ID is " << lwp_gettid() <<
        ", " << prefix << std::this_thread::get_id() <<'\n';
    std::cout << ss.str();
}

Build and run it:

# ./spawn_threads
Process ID is 16394, lwp ID is 1, main thread ID is 0x8007d00c0
Process ID is 16394, lwp ID is 2, sub thread ID is 0x8007d0240
Process ID is 16394, lwp ID is 3, sub thread ID is 0x8007d03c0

All 3 threads have same process ID, but LWP ID begins with 1, and increases contiguously (this is not same as Linux). By default, top command will only show processes:

Press ‘H‘ can display threads’ information (For ps command, -H option can be used to show threads):

P.S., the full code is here.

Reference:
How to get the thread number and every thread’s ID of a running process? .