【北京迅为】《iTOP-3588开发板系统编程手册》-第9章 线程

RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP,内置NPU,支持INT4/INT8/INT16/FP16混合运算能力,支持安卓12和、Debian11、Build root、Ubuntu20和22版本登系统。了解更多信息可点击迅为官网   

【粉丝群】824412014

【实验平台】:迅为RK3588开发板

【内容来源】《iTOP-3588开发板系统编程手册》

【全套资料及网盘获取方式】联系淘宝客服加入售后技术支持群内下载

【视频介绍】:【强者之芯】 新一代AIOT高端应用芯片 iTOP -3588人工智能工业AI主板


第9章 线程

经过了前面三个章节的学习可以了解到进程是操作系统中分配资源的最小单位,每个进程都拥有独立的内存空间、代码段、数据段、堆栈和文件描述符等资源,进程之间通常需要通过进程间通信(IPC)机制进行数据交换和协同操作。但是创建和销毁进程的开销较大,而且进程之间通信的机制比较复杂。相比之下,本章节要讲解的线程是操作系统中最小的执行单位,线程的创建和销毁开销非常小,可以更加高效地利用系统资源,提高程序的响应能力和并发性能。下面跟我一起进入线程的学习吧。

9.1线程的创建

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\46”目录下,如下图所示:

学习前的疑问

1.既然已经有了进程为什么还要有线程呢?

2.进程和线程的区别在哪?

3.如何对一个线程进行创建?

线程(thread)是进程中的一个执行流程,是操作系统中调度的最小单位,每个线程都共享同一进程的资源,包括内存空间、文件描述符等。线程之间可以通过共享内存和同步机制进行数据交换和协同操作,相对于进程,线程的创建和销毁开销较小,可以更加高效地利用系统资源,提高程序的并发性能和响应能力。

下面对进程和线程进行区分,进程是一个独立的执行单元,具有自己的内存空间和系统资源,而线程则是在进程内部共享这些资源的执行路径。就像是一个厨房中可以同时进行多个任务,不同的厨师在不同的地方进行不同的操作,但是每一个厨师都共享着厨房的设备和材料。因此,线程比进程更加轻量级,能够更高效地利用计算机系统资源。同时,线程之间的切换和通信成本也比进程低得多,能够更快速地进行线程切换和同步操作。

使用pthread_create()函数对线程进行创建,pthread_create()函数使用的头文件和函数原型,如下所示:

所需头文件

函数原型

1

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

pthread_create函数的返回值是一个整数,如果调用成功则返回0,否则返回错误代码。调用pthread_create函数时,操作系统会创建一个新的线程,该线程会在start_routine函数中开始执行。pthread_create()函数参数含义如下所示:

参数名称

参数含义

thread

指向pthread_t类型变量的指针,用于存储新线程的标识符

2

attr

指向pthread_attr_t类型变量的指针,用于设置新线程的属性

3

start_routine

新线程的入口函数,是一个指向函数的指针,返回类型为void*,接受一个void*类型的参数

4

arg

作为start_routine函数的参数传递给新线程。

下面对pthread_create()函数进行程序举例,创建一个名为demo46_pthread_create.c的程序,程序内容如下所示:

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

void* print_message(void* str) 
{
    printf("%s\n", (char*) str); // 打印传入的字符串
    return NULL;
}

int main() 
{
    pthread_t thread_id; // 线程ID
    char* message = "你好,线程!"; // 传给新线程的字符串指针
    int result;

    result = pthread_create(&thread_id, NULL, print_message, (void*) message); // 创建新线程并传入参数
    if (result != 0) 
	{
        printf("错误:创建新线程失败。\n"); // 如果创建新线程失败则输出错误信息
        return -1;
    }

    printf("主线程结束。\n"); // 主线程输出信息
    return 0;
}                 

上述代码在16行创建了一个新的线程,并将一个字符串指针作为参数传递给新线程,新线程会将该字符串打印输出。保存退出之后,使用以下命令对demo46_pthread_create.c进行编译,编译完成如下图所示:

gcc -o demo46_pthread_create demo46_pthread_create.c -lpthread

注:在Linux操作系统中,编译器和链接器并不会默认链接所有的库,因为这些库中包含的函数并不是所有程序都需要使用的,所以编译线程相关的库时就需要手动指定libpthread库。

