文章目录
-
- 前言
- 一、线程基础概念
-
- [1. 什么是线程?](#1. 什么是线程?)
- [2. 线程与进程的区别](#2. 线程与进程的区别)
- 二、POSIX线程库(pthread)
-
- [1. pthread简介](#1. pthread简介)
- [2. 编译与链接](#2. 编译与链接)
- [3. 创建线程](#3. 创建线程)
- [4. 线程同步](#4. 线程同步)
-
- 互斥锁(Mutex)
- [条件变量(Condition Variable)](#条件变量(Condition Variable))
- [5. 死锁与解决方案](#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的源文件,编译命令为:
bash
gcc -o mythread mythread.c -lpthread
3. 创建线程
pthread提供了一个核心函数pthread_create用于创建新线程。其基本原型为:
c
#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:传递给线程函数的参数。
示例代码:
c
#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)操作,确保同一时刻只有一个线程能访问特定资源。
示例代码:
c
#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)
条件变量允许线程在某些条件不满足时等待,并在条件满足时被唤醒。它与互斥锁配合使用,以实现更复杂的线程同步逻辑。
示例代码:
c
#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),给每个资源分配一个唯一的编号,线程必须按编号的顺序请求资源。
避免策略:
使用银行家算法等动态地检查资源分配的安全性,只允许那些不会形成死锁的资源请求。检测策略:
定期检测系统中的资源分配情况,一旦发现死锁,立即采取措施解除。恢复策略:
- 终止一个或多个线程以打破死锁循环。可以选择终止代价最小的线程,如优先级最低的线程或执行时间最短的线程。
- 回滚线程,使其放弃所占有的资源,回退到某个安全状态。
- 增加资源,以满足被阻塞线程的需求,从而解除死锁。
示例:预防死锁的资源有序分配法以下是一个使用资源有序分配法预防死锁的简化示例:
c
#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等你哦!我的主页😍