c/c++ 函数指针、线程和互斥锁


最近这段时间在学习的过程中遇到了许多问题,记录一下~

1、函数指针
1.1什么是函数指针

在C语言中,可以将函数看作为一种特殊的数据类型,因此可以有一个指向它的指针。就像 int 指针类型用来存储 int 类型变量的地址一样,函数指针类型用来存储函数的地址。这意味着你可以在程序中创建一个指向函数的指针,并通过该指针来调用函数。

1.2函数指针的使用

1)在 C 语言中,可以通过函数指针来实现接口的概念。虽然 C 语言不像 C++ 那样支持面向对象编程,但仍然可以通过函数指针和结构体来模拟接口的行为

2)函数指针允许你将函数当作变量来传递和使用。通过函数指针,你可以把函数当作参数传递给另一个函数,或者作为返回值从一个函数返回,甚至可以存储在数组中或作为结构体/类的成员。这对于实现回调函数、动态加载函等功能非常有用。

下面会有具体的代码例子说明:如何使用函数指针和结构体来模拟接口的行为,并使用回调函数(将函数指针作为参数传递,以实现回调机制)。

假设我们要创建一个简单的“计算器”接口,该接口可以执行加法和减法操作。我们将使用结构体来定义这个接口,并通过函数指针来实现不同的操作。

#include <stdio.h>

// 定义一个结构体来表示计算器接口
typedef struct Calculator {
    double (*add)(double, double); // 加法函数指针
    double (*subtract)(double, double); // 减法函数指针
} Calculator;

// 实现加法函数
double add(double x, double y) {
    return x + y;
}

// 实现减法函数
double subtract(double x, double y) {
    return x - y;
}

// 主函数
int main() {
    // 创建一个Calculator实例,并设置其函数指针
    Calculator myCalc = {
        .add = add,
        .subtract = subtract
    };

    // 使用Calculator结构体中的函数指针进行计算
    double sum = myCalc.add(5.0, 3.0);
    double diff = myCalc.subtract(5.0, 3.0);

    printf("5 + 3 = %.1f\n", sum);
    printf("5 - 3 = %.1f\n", diff);

    return 0;
}

在这个例子中,Calculator 结构体包含两个函数指针:addsubtract。这些指针指向实际执行加法和减法操作的函数。在主函数中,我们创建了一个 Calculator 实例,并为其指针成员分配了相应的函数地址。然后我们就可以通过这些指针来调用函数了。

接下来,让我们看看如何使用回调函数:

#include <stdio.h>

// 回调函数类型定义
typedef double (*Operation)(double, double);

// 处理器函数,接受一个回调函数
double process(double x, double y, Operation op) {
    return op(x, y);
}

// 实现加法函数
double add(double x, double y) {
    return x + y;
}

// 实现减法函数
double subtract(double x, double y) {
    return x - y;
}

int main() {
    // 使用process函数和回调函数
    double sum = process(5.0, 3.0, add);
    double diff = process(5.0, 3.0, subtract);

    printf("5 + 3 = %.1f\n", sum);
    printf("5 - 3 = %.1f\n", diff);

    return 0;
}

在这个例子中,process 函数接受三个参数:两个数字和一个操作(通过 Operation 类型的指针传递)。process 函数使用提供的回调函数来执行所需的操作。这种方式非常适合于需要执行多种不同类型操作的情况,因为你可以通过传递不同的回调函数来改变行为。

2.线程和互斥锁
2.1 什么是互斥锁

互斥锁(Mutex,全称为 Mutual Exclusion),是一种用于多线程同步的机制,用来防止多个线程同时访问共享资源(如全局变量、文件、数据库连接等),从而避免数据竞争和一致性问题。互斥锁通常用于保护那些一次只能由一个线程访问的资源

互斥锁的工作原理如下:

  1. 锁定(Lock):当一个线程需要访问共享资源时,它首先尝试锁定互斥锁。如果互斥锁已经被其他线程锁定,该线程将等待,直到互斥锁被释放。
  2. 访问资源:如果互斥锁没有被锁定,线程将成功锁定它,然后访问共享资源。
  3. 释放(Unlock):访问完毕后,线程将释放互斥锁,允许其他线程锁定并访问共享资源。