然后使用命令“./demo46_pthread_create”来运行该程序,运行成功如下图所示:
 

从打印结果可以看到只有“主线程结束”相关信息,而子线程中应该打印的“你好,线程!”并没有出来,原来呀,是主线程退出之后会关闭掉当前线程,此时子线程可能还没有执行完毕,所以没有打印信息的机会。那要如何避免这种情况呢?让我们进入下一小节的学习吧。

9.2线程的回收

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\47”目录下,如下图所示:

从上一小节的代码实例中了解到,线程之间的执行顺序是不确定的,所以我们无法保证新创建的线程会在主线程之前结束。因此,为了保证程序的正确性,我们需要等待新线程执行完毕再结束主线程。这时就需要使用到 pthread_join 函数了。pthread_join 函数的作用是等待一个指定线程结束。其函数原型和所需的头文件如下所示:

所需头文件

函数原型

1

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

其中,thread 参数是要等待的线程的线程 ID,retval 参数是用于存储新线程的返回值。

当调用 pthread_join函数时,主线程会一直阻塞,直到被等待的线程执行结束,或者被取消。如果线程已经结束,pthread_join函数会将线程的退出状态存储在retval 所指向的内存中,并返回 0。如果线程被取消,pthread_join函数也会返回相应的错误代码。

下面对pthread_join()函数进行程序举例,创建一个名为demo47_pthread_join.c的程序,程序内容如下所示:

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

void* print_message(void* str) 
{
    printf("%s\n", (char*) str); // 打印传入的字符串
    return NULL;
}

int main() 
{
    pthread_t thread_id; // 线程ID
    char* message = "你好,线程!"; // 传给新线程的字符串指针
    int result;

    result = pthread_create(&thread_id, NULL, print_message, (void*) message); // 创建新线程并传入参数
    if (result != 0) 
	{
        printf("错误:创建新线程失败。\n"); // 如果创建新线程失败则输出错误信息
        return -1;
    }
	
	result = pthread_join(thread_id, NULL);
	if (result != 0) 
	{
		printf("错误:等待子线程结束失败。\n");
		return -1;
	}

    printf("主线程结束。\n"); // 主线程输出信息
    return 0;
}

和上一小节的代码相比,添加了24-29行关于线程回收的代码,保存退出之后,使用以下命令对demo47_pthread_join.c进行编译,编译完成如下图所示:

gcc -o demo47_pthread_join demo47_pthread_join.c -lpthread

然后使用命令“./demo46_pthread_create”来运行该程序,运行成功如下图所示: 

对比打印结果,可以看到子线程的打印信息也出现了。

9.3获取线程ID 

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\48”目录下,如下图所示:

使用pthread_create函数创建一个子线程之后,会将线程ID保存在thread参数中,线程ID的作用与进程ID类似,用来标识不同的线程,通常被用来控制线程的创建、销毁、同步等操作,例如上一小节就是根据线程ID进行线程的回收。

在一个线程中可以使用 pthread_self()函数进行线程ID的获取, pthread_self()函数所需的头文件和函数原型如下所示:

所需头文件

函数原型

1

#include <pthread.h>

pthread_t pthread_self(void);

pthread_self() 函数没有参数,直接调用即可获取当前线程的 ID。在多线程程序中,可以使用 pthread_self() 函数来区分不同的线程,做到线程之间的区分和通信。

下面对pthread_self()函数进行程序举例,创建一个名为demo48_pthread_self.c的程序,程序内容如下所示:

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

void* thread_func(void* arg) 
{
    pthread_t thread_id = pthread_self();
    printf("子线程的 ID 是 %lu\n", thread_id);
    return NULL;
}

int main() 
{
    pthread_t thread_id;
    int ret = pthread_create(&thread_id, NULL, thread_func, NULL);
    if (ret != 0) 
	{
        printf("创建线程失败\n");
        return -1;
    }

    pthread_t main_thread_id = pthread_self();
    printf("主线程的 ID 是 %lu\n", main_thread_id);

    pthread_join(thread_id, NULL);
    printf("主线程结束\n");
    return 0;
}

保存退出之后,使用以下命令对self demo48_pthread_self.c进行编译,编译完成如下图所示:

gcc -o demo48_pthread_self demo48_pthread_self.c -lpthread

