C语言线程编程深度解析
文章目录
- 前言
- 一、线程基础概念
- 1. 什么是线程?
- 2. 线程与进程的区别
- 二、POSIX线程库(pthread)
- 1. pthread简介
- 2. 编译与链接
- 3. 创建线程
- 示例代码:
- 4. 线程同步
- 互斥锁(Mutex)
- 示例代码:
- 条件变量(Condition Variable)
- 示例代码:
- 5. 死锁与解决方案
- 死锁的产生条件
🌈你好呀!我是 山顶风景独好
🎈欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!😊
🌸愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。
📖这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。🌟
🚀让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!💪✨
前言
在现代计算中,多线程编程是提高应用程序效率和响应能力的关键技术之一。C语言,作为系统编程的基石,提供了强大的工具来直接管理线程,尽管它没有内置的线程支持,但通过POSIX线程库(通常称为pthread),开发者可以在Unix-like系统(如Linux)上实现多线程功能。本文将深入探讨C语言中的线程编程,包括基本概念、线程创建、同步、互斥、死锁处理等多个方面,并通过丰富的示例代码来加深理解。
一、线程基础概念
1. 什么是线程?
线程是进程中的一个执行单元,负责执行程序中的代码。一个进程可以包含多个线程,它们共享进程的地址空间、全局变量等资源,但拥有各自独立的栈和程序计数器。这种设计使得线程间的通信和数据共享比进程间更为高效。
2. 线程与进程的区别
- 资源拥有:进程是资源分配的基本单位,拥有独立的内存空间和系统资源;线程是CPU调度的基本单位,不拥有资源,但可与同属进程的其他线程共享资源。
- 开销:创建和销毁一个进程的开销远大于线程,因为进程涉及内存空间的分配和回收、系统资源的分配等;而线程的创建和销毁只需保存和恢复少量的寄存器内容。
- 并发性:在同一时刻,多个进程可以并发执行,但进程间通信较为复杂;同一进程内的多个线程也可以并发执行,且通信更为简便。
二、POSIX线程库(pthread)
1. pthread简介
POSIX线程(pthread)是IEEE为要求操作系统兼容设计的一系列API标准的总称,旨在提供跨平台的线程支持。在Linux系统中,pthread是实现多线程编程的标准方式。
2. 编译与链接
使用pthread库的程序需要在编译时链接pthread库。例如,对于名为mythread.c的源文件,编译命令为:
gcc -o mythread mythread.c -lpthread
3. 创建线程
pthread提供了一个核心函数pthread_create用于创建新线程。其基本原型为:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- thread:指向pthread_t类型变量的指针,用于存储新线程的ID。
- attr:线程属性指针,通常传递NULL以采用默认属性。
- start_routine:指向线程函数的指针,该函数在新线程中执行。
- arg:传递给线程函数的参数。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *print_message_function(void *ptr) {
char *message;
message = (char *) ptr;
printf("%s \n", message);
pthread_exit(NULL); // 线程退出
}
int main() {
pthread_t thread1, thread2; // 线程的标识符
const char *message1 = "Thread 1";
const char *message2 = "Thread 2";
int iret1, iret2;
// 创建线程
iret1 = pthread_create(&thread1, NULL, print_message_function, (void*) message1);
if (iret1) {
fprintf(stderr, "Error - pthread_create() return code: %d\n", iret1);
exit(EXIT_FAILURE);
}
iret2 = pthread_create(&thread2, NULL, print_message_function, (void*) message2);
if (iret2) {
fprintf(stderr, "Error - pthread_create() return code: %d\n", iret2);
exit(EXIT_FAILURE);
}
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("Thread 1 returns: %d\n", iret1);
printf("Thread 2 returns: %d\n", iret2);
exit(EXIT_SUCCESS);
}
在这个例子中,我们创建了两个线程,每个线程执行print_message_function函数,打印不同的消息。pthread_join函数用于等待线程执行完毕,确保主线程不会提前退出。
4. 线程同步
在多线程程序中,线程间的同步是至关重要的,它确保线程按照预期的顺序执行,避免数据竞争和不一致。
互斥锁(Mutex)
互斥锁是pthread中用于保护共享资源不被多个线程同时访问的机制。通过锁定(lock)和解锁(unlock)操作,确保同一时刻只有一个线程能访问特定资源。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t lock;
void *print_message_function(void *ptr) {
pthread_mutex_lock(&lock);
char *message;
message = (char *) ptr;
printf("%s \n", message);
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
int main() {
pthread_t thread1, thread2;
const char *message1 = "Thread 1";
const char *message2 = "Thread 2";
int iret1, iret2;
pthread_mutex_init(&lock, NULL);
iret1 = pthread_create(&thread1, NULL, print_message_function, (void*) message1);
if (iret1) {
fprintf(stderr, "Error - pthread_create() return code: %d\n", iret1);
exit(EXIT_FAILURE);
}
iret2 = pthread_create(&thread2, NULL, print_message_function, (void*) message2);
if (iret2) {
fprintf(stderr, "Error - pthread_create() return code: %d\n", iret2);
exit(EXIT_FAILURE);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&lock);
exit(EXIT_SUCCESS);
}
在这个例子中,我们使用了pthread_mutex_lock和pthread_mutex_unlock来确保两个线程不会同时打印消息,避免了输出的混乱。
条件变量(Condition Variable)
条件变量允许线程在某些条件不满足时等待,并在条件满足时被唤醒。它与互斥锁配合使用,以实现更复杂的线程同步逻辑。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t lock;
pthread_cond_t cond;
int ready = 0;
void *wait_function(void *ptr) {
pthread_mutex_lock(&lock);
while (!ready) {
pthread_cond_wait(&cond, &lock);
}
printf("Thread is unblocked.\n");
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
void *signal_function(void *ptr) {
sleep(1); // 模拟某些工作
pthread_mutex_lock(&lock);
ready = 1;
printf("Setting ready to 1.\n");
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
pthread_exit(NULL);
}
int main() {
pthread_t thread1, thread2;
int iret1, iret2;
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&cond, NULL);
iret1 = pthread_create(&thread1, NULL, wait_function, NULL);
if (iret1) {
fprintf(stderr, "Error - pthread_create() return code: %d\n", iret1);
exit(EXIT_FAILURE);
}
iret2 = pthread_create(&thread2, NULL, signal_function, NULL);
if (iret2) {
fprintf(stderr, "Error - pthread_create() return code: %d\n", iret2);
exit(EXIT_FAILURE);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
exit(EXIT_SUCCESS);
}
在这个例子中,wait_function线程等待条件变量cond,而signal_function线程在准备好后发送信号,唤醒等待线程。
5. 死锁与解决方案
死锁是指两个或多个线程在互相等待对方释放资源,导致它们之间形成了一种循环等待的情况,从而使得这些线程都无法继续执行。死锁是多线程编程中需要特别注意和避免的问题,因为它会导致程序挂起,无法正常工作。
死锁的产生条件
死锁通常发生在满足以下四个条件时:
- 互斥条件:至少有一个资源必须处于非共享模式,即一次只有一个线程能使用资源。如果其他线程请求该资源,则请求线程必须等待。
持有并等待条件:一个线程至少持有一个资源,并等待获取额外的资源,而该资源可能被其他线程持有。- 不可抢占条件:资源一旦被分配,就无法被强制从该线程中取走,必须由持有它的线程主动释放。
- 循环等待条件:存在一个等待循环,每个线程都等待下一个线程所持有的资源。
死锁的解决方案
为了避免死锁,可以采取以下几种策略:
- 预防策略:
- 破坏互斥条件:使资源尽可能多地共享,从而减少死锁的可能性。
破坏持有并等待条件:要求所有线程在开始时一次性请求所有所需资源,或者要求线程在获取资源时不得持有其他资源。- 破坏不可抢占条件:允许资源被抢占,即当一个线程持有的资源被另一个线程更需要时,可以强制剥夺该资源。
- 破坏循环等待条件:采用资源有序分配法(Resource Ordering),给每个资源分配一个唯一的编号,线程必须按编号的顺序请求资源。
- 避免策略:
使用银行家算法等动态地检查资源分配的安全性,只允许那些不会形成死锁的资源请求。- 检测策略:
定期检测系统中的资源分配情况,一旦发现死锁,立即采取措施解除。- 恢复策略:
- 终止一个或多个线程以打破死锁循环。可以选择终止代价最小的线程,如优先级最低的线程或执行时间最短的线程。
- 回滚线程,使其放弃所占有的资源,回退到某个安全状态。
- 增加资源,以满足被阻塞线程的需求,从而解除死锁。
示例:预防死锁的资源有序分配法
以下是一个使用资源有序分配法预防死锁的简化示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_RESOURCES 2
pthread_mutex_t resource_locks[NUM_RESOURCES];
int resources[NUM_RESOURCES] = {1, 1}; // 假设资源初始都可用
void *thread_function(void *arg) {
int thread_id = *(int *)arg;
int first_resource = thread_id % NUM_RESOURCES;
int second_resource = (thread_id + 1) % NUM_RESOURCES;
// 按照资源编号的顺序请求资源
pthread_mutex_lock(&resource_locks[first_resource]);
while (resources[first_resource] == 0) {
pthread_mutex_unlock(&resource_locks[first_resource]); // 释放锁以避免忙等
sched_yield(); // 让出CPU,等待资源可用
pthread_mutex_lock(&resource_locks[first_resource]);
}
resources[first_resource] = 0; // 占用资源
pthread_mutex_lock(&resource_locks[second_resource]);
while (resources[second_resource] == 0) {
pthread_mutex_unlock(&resource_locks[second_resource]);
pthread_mutex_unlock(&resource_locks[first_resource]); // 释放已占有的资源,避免死锁
sched_yield();
pthread_mutex_lock(&resource_locks[first_resource]);
pthread_mutex_lock(&resource_locks[second_resource]);
}
resources[second_resource] = 0; // 占用资源
// 执行某些操作
printf("Thread %d has acquired resources %d and %d\n", thread_id, first_resource, second_resource);
// 释放资源
resources[second_resource] = 1;
pthread_mutex_unlock(&resource_locks[second_resource]);
resources[first_resource] = 1;
pthread_mutex_unlock(&resource_locks[first_resource]);
pthread_exit(NULL);
}
int main() {
pthread_t threads[4];
int thread_ids[4] = {0, 1, 2, 3};
for (int i = 0; i < NUM_RESOURCES; i++) {
pthread_mutex_init(&resource_locks[i], NULL);
}
for (int i = 0; i < 4; i++) {
pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
}
for (int i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
for (int i = 0; i < NUM_RESOURCES; i++) {
pthread_mutex_destroy(&resource_locks[i]);
}
return 0;
}
✨ 这就是今天要分享给大家的全部内容了,我们下期再见!😊
🏠 我在CSDN等你哦!我的主页😍