Linux应用:线程基础

线程介绍

进程是程序在操作系统里的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件描述符等。

POSIX 线程库(pthread)

在 C 语言中,通常使用 POSIX 线程库(pthread)来创建和管理线程。该库提供了一系列函数来操作线程,例如创建线程、等待线程结束、设置线程属性等。使用 pthread 库需要包含头文件 <pthread.h>,并且在编译时需要链接 -lpthread 库。

基本的线程操作函数

创建线程: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:指向 pthread_attr_t 类型的指针,用于设置线程的属性。若为 NULL,则使用默认属性。
start_routine:指向线程执行函数的指针,该函数的返回值类型为 void *,参数类型为 void *。
arg:传递给线程执行函数的参数。

等待线程结束:pthread_join

#include <pthread.h>

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

thread:要等待的线程的线程 ID。
retval:指向 void * 类型的指针,用于存储线程执行函数的返回值。

线程退出:pthread_exit

#include <pthread.h>

void pthread_exit(void *retval);

retval:线程的返回值。

简单的 C 语言线程示例

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

// 线程执行函数
void *thread_function(void *arg) {
    int *num = (int *)arg;
    printf("线程正在运行,接收到的参数是: %d\n", *num);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread_id;
    int number = 10;

    // 创建线程
    if (pthread_create(&thread_id, NULL, thread_function, &number) != 0) {
        perror("线程创建失败");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread_id, NULL) != 0) {
        perror("等待线程结束失败");
        return 1;
    }

    printf("主线程继续执行\n");
    return 0;
}

编译命令:

gcc fs.cpp -lpthread

在这里插入图片描述

线程取消

pthread_cancel 函数

用于向指定线程发送取消请求,通常由主线程调用以取消子线程的执行。

#include <pthread.h>

int pthread_cancel(pthread_t thread);

thread:要取消的线程的线程 ID。
返回值:若成功,返回 0;若出错,返回错误码。

pthread_setcancelstate 函数

用于设置线程的取消状态,即线程是否允许被取消

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);

state:新的取消状态,有两个可选值:
PTHREAD_CANCEL_ENABLE:允许线程被取消(默认状态)。
PTHREAD_CANCEL_DISABLE:禁止线程被取消。
oldstate:指向整数的指针,用于存储线程原来的取消状态。若不需要保存原来的状态,可传入 NULL。
返回值:若成功,返回 0;若出错,返回错误码。

pthread_setcanceltype 函数

用于设置线程的取消类型,即线程在接收到取消请求后何时响应

#include <pthread.h>

int pthread_setcanceltype(int type, int *oldtype);

type:新的取消类型,有两个可选值:
PTHREAD_CANCEL_DEFERRED:延迟取消,线程在接收到取消请求后,会在执行到取消点(如 pthread_testcancel、sleep、read、write 等函数)时才响应取消请求。
PTHREAD_CANCEL_ASYNCHRONOUS:异步取消,线程在接收到取消请求后,会立即响应取消请求。
oldtype:指向整数的指针,用于存储线程原来的取消类型。若不需要保存原来的类型,可传入 NULL。
返回值:若成功,返回 0;若出错,返回错误码。

代码示例

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

// 子线程函数
void *thread_function(void *arg) {
    int oldstate, oldtype;

    // 设置线程允许被取消
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
    // 设置线程为延迟取消类型
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

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

    // 模拟一些工作
    for (int i = 0; i < 10; i++) {
        printf("子线程正在工作: %d\n", i);
        sleep(1);
        // 检查取消点
        pthread_testcancel();
    }

    printf("子线程执行完毕\n");
    return NULL;
}

int main() {
    pthread_t thread_id;

    // 创建子线程
    if (pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
        perror("线程创建失败");
        return 1;
    }

    // 主线程休眠3秒
    sleep(3);

    // 向子线程发送取消请求
    if (pthread_cancel(thread_id) != 0) {
        perror("取消线程失败");
        return 1;
    }

    printf("主线程已发送取消请求\n");

    // 等待子线程结束
    if (pthread_join(thread_id, NULL) != 0) {
        perror("等待线程结束失败");
        return 1;
    }

    printf("主线程继续执行\n");
    return 0;
}

在这里插入图片描述

异步取消

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