然后使用命令“./demo46_pthread_create”来运行该程序,运行成功如下图所示: 

主线程和子线程的ID就打印了出来。

9.4线程的终止

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\49”目录下,如下图所示:.

pthread_exit()函数用于终止调用它的线程。在调用该函数后,线程不再继续执行,它的线程控制块和线程栈所占用的资源将被系统回收。pthread_exit()函数所需头文件和函数原型如下所示:

所需头文件

函数原型

1

#include <pthread.h>

void pthread_exit(void *retval);

pthread_exit() 函数有两种调用方式:

(1)不带参数:调用 pthread_exit() 函数将结束当前线程的执行,没有返回值。

(2)带参数:调用 pthread_exit(void *value_ptr) 函数将结束当前线程的执行,并将一个指向 void 类型的值传递给它的创建者。

在使用 pthread_exit() 函数时,需要注意以下几点:

(1)线程在调用 pthread_exit() 函数之前必须完成它的工作,否则会造成资源泄漏和数据不一致等问题。

(2)当一个线程调用 pthread_exit() 函数时,其它线程不会受到影响。

(3)调用 pthread_exit() 函数并不会自动关闭线程中打开的文件和释放线程中分配的内存,需要手动进行关闭和释放。

(4)在使用 pthread_exit() 函数时,需要注意它与 exit() 函数的区别。exit() 函数会终止整个进程的执行,包括所有的线程,而 pthread_exit() 函数仅终止当前线程的执行。

下面对pthread_exit()函数进行程序举例,创建一个名为demo49_pthread_exit.c的程序,程序内容如下所示:

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

void* thread_func(void* arg) 
{
    printf("子线程开始执行\n");
    sleep(3);
    printf("子线程执行完毕\n");
    return NULL;
}

int main() 
{
    pthread_t tid;
    int ret;

    ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) 
	{
        printf("创建线程失败\n");
        exit(EXIT_FAILURE);
    }

    printf("主线程即将退出\n");
    pthread_exit(NULL); // 主线程退出
}

保存退出之后,使用以下命令对demo49_pthread_exit.c进行编译,编译完成如下图所示:

gcc -o demo49_pthread_exit demo49_pthread_exit.c -lpthread

然后使用命令“./demo49_pthread_exit”来运行该程序,运行成功如下图所示: 

与9.1小节使用exit()函数退出整个进程的结果不同,主线程退出之后,整个进程并不会退出,而是等待子线程运行完毕。

9.5取消线程

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\50”目录下,如下图所示:

pthread_cancel()函数用于取消正在运行的线程的函数。线程可以被另一个线程通过调用pthread_cancel()函数来取消。当一个线程被取消时,它会在适当的时候终止。这个过程中,线程的状态将被设置为已取消,但是线程并不会立即终止。

pthread_cancel()函数的函数原型和所需头文件如下所示:

所需头文件

函数原型

1

#include <pthread.h>

int pthread_cancel(pthread_t thread);

其中,thread参数是要取消的线程的ID。当pthread_cancel()函数被调用时,被取消的线程将被发送一个取消请求。如果线程已经在等待取消请求,则取消请求会立即生效,否则线程会在取消请求到达时终止执行。被取消的线程会执行一个清理处理程序,这个清理处理程序是由线程自己来注册的。

需要注意的是,pthread_cancel()函数只是发送一个取消请求,实际上并不能保证线程一定能被取消。如果线程忽略了取消请求,那么线程就不能被取消。因此,在使用pthread_cancel()函数时,需要考虑线程是否能够正确地响应取消请求,并且需要在线程中正确地处理取消请求。

下面对pthread_cancel()函数进行程序举例,创建一个名为demo50_pthread_cancel.c的程序,程序内容如下所示:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

void* thread_function(void* arg) 

{

    int i;

    printf("子线程开始运行\n");

    for (i = 1; i <= 5; i++) 

    {

        printf("子线程打印: %d\n", i);

        sleep(1);

    }

    printf("子线程结束\n");

    return NULL;

}

int main() 

