This content originally appeared on Level Up Coding – Medium and was authored by Anshumaan Tiwari
Threads are fundamental to achieving parallelism in programs, allowing multiple tasks to run concurrently within the same process. There are two main types of threads: User-Level Threads (ULT) and Kernel-Level Threads (KLT). Here’s how you can implement both in C.
User-Level Threads (ULT)
User-level threads are managed entirely in user space, without kernel intervention. This makes them fast to create and manage, but with some limitations, like not being able to run in true parallelism on multiprocessor systems.
Implementing User-Level Threads in C
To implement ULTs, you can use the setjmp and longjmp functions from the C standard library to manually manage the execution contexts (i.e., saving and restoring the CPU state).
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#define THREAD_STACK_SIZE 1024 * 64
typedef struct thread {
jmp_buf context;
void (*func)(void);
void *stack;
} thread_t;
thread_t *current_thread;
thread_t *threads[2];
int thread_count = 0;
void thread_create(void (*func)(void)) {
thread_t *t = (thread_t *)malloc(sizeof(thread_t));
t->func = func;
t->stack = malloc(THREAD_STACK_SIZE);
if (setjmp(t->context) == 0) {
// Adjust the stack pointer to the new stack
t->context[0].__jmpbuf[6] = (__intptr_t)((char *)t->stack + THREAD_STACK_SIZE - sizeof(void *));
threads[thread_count++] = t;
}
}
void thread_yield() {
if (setjmp(current_thread->context) == 0) {
current_thread = threads[(current_thread == threads[0]) ? 1 : 0];
longjmp(current_thread->context, 1);
}
}
void thread_start() {
current_thread = threads[0];
longjmp(current_thread->context, 1);
}
void thread1_func() {
while (1) {
printf("Thread 1\n");
thread_yield();
}
}
void thread2_func() {
while (1) {
printf("Thread 2\n");
thread_yield();
}
}
int main() {
thread_create(thread1_func);
thread_create(thread2_func);
thread_start();
return 0;
}
Explanation:
- thread_create: Initializes a new thread, setting up its stack and saving its context using setjmp.
- thread_yield: Saves the current thread’s state and switches to the next thread.
- thread_start: Begins execution of the first thread.
Kernel-Level Threads (KLT)
Kernel-level threads are managed directly by the operating system’s kernel. In Linux, the POSIX threads (pthreads) library is commonly used to implement KLTs.
Implementing Kernel-Level Threads in C
Here’s how you can create and manage kernel-level threads using the pthreads library:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *thread1_func(void *arg) {
while (1) {
printf("Thread 1\n");
sleep(1);
}
return NULL;
}
void *thread2_func(void *arg) {
while (1) {
printf("Thread 2\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
// Create threads
if (pthread_create(&thread1, NULL, thread1_func, NULL) != 0) {
perror("Failed to create thread 1");
return 1;
}
if (pthread_create(&thread2, NULL, thread2_func, NULL) != 0) {
perror("Failed to create thread 2");
return 1;
}
// Join threads (not necessary in this example, but good practice)
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
return 0;
}
Explanation:
- pthread_create: Creates a new kernel-level thread, which is managed by the OS.
- pthread_join: Waits for the specified thread to finish execution, though in this example the threads run indefinitely.
Key Differences
- User-Level Threads (ULT): Fast to create and switch between, but limited by the inability to run on multiple processors simultaneously. Managed entirely in user space.
- Kernel-Level Threads (KLT): Slower to create and manage due to kernel involvement, but capable of true parallelism on multiprocessor systems. Managed by the operating system.
Conclusion
Implementing user-level threads gives you more control and can be more efficient in certain scenarios, but lacks the parallel processing power that kernel-level threads provide. Kernel-level threads, while heavier, are more versatile and can take full advantage of modern multicore processors.
Both implementations are valuable tools in a developer’s toolkit, depending on the specific needs of your application.
User-Level and Kernel-Level Threads Using C was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding – Medium and was authored by Anshumaan Tiwari