// 子线程函数
void *thread_function(void *arg) {
    int oldstate, oldtype;

    // 设置线程允许被取消
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
    // 设置线程为异步取消类型
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);

    printf("子线程开始执行\n");
  
    int sum = 0;
    // 模拟长时间运行的任务
    while (1) {
        printf("子线程正在执行 sum = %d\n", sum);
        fflush(stdout); // 刷新输出缓冲区
        usleep(1000);
        sum += 10;
    }

    // 以下代码不会被执行到,因为线程会在收到取消请求时立即结束
    printf("子线程执行完毕\n");
    return NULL;
}

int main() {
    pthread_t thread_id;

    // 创建子线程
    if (pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
        perror("线程创建失败");
        return 1;
    }

    // 主线程休眠2秒,让子线程有时间开始执行
    sleep(2);

    // 向子线程发送取消请求
    if (pthread_cancel(thread_id) != 0) {
        perror("取消线程失败");
        return 1;
    }

    printf("主线程已发送取消请求\n");

    // 等待子线程结束
    if (pthread_join(thread_id, NULL) != 0) {
        perror("等待线程结束失败");
        return 1;
    }

    printf("主线程继续执行\n");

    return 0;
}

实际上没有判断处理是哪里异步取消!!!

在这里插入图片描述

线程清理

pthread_cleanup_push

#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);

功能:将一个清理处理程序压入线程的清理处理程序栈。当线程通过 pthread_exit 终止、被 pthread_cancel 取消或者执行 pthread_cleanup_pop 且参数为非零值时,会调用该清理处理程序。
参数:
routine:指向清理处理程序的函数指针,该函数接受一个 void * 类型的参数,并且没有返回值。
arg:传递给清理处理程序的参数。

pthread_cleanup_pop

#include <pthread.h>

void pthread_cleanup_pop(int execute);

功能:从线程的清理处理程序栈中弹出一个清理处理程序。如果 execute 参数为非零值,则会调用该清理处理程序;如果为零,则不会调用该清理处理程序。
参数:
execute:一个整数,用于决定是否执行清理处理程序。非零值表示执行,零表示不执行。

代码示例

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

// 清理处理程序
void cleanup_handler(void *arg) {
    printf("清理处理程序被调用,参数为: %s\n", (const char *)arg);
}

// 子线程函数
void *thread_function(void *arg) {
    const char *message = (const char *)arg;

    // 压入清理处理程序
    pthread_cleanup_push(cleanup_handler, (void *)message);

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

    // 模拟长时间运行的任务
    for (int i = 0; i < 5; i++) {
        printf("子线程正在工作: %d\n", i);
        sleep(1);
    }

    // 弹出清理处理程序,并执行它
    pthread_cleanup_pop(1);

    printf("子线程执行完毕\n");
    return NULL;
}

int main() {
    pthread_t thread_id;
    const char *message = "Hello, Cleanup!";

    // 创建子线程
    if (pthread_create(&thread_id, NULL, thread_function, (void *)message) != 0) {
        perror("线程创建失败");
        return 1;
    }

    // 等待子线程结束
    if (pthread_join(thread_id, NULL) != 0) {
        perror("等待线程结束失败");
        return 1;
    }

    printf("主线程继续执行\n");
    return 0;
}

主要是保证即便子线程被取消了,该进行的操作还是可以顺利完成
在这里插入图片描述

pthread_detach

#include <pthread.h>
int pthread_detach(pthread_t thread);

参数:
thread:要分离的线程的线程 ID。
返回值:若成功,返回 0;若出错,返回错误码。

pthread_self

#include <pthread.h>

pthread_t pthread_self(void);

参数:该函数没有参数。
返回值:返回调用该函数的线程的线程 ID,类型为 pthread_t。

代码演示

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

// 子线程函数
void *thread_function(void *arg) {
    // 获取当前线程的线程 ID
    pthread_t tid = pthread_self();
    printf("子线程(线程 ID: %lu)开始执行\n", (unsigned long)tid);

    // 模拟子线程工作
    sleep(3);

    printf("子线程(线程 ID: %lu)执行完毕\n", (unsigned long)tid);
    return NULL;
}

