Complete guide to multithreading in C - from basics to exam mastery C语言多线程完整指南 - 从基础到考试精通
arg?
概念1:arg(参数)是什么?
Real-world analogy: Think of a delivery service 生活例子:外卖配送
Boss (main function) → Delivery Person (thread function) Prepares order (data) Receives order (arg) ↓ ↓ "Address: 123 Main St" "This package is the order" "Food: Pizza" "Let me open it..." "Phone: 123456"老板(主函数) → 送餐员(线程函数) 准备订单(数据) 接收订单(arg) ↓ ↓ "地址:XX路123号" "这个包裹里是订单" "食物:披萨" "打开看看..." "电话:123456"
// Boss prepares the order (main function)
struct thread_data order = {
.address = "123 Main St",
.food = "Pizza",
.phone = "123456"
};
// Delivery person receives the order (thread function)
void *delivery(void *arg) { // ← arg is the "package"
// Open the package to see what's inside...
}
✅ Summary: arg = the "package" passed to the thread ✅ 总结:arg = 传给线程的"包裹"
(struct thread_data *)arg - Opening the Package
概念2:(struct thread_data *)arg - 拆包裹
Real-world analogy: Receiving a package 生活例子:收快递
You receive a package (arg) ↓ Package label: "Generic Package" (void*) ↓ But you ordered a "Book" (struct thread_data) ↓ Open it: It IS a book! ↓ (struct thread_data *)arg ← Telling the system: "This is a book!"你收到一个包裹(arg) ↓ 包裹上写着:"通用包裹"(void*) ↓ 但你订的是"书籍"(struct thread_data) ↓ 拆开看看:确实是书! ↓ (struct thread_data *)arg ← 告诉系统:"这是书!"
void *worker(void *arg) {
// Step 1: Receive the package
void *arg // Generic box
// Step 2: Open the package (type casting)
(struct thread_data *)arg // "Open it! This is a task sheet!"
// Step 3: Give it a name for easy use
struct thread_data *data = (struct thread_data *)arg;
// Step 4: Now you can use it!
data->thread_id // Read the task sheet's ID
}
✅ Summary: Type casting = telling the system "what's in the package" ✅ 总结:类型转换 = 告诉系统"包裹里是什么"
FILE *fp - File Pointer
概念3:FILE *fp - 文件指针
Real-world analogy: Library card 生活例子:图书馆借书证
Library (file) → Your library card (FILE *fp) Many books ← Use card to borrow books | ↓ Can only operate through the card (Cannot directly touch the file)图书馆(文件) → 你的借书证(FILE *fp) 很多书 ← 用借书证借书 | ↓ 只能通过借书证操作 (不能直接操作文件)
// FILE is like the "library card" type
// *fp is your library card
FILE *fp; // I have a "file library card"
// With this card you can:
// - Open file (borrow book)
// - Read content (read book)
// - Close file (return book)
✅ Summary: FILE *fp = the "remote control" for files ✅ 总结:FILE *fp = 文件的"遥控器"
fopen() - Opening Files
概念4:fopen() - 打开文件
Real-world analogy: Opening a door 生活例子:开门
// Opening a file
FILE *fp = fopen("data.txt", "r");
// ^^^^^^^^^^^^^^^^^^^^^^
// Open "data.txt", mode is "read"
// ^^^
// Returns the "key" (file pointer)
if (fp == NULL) {
// No key! Door won't open!
printf("Cannot open file!\n");
} else {
// Got the key! Can enter now!
printf("File opened successfully!\n");
}
// Different modes (like different keys)
fopen("file.txt", "r"); // "Read" key - can only view, not modify
fopen("file.txt", "w"); // "Write" key - can write, clears old content
fopen("file.txt", "a"); // "Append" key - adds at the end
fopen("file.txt", "r+"); // "Read/Write" key - can view and modify
✅ Summary: fopen() = open file, gives you "remote control" ✅ 总结:fopen() = 打开文件,给你"遥控器"
-> Operator
概念5:-> 操作符
Real-world analogy: Remote control for TV 生活例子:遥控器操作电视
// Method 1: Direct access (variable)
struct thread_data info; // TV is in your hands
info.thread_id = 0; // Press button directly (use .)
// Method 2: Use remote control (pointer)
struct thread_data *data = &info; // You have the remote
data->thread_id = 0; // Use remote to press button (use ->)
// Comparison table:
// ╔═══════════════════════╦═══════╦═══════════════════╗
// ║ Situation ║ Use ║ Example ║
// ╠═══════════════════════╬═══════╬═══════════════════╣
// ║ Variable (TV in hand) ║ . ║ info.thread_id ║
// ║ Pointer (have remote) ║ -> ║ data->thread_id ║
// ╚═══════════════════════╩═══════╩═══════════════════╝
✅ Summary: -> = "remote access" to struct members through pointer ✅ 总结:-> = 通过指针"遥控"访问结构体成员
Step 1: Main function prepares "task sheet" ↓ struct thread_data task; task.thread_id = 0; task.filename = "data.txt"; ↓ Step 2: Create thread, pass "task sheet address" ↓ pthread_create(..., &task); ↓ Passed to thread function ↓ Step 3: Thread function receives "package" (arg) ↓ void *worker(void *arg) // arg is the "package" ↓ Step 4: Open package (type casting) ↓ struct thread_data *data = (struct thread_data *)arg; ↓ Step 5: Use data-> to access task sheet contents ↓ printf("Filename: %s\n", data->filename); ↓ Step 6: Open file ↓ FILE *fp = fopen(data->filename, "r"); ↓ Step 7: Use file pointer to operate on file ↓ fseek(fp, ...); fgets(line, 11, fp); ↓ Step 8: Close file ↓ fclose(fp);步骤1:主函数准备"任务单" ↓ struct thread_data task; task.thread_id = 0; task.filename = "data.txt"; ↓ 步骤2:创建线程,传递"任务单的地址" ↓ pthread_create(..., &task); ↓ 传给线程函数 ↓ 步骤3:线程函数接收"包裹"(arg) ↓ void *worker(void *arg) // arg 是"包裹" ↓ 步骤4:拆包裹(类型转换) ↓ struct thread_data *data = (struct thread_data *)arg; ↓ 步骤5:用 data-> 访问任务单内容 ↓ printf("文件名:%s\n", data->filename); ↓ 步骤6:打开文件 ↓ FILE *fp = fopen(data->filename, "r"); ↓ 步骤7:用文件指针操作文件 ↓ fseek(fp, ...); fgets(line, 11, fp); ↓ 步骤8:关闭文件 ↓ fclose(fp);
arg = package Passed to you, must openarg = 包裹 传给你,要拆开
(void*) → (struct thread_data*) Generic box → Task sheet box(void*) → (struct thread_data*) 通用盒 → 任务单盒
FILE *fp = file remote control fopen() = turn on, gives you remote fclose() = turn off, return remoteFILE *fp = 文件遥控器 fopen() = 开机,给你遥控器 fclose() = 关机,还遥控器
Pointer uses -> Variable uses . Remember this!指针用 -> 变量用 . 记住这个!
void *worker(void *arg) {
// Q1: Convert arg to what type?
struct thread_data *data = (______)arg;
// Q2: Use what operator to access struct member?
printf("%s\n", data______filename);
// Q3: What function opens a file?
FILE *fp = ______(data->filename, "r");
}
// Answers:
// 1. struct thread_data *
// 2. ->
// 3. fopen
Creates a new thread and starts execution 创建新线程并开始执行
pthread_t thread_id;
pthread_create(
&thread_id, // Thread ID
NULL, // Attributes
worker_function, // Function to run
&data // Argument to pass
);
Waits for thread to complete and gets return value 等待线程完成并获取返回值
void *return_value;
pthread_join(
thread_id, // Thread to wait for
&return_value // Pointer to store return
);
Protects shared data from race conditions 保护共享数据,避免竞态条件
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
// Critical section
pthread_mutex_unlock(&lock);
Lock-free operations for simple data 简单数据的无锁操作
#include
atomic_int counter = 0;
atomic_fetch_add(&counter, 1);
int value = atomic_load(&counter);
❌ WRONG: ❌ 错误:
void *worker(void *arg) {
int count = 10;
return &count; // ❌ count disappears!
}
✅ CORRECT: ✅ 正确:
void *worker(void *arg) {
int *count = malloc(sizeof(int));
*count = 10;
return count; // ✅ malloc memory persists
}
❌ WRONG: ❌ 错误:
pthread_join(thread, (void**)&result);
// Forgot free(result) - memory leak!
✅ CORRECT: ✅ 正确:
pthread_join(thread, (void**)&result);
total += *result;
free(result); // ✅ Always free!
❌ WRONG: ❌ 错误:
void *worker(void *arg) {
total_size += size; // ❌ Race condition!
}
✅ CORRECT: ✅ 正确:
void *worker(void *arg) {
pthread_mutex_lock(&lock);
total_size += size; // ✅ Protected!
pthread_mutex_unlock(&lock);
}
// Will show highlighted code during simulation
#include
#include
#include
// Step 1: Define "task sheet" structure
struct thread_data {
int thread_id; // Thread number: 0, 1, 2, 3, 4
int start_line; // Starting line
int end_line; // Ending line
char *filename; // File name
};
// Step 2: Thread function (what each thread does)
void *worker_thread(void *arg) {
// 1. Get the "task sheet"
struct thread_data *data = (struct thread_data *)arg;
printf("Thread %d starts: lines %d to %d\n",
data->thread_id, data->start_line, data->end_line);
// 2. Open file (each thread opens separately to avoid conflicts)
FILE *fp = fopen(data->filename, "r");
if (fp == NULL) {
printf("Thread %d: Cannot open file\n", data->thread_id);
return NULL;
}
// 3. Jump to my starting position
// Each line is 10 chars, so: start byte = line_number × 10
fseek(fp, data->start_line * 10, SEEK_SET);
// 4. Initialize counter
int count = 0;
char line[11]; // 10 chars + '\0'
// 5. Read and process my lines
for (int i = data->start_line; i <= data->end_line; i++) {
if (fgets(line, 11, fp) == NULL) {
break;
}
// TODO: Call evaluate_expression(line) and check if == 10
// count += (evaluate_expression(line) == 10);
}
// 6. Close file
fclose(fp);
// 7. Return result (must use malloc, local variable disappears!)
int *result = malloc(sizeof(int));
*result = count;
printf("Thread %d complete: found %d expressions\n",
data->thread_id, count);
return result;
}
// Step 3: Main function (boss assigns tasks)
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s \n", argv[0]);
return 1;
}
char *filename = argv[1];
int total_lines = 2880;
int num_threads = 5;
int lines_per_thread = total_lines / num_threads;
// Create 5 "task sheets" and 5 threads
struct thread_data thread_args[5];
pthread_t threads[5];
// Assign work to each thread
for (int i = 0; i < 5; i++) {
thread_args[i].thread_id = i;
thread_args[i].start_line = i * lines_per_thread;
thread_args[i].end_line = (i + 1) * lines_per_thread - 1;
thread_args[i].filename = filename;
// Last thread handles remaining lines
if (i == 4) {
thread_args[i].end_line = total_lines - 1;
}
// Create thread, give it "task sheet"
pthread_create(&threads[i], NULL, worker_thread, &thread_args[i]);
}
// Wait for all threads to complete, collect results
int total_count = 0;
for (int i = 0; i < 5; i++) {
int *result;
pthread_join(threads[i], (void **)&result);
if (result != NULL) {
total_count += *result;
free(result); // Don't forget to free!
}
}
printf("\n=== Final Result ===\n");
printf("Found %d expressions equal to 10\n", total_count);
return 0;
}
/*
* Key Points:
*
* 1. Why not use global variables?
* - 5 threads modifying same global variable → conflict (race condition)
* - Each thread returns own result, main thread adds them up
*
* 2. Why use malloc for return value?
* - pthread_join needs pointer
* - Returning local variable address → variable disappears after function
* - malloc allocates on heap → persists
*
* 3. Why each thread opens file separately?
* - Multiple threads sharing one file pointer → interfere with each other
* - Each thread uses fseek to jump to own position
*
* 4. How to divide work evenly?
* - 2880 ÷ 5 = 576 lines/thread
* - Thread 0: 0-575
* - Thread 1: 576-1151
* - ...and so on
*/
#include
#include
#include
#define QUOTA 10
#define FILE_NOT_FOUND -1
#define QUOTA_EXCEEDED_MESSAGE "Quota exceeded!"
#define QUOTA_RESOLVED_MESSAGE "Quota resolved!"
// Shared data (needs protection)
int total_size = 0;
int warning_printed = 0;
// Mutex lock (like a key)
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// Thread function: monitor one file
void *watch_file(void *arg) {
char *filename = (char *)arg;
printf("Thread starts monitoring: %s\n", filename);
// Keep monitoring until file is deleted
while (1) {
int size = file_size(filename);
if (size == FILE_NOT_FOUND) {
printf("File %s deleted, thread exits\n", filename);
break;
}
// ========== Critical Section: Use lock to protect shared data ==========
// 🔒 Acquire lock (only one thread can enter)
pthread_mutex_lock(&lock);
// Update total size
total_size += size;
// Check if exceeds quota
if (total_size > QUOTA && !warning_printed) {
printf("%s\n", QUOTA_EXCEEDED_MESSAGE);
warning_printed = 1;
} else if (total_size <= QUOTA && warning_printed) {
printf("%s\n", QUOTA_RESOLVED_MESSAGE);
warning_printed = 0;
}
// 🔓 Release lock (let others use)
pthread_mutex_unlock(&lock);
// Wait before checking again
usleep(100000); // 0.1 second
}
return NULL;
}
int main(void) {
char *files[] = {"file1", "file2", "file3"};
int num_files = 3;
pthread_t threads[num_files];
// Create monitoring thread for each file
for (int i = 0; i < num_files; i++) {
pthread_create(&threads[i], NULL, watch_file, files[i]);
}
// Wait for all threads to complete
for (int i = 0; i < num_files; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
/*
* Key Concepts:
*
* 1. What is a race condition?
* Imagine you and friend both putting money in same piggy bank:
* - You see $10 in piggy bank
* - Friend also sees $10 (not updated yet)
* - You add $5, write back $15
* - Friend adds $3, writes back $13 (overwrites yours!)
* - Should be $18, but only $13
*
* 2. How does mutex work?
* - Like a key, only one person can hold it
* - Person with key can modify data
* - After modification, return key for next person
* - This prevents conflicts
*
* 3. Why need warning_printed flag?
* - Avoid printing same message repeatedly
* - Only print when state changes
*
* 4. Why use usleep?
* - Let thread rest, don't check too frequently
* - Reduces CPU usage
* - Also reduces file system load
*/
#include
#include
#include
#include
#define QUOTA 10
#define FILE_NOT_FOUND -1
#define QUOTA_EXCEEDED_MESSAGE "Quota exceeded!"
#define QUOTA_RESOLVED_MESSAGE "Quota resolved!"
// Shared data (using atomic types)
atomic_int total_size = 0;
atomic_int warning_printed = 0;
void *watch_file(void *arg) {
char *filename = (char *)arg;
while (1) {
int size = file_size(filename);
if (size == FILE_NOT_FOUND) {
break;
}
// ========== Atomic operations: Safe without locks ==========
// Atomic operation: safely add to total
atomic_fetch_add(&total_size, size);
// Read current values
int current_total = atomic_load(&total_size);
int warned = atomic_load(&warning_printed);
// Check quota
if (current_total > QUOTA && !warned) {
// Atomic: set warning flag (if not already set)
int expected = 0;
if (atomic_compare_exchange_strong(&warning_printed, &expected, 1)) {
// Only first thread reaching here prints
printf("%s\n", QUOTA_EXCEEDED_MESSAGE);
}
} else if (current_total <= QUOTA && warned) {
// Atomic: clear warning flag
int expected = 1;
if (atomic_compare_exchange_strong(&warning_printed, &expected, 0)) {
printf("%s\n", QUOTA_RESOLVED_MESSAGE);
}
}
usleep(100000);
}
return NULL;
}
int main(void) {
char *files[] = {"file1", "file2", "file3"};
int num_files = 3;
pthread_t threads[num_files];
for (int i = 0; i < num_files; i++) {
pthread_create(&threads[i], NULL, watch_file, files[i]);
}
for (int i = 0; i < num_files; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
pthread_create(&thread_id, NULL,
thread_function,
argument_to_pass);
thread_id: Thread's "ID card"线程的"身份证"NULL: Thread attributes (usually NULL for exams)线程属性(考试通常用NULL)thread_function: Function thread will execute线程要执行的函数argument: Data passed to thread (usually struct pointer)传给线程的数据(通常是结构体指针)pthread_join(thread_id, &return_value_ptr);
thread_id: Thread to wait for要等待的线程return_value_ptr: Receives thread's return value用来接收线程的返回值pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
// ... protected critical code ...
pthread_mutex_unlock(&lock);
pthread_create = assign task给任务
pthread_join = collect result收结果
malloc, remember free
用 malloc,记得 free
fseek() to jump to position用 fseek() 跳到位置// Key code snippet
fseek(fp, start_line * 10, SEEK_SET);
for (int i = start; i <= end; i++) {
fgets(line, 11, fp);
// process...
}
while(1) loop用 while(1) 持续监控usleep() to avoid busy-waiting用 usleep() 避免忙等待// Key code snippet
pthread_mutex_lock(&lock);
total_size += size;
if (total_size > QUOTA && !warned) {
printf("Warning!\n");
warned = 1;
}
pthread_mutex_unlock(&lock);
pthread_create and pthread_join syntax记住 pthread_create 和 pthread_join 语法malloc知道如何用 malloc 返回结果fseek to jump to position知道如何用 fseek 跳到文件位置free() after pthread_join()记得在 pthread_join() 后 free()