C语言线程编程深度解析

文章目录

🌈你好呀!我是 山顶风景独好

🎈欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!😊

🌸愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。

📖这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。🌟

🚀让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!💪✨

前言

在现代计算中,多线程编程是提高应用程序效率和响应能力的关键技术之一。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. 死锁与解决方案

死锁是指两个或多个线程在互相等待对方释放资源,导致它们之间形成了一种循环等待的情况,从而使得这些线程都无法继续执行。死锁是多线程编程中需要特别注意和避免的问题,因为它会导致程序挂起,无法正常工作。

死锁的产生条件

死锁通常发生在满足以下四个条件时:

  1. 互斥条件 :至少有一个资源必须处于非共享模式,即一次只有一个线程能使用资源。如果其他线程请求该资源,则请求线程必须等待。
    持有并等待条件:一个线程至少持有一个资源,并等待获取额外的资源,而该资源可能被其他线程持有。

  2. 不可抢占条件:资源一旦被分配,就无法被强制从该线程中取走,必须由持有它的线程主动释放。

  3. 循环等待条件 :存在一个等待循环,每个线程都等待下一个线程所持有的资源。
    死锁的解决方案
    为了避免死锁,可以采取以下几种策略:

  4. 预防策略:

    • 破坏互斥条件:使资源尽可能多地共享,从而减少死锁的可能性。
      破坏持有并等待条件:要求所有线程在开始时一次性请求所有所需资源,或者要求线程在获取资源时不得持有其他资源。
    • 破坏不可抢占条件:允许资源被抢占,即当一个线程持有的资源被另一个线程更需要时,可以强制剥夺该资源。
    • 破坏循环等待条件:采用资源有序分配法(Resource Ordering),给每个资源分配一个唯一的编号,线程必须按编号的顺序请求资源。
  5. 避免策略:
    使用银行家算法等动态地检查资源分配的安全性,只允许那些不会形成死锁的资源请求。

  6. 检测策略:
    定期检测系统中的资源分配情况,一旦发现死锁,立即采取措施解除。

  7. 恢复策略:

    • 终止一个或多个线程以打破死锁循环。可以选择终止代价最小的线程,如优先级最低的线程或执行时间最短的线程。
    • 回滚线程,使其放弃所占有的资源,回退到某个安全状态。
    • 增加资源,以满足被阻塞线程的需求,从而解除死锁。
      示例:预防死锁的资源有序分配法

以下是一个使用资源有序分配法预防死锁的简化示例:

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等你哦!我的主页😍

相关推荐
pumpkin8451410 分钟前
jacoco-maven-plugin使用
java·maven
java—大象13 分钟前
基于Java+Jsp+SpringMVC漫威手办商城系统设计和实现
java·数据库·spring boot·python·课程设计
TeYiToKu14 分钟前
笔记整理—内核!启动!—linux应用编程、网络编程部分(6)随机数与proc文件系统
linux·c语言·arm开发·笔记·嵌入式硬件
JOJO___15 分钟前
Spring MVC 基本配置步骤 总结
java·spring·mvc
鱟鲥鳚16 分钟前
Maven的详细解读和配置
java·maven
程序猿进阶18 分钟前
定时任务上云改造方案
java·服务器·网络·数据库·性能优化·定时任务·上云
一休哥助手19 分钟前
Java/Spring项目中包名以“com”开头的原因分析
java·开发语言·spring
最爱菠萝32 分钟前
Java中浮点数运算存在的精度问题以及解决方法
java
张某布响丸辣34 分钟前
SQL关键字的优先级执行顺序:深入理解SQL查询的构造
java·sql·mysql·面试
生命的演绎1 小时前
Java将驼峰命名转化为下划线命名
java·开发语言