int main() {
    pthread_t thread_id;

    // 创建子线程
    if (pthread_create(&thread_id, NULL, thread_function, NULL) != 0) {
        perror("线程创建失败");
        return 1;
    }

    // 获取主线程的线程 ID
    pthread_t main_tid = pthread_self();
    printf("主线程(线程 ID: %lu)创建了子线程\n", (unsigned long)main_tid);

    // 分离子线程
    if (pthread_detach(thread_id) != 0) {
        perror("分离线程失败");
        return 1;
    }
    printf("主线程(线程 ID: %lu)已分离子线程\n", (unsigned long)main_tid);

    // 主线程继续执行其他任务
    printf("主线程(线程 ID: %lu)继续执行其他任务\n", (unsigned long)main_tid);
    sleep(5);

    printf("主线程(线程 ID: %lu)结束\n", (unsigned long)main_tid);
    return 0;
}

子线程函数 thread_function:
调用 pthread_self 获取当前子线程的线程 ID 并打印。
模拟子线程执行 3 秒的任务。
任务完成后,再次打印线程 ID 表示执行完毕。
主线程:
创建子线程。
调用 pthread_self 获取主线程的线程 ID 并打印。
调用 pthread_detach 分离子线程。
主线程继续执行其他任务,模拟执行 5 秒。
最后打印主线程结束信息。

在这里插入图片描述
分离后的子线程会自己回收自己需要销毁的资源

进程和线程的区别

定义

进程:是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的地址空间、内存、数据栈以及其他用于维护进程运行的资源。
线程:是进程中的一个执行单元,是程序执行的最小单位。线程共享所属进程的资源,如地址空间、打开的文件等,但有自己独立的栈空间和程序计数器等。

资源分配

进程:拥有独立的资源,包括内存空间、文件描述符、信号量等。不同进程之间的资源相互隔离,一个进程的资源不会被另一个进程直接访问,以保证进程的独立性和稳定性。
线程:共享所属进程的资源,多个线程可以访问进程中的同一变量、文件等资源。这使得线程之间的通信相对容易,但也需要注意资源访问的同步和互斥问题,以避免数据冲突和不一致性。

调度

进程:作为系统调度的基本单位,进程的调度由操作系统的调度算法决定。进程的切换需要保存和恢复大量的上下文信息,包括寄存器值、内存管理信息等,因此进程切换的开销较大。
线程:是程序执行的最小单位,也是操作系统进行调度的基本单位。由于线程共享进程的资源,线程切换时只需保存和恢复少量的上下文信息,如寄存器值和栈指针等,因此线程切换的开销较小,能更高效地进行并发执行。

并发性

进程:多个进程可以并发执行,操作系统通过调度算法在不同的进程之间切换 CPU 时间片,实现多个进程的并发运行。但进程间的并发粒度较大,进程之间的切换相对较慢。
线程:一个进程中的多个线程可以并发执行,它们可以同时访问进程的资源,并且可以在不同的 CPU 核心上同时运行,实现真正的并行。线程间的并发粒度较小,线程的创建、销毁和切换速度较快,能更细粒度地控制程序的执行流程,提高程序的并发性能。

独立性

进程:具有较高的独立性,一个进程的崩溃通常不会影响到其他进程(除了一些特殊情况,如共享内存导致的问题)。每个进程都有自己独立的地址空间和资源,相互之间的影响较小。
线程:线程的独立性相对较低,因为它们共享所属进程的资源。如果一个线程出现错误,如访问了非法的内存地址或导致进程崩溃,可能会影响到整个进程以及其他线程的运行。

通信

进程:进程间通信(IPC)需要通过专门的机制,如管道、消息队列、信号量、共享内存等。这些机制需要操作系统的支持,并且涉及到数据在不同进程地址空间之间的传递,相对较为复杂。
线程:由于线程共享进程的地址空间,线程之间的通信可以直接通过共享变量来实现,通信方式较为简单和高效。但需要注意在多线程环境下对共享变量的访问控制,以确保数据的一致性和正确性。

适用场景

进程:适用于需要高度独立性和稳定性的任务,如服务器程序中的多个服务进程,每个进程可以独立处理不同类型的请求,互不干扰。另外,在一些对资源隔离要求较高的场景,如不同用户的任务处理,也适合使用进程。
线程:适用于需要频繁进行并发操作且对资源共享要求较高的场景,如图形界面应用程序中,一个线程用于处理用户界面的更新,另一个线程用于执行后台任务,如文件下载或数据处理,通过线程间的协作可以提高程序的响应性和用户体验。同时,在一些高性能计算和并发处理的场景中,线程也能发挥其高效并发的优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

li星野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值