2.2 互斥锁通常在以下情况下使用:
  • 访问共享资源:当多个线程需要访问同一个共享资源(如全局变量、文件、数据库连接等)时,为了避免数据竞争和不一致,可以使用互斥锁来保护共享资源。
  • 执行临界区代码:如果一段代码需要独占访问某些资源才能正确执行(即临界区),则可以在进入临界区前获取互斥锁,在离开时释放互斥锁。
  • 控制对共享数据结构的访问:当多个线程需要同时读取或修改共享的数据结构时,使用互斥锁可以确保数据的一致性和完整性。
  • 协调复杂的并发任务:在设计需要多个线程协作完成的任务时,互斥锁可以用来同步线程之间的活动,确保有序执行。
  • 防止竞态条件:在多线程编程中,竞态条件是指两个或多个线程以特定的时间顺序访问共享资源而导致的问题。互斥锁可以用来防止这种情况的发生。
  • 保护敏感操作:对于一些敏感的操作,如配置加载、日志记录等,使用互斥锁可以确保这些操作不会被中断或干扰。

互斥锁虽然强大,但也需要注意正确使用,以避免死锁和其他同步问题。常见的注意事项包括:

  • 确保每次获取锁后都能够正确地释放锁。
  • 在函数返回之前释放锁。
  • 避免在持有锁的情况下阻塞或等待其他资源,以防止死锁。
  • 在多线程环境中,尽量减少锁的使用范围,以提高程序的并发性能。

在 POSIX 线程(pthreads)中,互斥锁可以通过 pthread_mutex_t 类型来实现。以下是使用互斥锁的基本步骤:

  1. 初始化互斥锁:pthread_mutex_init(&mutex, NULL);
  2. 锁定互斥锁:pthread_mutex_lock(&mutex);
  3. 访问共享资源
  4. 释放互斥锁:pthread_mutex_unlock(&mutex);
  5. 销毁互斥锁:pthread_mutex_destroy(&mutex);

下面是一个使用 POSIX 线程库(pthreads)和互斥锁(mutex)的简单示例,展示如何使用互斥锁来保护一个共享资源。在这个例子中,我们将会创建两个线程,它们将交替地修改一个共享变量。为了确保线程安全,我们将使用互斥锁来保护对这个变量的访问。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

// 1.定义一个全局的互斥锁并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 2.共享资源:定义了一个共享资源 shared_resource,初始值为0。
int shared_resource = 0;

//3. 线程函数
/*线程函数 thread_function 在循环中递增共享资源 shared_resource,并在每次修改前后锁定和解锁互斥锁 mutex。printf 输出当前线程ID以及 shared_resource 的值。sleep(1) 模拟了一些耗时的操作。*/
void* thread_function(void* arg) {
    // 模拟工作负载
    for (int i = 0; i < 5; ++i) {
        // 锁定互斥锁
        pthread_mutex_lock(&mutex);

        // 访问和修改共享资源
        shared_resource++;
        printf("Thread %ld: shared_resource is now %d\n", pthread_self(),shared_resource);

        // 模拟其他工作
        sleep(1);

        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

//4.主线程
/*主函数 main 创建了两个线程 thread1 和 thread2,并将它们连接到 thread_function 上。如果创建线程失败,则输出错误信息并返回非零值。最后,等待两个线程结束后销毁互斥锁,并输出一条消息表示所有线程已完成。*/
int main() {
    pthread_t thread1, thread2;

    // 创建两个线程
    if (pthread_create(&thread1, NULL, thread_function, NULL) != 0) {
        perror("Failed to create thread 1");
        return 1;
    }
    if (pthread_create(&thread2, NULL, thread_function, NULL) != 0) {
        perror("Failed to create thread 2");
        return 1;
    }

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);

    printf("Both threads have finished.\n");
    return 0;
}

有锁时的运行结果:
在这里插入图片描述

无锁时的运行结果:
在这里插入图片描述

由于互斥锁的存在,即使两个线程并发执行,shared_resource 的值也会被正确地递增。每次修改 shared_resource 前后都会锁定和解锁互斥锁,确保了线程安全。

关于什么是线程?这篇文章中有写:什么是进程?线程?进程栈?线程栈?(小白请进!!!含代码示例和运行结果分析)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值