{

    pthread_t thread;

    int res;

    

    // 创建子线程

    res = pthread_create(&thread, NULL, thread_function, NULL);

    if (res != 0) 

    {

        perror("Thread creation failed");

        exit(EXIT_FAILURE);

    }

    // 等待2秒钟

    sleep(2);

    

    // 取消子线程

    if (pthread_cancel(thread) != 0) 

    {

        perror("Thread cancellation failed");

        exit(EXIT_FAILURE);

    }

    printf("主线程结束\n");

    return 0;

}

保存退出之后,使用以下命令对demo50_pthread_cancel.c进行编译,编译完成如下图所示:

gcc -o demo50_pthread_cancel demo50_pthread_cancel.c  -lpthread

然后使用命令“./demo50_pthread_cancel”来运行该程序,运行成功如下图所示: 

正常情况下,子线程会在打印五次计数值之后退出,而在运行两次之后,主线程通过pthread_cancel()函数取消了子线程的运行,所以只会有两次打印。

9.6分离线程

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\51”目录下,如下图所示:

pthread_detach函数用于将一个线程设置为可分离状态。可分离状态的线程在终止时自动释放其占用的资源,而无需主线程显式地等待它们结束。pthread_detach函数所需头文件及其函数原型如下所示:

所需头文件

函数原型

1

#include <pthread.h>

int pthread_detach(pthread_t thread);

其中,thread参数指定要设置为可分离状态的线程的ID。调用pthread_detach函数将一个线程设置为可分离状态后,该线程在终止时会自动释放它占用的资源,而不需要主线程调用pthread_join函数等待它结束。如果主线程调用pthread_join函数等待一个已经被设置为可分离状态的线程结束,pthread_join函数将立即返回并返回EINVAL错误码。

如果一个线程已经处于分离状态,则调用pthread_detach函数将不会产生任何效果,函数返回值为0。如果调用pthread_detach函数失败,函数将返回一个非0值,表示错误代码。

下面对pthread_detach()函数进行程序举例,创建一个名为demo51_pthread_detach.c的程序,程序内容如下所示:

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

// 子线程执行的函数
void* thread_func(void* arg)
{
    int i;
    for (i = 0; i < 5; i++) 
	{
        printf("子线程打印: %d\n", i);
        sleep(1);
    }
    printf("子线程结束\n");
    pthread_exit(NULL);
}

int main()
{
    pthread_t thread_id; // 子线程的ID
    int result;

    // 创建子线程并运行
    result = pthread_create(&thread_id, NULL, thread_func, NULL);
    if (result != 0) 
	{
        printf("错误:pthread_create 失败。\n");
        exit(EXIT_FAILURE);
    }

    // 分离子线程,让它运行在后台
    result = pthread_detach(thread_id);
    if (result != 0) 
	{
        printf("错误:pthread_detach 失败。\n");
        exit(EXIT_FAILURE);
    }

    printf("主线程正在退出。\n");
    pthread_exit(NULL);
}

保存退出之后,使用以下命令对demo51_pthread_detach.c进行编译,编译完成如下图所示:

gcc -o demo51_pthread_detach demo51_pthread_detach.c  -lpthread

然后使用命令“./demo51_pthread_detach”来运行该程序,运行成功如下图所示: 

主线程退出之后,由于线程分离的原因并不会影响子线程的运行,所以子线程可以正常打印五次数值。

9.7线程同步

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\52”目录下,如下图所示:

主线程退出之后,由于线程分离的原因并不会影响子线程的运行,所以子线程可以正常打印五次数值。

9.7线程同步

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\52”目录下,如下图所示:

每一个服务进程的运行,都包含若干进程(Thread),线程是调度的基本单位,进程则是资源拥有的基本单位。

线程的主要优势在于,资源的共享性,譬如通过全局变量来实现信息共享线程,不过这种便捷的共享是有代价的,进程中的多个线程间是并发执行的,每个线程都是系统调用的基本单元,参与到系统调度队列中;对于多个线程间的共享资源,并发执行会导致对共享资源的并发访问,并发访问所带来的问题就是竞争(如果多个线程同时对共享资源进行访问就表示存在竞争。要防止并发访问共享资源,那么就需要对共享资源的访问进行保护,防止出现并发访问共享资源。

Linux 系统提供了多种用于实现线程同步的机制,下面对常见的线程同步进行介绍:

同步方法

介绍

互斥锁

使用锁来限制同一时刻只有一个线程访问共享资源。

条件变量

用于在一个线程等待另一个线程满足特定条件时阻塞该线程。

信号量

用于控制对共享资源的访问,限制访问共享资源的线程数量,或者在访问共享资源前进行资源分配。

读写锁

用于在多个线程同时访问共享资源时实现更高效的读写操作。

下面我们编写一个简单的实验对线程间的资源共享进行测试,来证明线程同步的必要性。

首先创建一个名为demo52_pthread_test文件,向该文件写入以下内容,如下所示:

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

#define THREAD_NUM 2  // 线程数量

int count = 0;  // 全局变量,计数器

void* thread_func(void* arg)
{
    int i;
    for (i = 0; i < 100000; i++) 
    {
        count++;  // 计数器加1
    }
    pthread_exit(NULL);
}

int main()
{
    pthread_t thread_id[THREAD_NUM];  // 定义线程ID数组
    int result, i;

    for (i = 0; i < THREAD_NUM; i++) 
    {
        result = pthread_create(&thread_id[i], NULL, thread_func, NULL);  // 创建线程
        if (result != 0) {
            printf("Error: pthread_create failed.\n");
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < THREAD_NUM; i++) 
    {
        result = pthread_join(thread_id[i], NULL);  // 等待线程结束
        if (result != 0) 
        {
            printf("Error: pthread_join failed.\n");
            exit(EXIT_FAILURE);
        }
    }

    printf("The final count is: %d\n", count);  // 输出计数器最终的值

    pthread_exit(NULL);  // 退出主线程
}

这段代码展示了一个非常简单的多线程程序,主要功能是创建两个线程来对全局变量count进行累加操作,最后输出计数器count的最终值,由于全局变量count被多个线程同时访问和修改,因此在多线程环境下存在竞争条件,可能会导致结果不正确。在这个例子中,计数器的初始值是0,预期的最终值应该是200000,但由于多个线程同时访问计数器,存在竞争条件,导致最终值可能小于预期值。

保存退出之后,使用以下命令对demo52_pthread_test.c进行编译,编译完成如下图所示:

gcc -o demo52_pthread_test demo52_pthread_test.c -lpthread

然后使用命令“./demo52_pthread_test”来进行测试,可以看到最终的计数值为107886,与理论值的200000不符,证明共享资源的并发访问出现数据不一致的问题。 

在后续的实验中,会加入互斥锁等线程同步函数,对上述实验进行改进。

9.8互斥锁

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\53”目录下,如下图所示:

互斥锁是一种常见的线程同步机制,用于保护共享资源的访问,使得同一时刻只有一个线程能够访问该资源。在多线程编程中,由于多个线程同时访问共享资源可能会导致数据竞争和并发错误,因此必须采用同步机制来确保线程安全。

互斥锁的基本思想是在访问共享资源前获取锁,访问结束后释放锁。在获取锁的期间,其他线程将被阻塞,直到当前线程释放锁为止。由于只有一个线程能够持有锁,因此可以保证同一时刻只有一个线程访问共享资源,从而避免了数据竞争和并发错误。

互斥锁的使用步骤及所使用的函数如下表所示:

步骤

描述

所使用的函数

1

定义互斥锁变量

pthread_mutex_t mutex;

2

初始化互斥锁

pthread_mutex_init()

3

获取互斥锁,如果锁已被占用则线程阻塞等待

pthread_mutex_lock()

4

对共享资源进行操作

访问共享资源

5

释放互斥锁

pthread_mutex_unlock()

6

销毁互斥锁

pthread_mutex_destroy()

接下来对上述每个步骤及其对应的函数进行详细解释:

  1. 定义互斥锁变量

pthread_mutex_t是POSIX线程库中用来实现互斥锁的数据类型。它是一个结构体类型,用于表示一个互斥锁对象。

  1. 初始化互斥锁

使用pthread_mutex_init()函数进行互斥锁的初始化,pthread_mutex_init()函数所需头文件和函数原型如下所示:

所需头文件

函数原型

1 

2

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,  const pthread_mutexattr_t *restrict attr);

函数成功初始化互斥锁后返回 0,否则返回一个非零错误码。一般情况下,我们可以在程序开始时调用该函数来初始化互斥锁。

pthread_mutex_init()函数的2个参数含义如下所示:

参数名称

参数含义

mutex

指向互斥锁对象的指针

2

attr

指向互斥锁属性对象的指针,可传入 NULL,表示使用默认属性

(3)获取互斥锁

使用pthread_mutex_lock()函数和pthread_mutex_trylock()函数用于获取一个互斥锁。两个函数所需头文件和函数原型如下所示:

所需头文件

函数原型

1 

2

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

函数的参数 mutex 是一个指向互斥锁的指针,它指向要获取的互斥锁。

当一个线程调用 pthread_mutex_lock() 函数和pthread_mutex_trylock()函数时,如果该锁当前没有被占用,则该线程会立即获得锁,并且该锁的状态会被设置为“被占用”。两个函数的不同之处在于当锁被其他线程持有时的行为不同。 pthread_mutex_lock() 函数会一直等待,直到获得锁为止。如果锁已经被其他线程持有,则当前线程会被阻塞,直到锁被释放。而pthread_mutex_trylock() 函数则是尝试获取锁,如果锁已经被其他线程持有,函数将立即返回,并返回错误码 EBUSY。如果锁没有被其他线程持有,则当前线程会获取锁,并返回0。

(4)释放互斥锁

使用pthread_mutex_unlock()函数进行互斥锁解锁(也可以称为释放互斥锁),从而使得其他线程可以获取该互斥锁并访问共享资源。pthread_mutex_unlock函数所需头文件和函数原型如下所示:

所需头文件

函数原型

1 

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

该函数需要传入一个指向互斥锁变量的指针作为参数,以释放该互斥锁。如果成功释放互斥锁,则返回值为 0,否则返回一个非零的错误码。

(5)销毁互斥锁

互斥锁在使用完毕后,需要进行销毁操作,以释放所占用的资源,避免资源泄漏。互斥锁的销毁可以使用pthread_mutex_destroy()函数进行操作,pthread_mutex_destroy()函数所需头文件和函数原型如下所示:

所需头文件

函数原型

1 

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

该函数用于销毁已经初始化的互斥锁,参数mutex为指向需要销毁的互斥锁的指针。函数执行成功时返回0,否则返回一个非零错误码。

需要注意的是,销毁互斥锁前必须保证已经解锁,否则将会返回EBUSY错误码。销毁互斥锁时,不管锁是否被锁住,都应该调用pthread_mutex_destroy函数进行销毁。

至此,关于互斥锁使用相关的API就讲解完成了,下面以上一小节编写的线程同步测试代码为基础加入互斥锁。

 首先创建一个名为demo53_pthread_mutex.c文件,向该文件写入以下内容,如下所示:

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

#define THREAD_NUM 2  // 线程数量

int count = 0;  // 全局变量,计数器
pthread_mutex_t mutex;  // 定义互斥锁变量

void* thread_func(void* arg)
{
    int i;
    for (i = 0; i < 100000; i++)
    {
        pthread_mutex_lock(&mutex);  // 加锁
        count++;  // 计数器加1
        pthread_mutex_unlock(&mutex);  // 解锁
    }
    pthread_exit(NULL);
}

int main()
{
    pthread_t thread_id[THREAD_NUM];  // 定义线程ID数组
    int result, i;

    pthread_mutex_init(&mutex, NULL);  // 初始化互斥锁

    for (i = 0; i < THREAD_NUM; i++)
    {
        result = pthread_create(&thread_id[i], NULL, thread_func, NULL);  // 创建线程
        if (result != 0) {
            printf("Error: pthread_create failed.\n");
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < THREAD_NUM; i++)
    {
        result = pthread_join(thread_id[i], NULL);  // 等待线程结束
        if (result != 0)
        {
            printf("Error: pthread_join failed.\n");
            exit(EXIT_FAILURE);
        }
    }

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

    printf("The final count is: %d\n", count);  // 输出计数器最终的值

    pthread_exit(NULL);  // 退出主线程
}

保存退出之后,使用以下命令对 demo53_pthread_mutex.c进行编译,编译完成如下图所示:

gcc -o demo53_pthread_mutex demo53_pthread_mutex.c -lpthread

然后使用命令“./demo53_pthread_mutex”来进行测试,可以看到最终的计数值为200000,与理论值相同,如下图所示: 

至此,关于进程同步-互斥锁相关的讲解和测试就完成了,关于其他进程同步的方法等后续有需求之后再进行讲解。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值