Linux系统编程-线程与多线程模块的封装

目录

[一. 线程](#一. 线程)

[1.1 线程概念](#1.1 线程概念)

[1.2 线程与进程的区别](#1.2 线程与进程的区别)

[二. 线程相关函数接口](#二. 线程相关函数接口)

[2.1 pthread_create](#2.1 pthread_create)

[2.2 pthread_exit](#2.2 pthread_exit)

[2.3 pthread_join](#2.3 pthread_join)

2.3.1对比记忆

[2.3.2 注意](#2.3.2 注意)

[2.3.3 示例代码](#2.3.3 示例代码)

[2.4 pthread_detach](#2.4 pthread_detach)

[三. 线程的互斥](#三. 线程的互斥)

[3.1 线程的互斥机制](#3.1 线程的互斥机制)

[3.2 保护临界资源-互斥锁](#3.2 保护临界资源-互斥锁)

[3.2.1 pthread_mutex_init](#3.2.1 pthread_mutex_init)

[3.2.2 pthread_mutex_lock/trylock](#3.2.2 pthread_mutex_lock/trylock)

[3.2.4 pthread_mutex_unlock(*mutex)](#3.2.4 pthread_mutex_unlock(*mutex))

[3.2.5 pthread_mutex_destroy(*mutex)](#3.2.5 pthread_mutex_destroy(*mutex))

[四. 多线程模块的封装](#四. 多线程模块的封装)

一. 线程

1.1 线程概念

**线程:**线程是轻量级进程(LWP)

1.2 线程与进程的区别

|-----------------------------|--------------------------|
| 进程 | 线程 |
| 正在执行的程序 | 轻量级进程 |
| 操作系统资源分配的最小单位 | 操作系统任务调度的最小单位 |
| 资源空间消耗大,0~4G虚拟内存地址空间 | 资源空间消耗小,栈区独立,其他空间共享 |
| 进程的效率低 | 线程效率高 |
| 进程安全性高 | 线程安全性低 |
| 进程间通信复杂,需要用到IPC机制 | 线程间通信方便,由于共享数据区,使用全局变量即可 |
| 在相同资源的平台下,多进程的并发量要少于多线程的并发量 | |

二. 线程相关函数接口

2.1 pthread_create

**作用:**会在调用进程内启动一个新的线程

参数:*thread:保存新线程的id

*attr:线程属性

*start_routine:回调函数,线程的执行函数

*arg:给回调函数的参数

**返回值:**成功返回0 失败返回一个错误号

2.2 pthread_exit

作用: pthread_exit() 函数会终止调用该函数的线程,并通过retval参数返回一个值;而这个值则

可供同一进程 中调用 pthread_join() 的其他线程使用。

当一个线程终止时,进程共享资源(例如互斥锁、条件变量、信号量和文件描述符)不会被

释放

当进程中的最后一个线程终止后,该进程便会像调用**exit()**并设置退出状态为零那样结束;

因此,进程共享资源会被释放。

参数:*retval:退出值

**返回值:**无

2.3 pthread_join

作用:阻塞等待线程退出

pthread_join() 函数会等待由 thread 参数指定的线程完成其执行过程。如果该线程已经结

束执行,那么 pthread_join() 将立即返回。这个由 thread指定的线程必须是非分离状态

如果 retval 不为 NULL,则 pthread_join() 会将目标线程的退出状态复制到由 retval 所指向

的位置。如果目标线程被取消(pthread_cancel ),则会在由 retval 所指向的位置放置 **PTHREAD_CANCELED (-1)**标志。

**参数:**thread:指定线程 **retval:保存线程退出状态

**返回值:**成功返回0 失败返回一个错误号

2.3.1对比记忆

线程与进程在接收返回值时的对比:

|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 进程 | 线程 |
| 进程在退出时可使用:return 0;return -1;exit(1); exit(0);_exit(0);_Exit(0)等 可以看到进程的退出状态都是整型 | 线程的退出可使用:return NULL;pthread_exit(NULL)等 可以看到线程的退出状态都是指针类型,另外,在pthread_create 创建线程时,那个回调函数的返回类型就是void* 类型,这就规定了线程的执行逻辑的返回值类型是void*指针类型 |
| 在获取进程的退出状态时,使用函数:wait 或者waitpid ,其中有一个参数int *wstatus 来保存进程的退出状态,然后使用各种宏来获取进程的退出值等 由于进程的退出状态是int,故用int*来接收 | 获取线程的退出状态时,使用pthread_join,第二个参数 void **retval就是用来保存线程的退出状态的 由于线程的退出状态是void*,故使用void**来接收 |

2.3.2 注意

**注意:**使用pthread_exit不可返回栈区空间的地址,因为线程结束,栈区空间释放,会出现段错误。

以下指针可以通过函数返回:全局变量的地址

static修饰的局部变量的地址

堆区申请的未被释放的空间的地址

字符串常量的地址

通过函数传参传过来的地址

2.3.3 示例代码

cpp 复制代码
int num_g = 66;
void *th_task1(void *arg)
{
    int cnt = 5;
    while(cnt--){
        printf("---thread 1 :%ld,pid: %d\n",pthread_self(),getpid());
        sleep(1);
    }
    //return &num_g;
    return (void*)666;
}
void *th_task2(void *arg)
{
    while(1){
        printf("---thread 2 :%ld, pid : %d\n",pthread_self(), getpid());
        sleep(1);
    }
    return NULL;
}
int main()
{
    int ret;
    void *retval;
    pthread_t tid1, tid2;
    ret = pthread_create(&tid1, NULL,th_task1,NULL);
    if(ret != 0){
        fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
        exit(1);
    }
    ret = pthread_create(&tid2, NULL,th_task2,NULL);
    if(ret != 0){
        fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
        exit(1);
    }
        printf("---thread main :%ld, pid : %d\n",pthread_self(), getpid());
        printf("main:%ld,%ld\n",tid1,tid2);
    pthread_join(tid1, &retval);
    //printf("---pthread join finish:%ld,retval is %d\n",tid1,*(int*)retval);
    printf("---pthread join finish:%ld,retval is %ld\n",tid1,(long)retval);
    /这种情况下最好将接收的状态值强转为long型,因为64位系统下long与指针都是8位
    pthread_join(tid2, NULL);
    return 0;
}

结果:

2.4 pthread_detach

**作用:**pthread_detach() 函数将由 thread 标识的线程标记为已分离状态。当一个已分离的线程终止时,其资源会自动返回给系统,无需其他线程与已终止的线程进行连接。试图分离一个已经分离的线程会导致未定义的行为。

**参数:**thread指定的线程

**返回值:**成功返回0 失败返回一个错误号

**分离线程:**不需要回收的线程,当线程结束时可以被操作系统自动回收其资源,类似孤儿进程。

**非分离线程:**可以被其他回收或者结束的线程,默认属性为非分离线程

示例代码:

cpp 复制代码
void *th_task1(void *arg)
{
    return NULL;
}
int main()
{
    pthread_t tid;
    int num = 0;
    while(1){
        num++;
        printf("num is %d\n",num);
        pthread_create(&tid, NULL,th_task1,NULL);
        pthread_detach(tid);
    }
    return 0;
}

三. 线程的互斥

3.1 线程的互斥机制

多线程在访问临界资源时存在资源竞争!

临界资源: 多个线程可以同时访问的资源,如全局变量,共享内存等

**线程互斥机制:**让多个线程在访问临界资源时,具有排他访问的特性

3.2 保护临界资源-互斥锁

互斥锁:

使用流程:创建锁->初始化锁->加锁->访问临界资源->解锁->销毁锁

3.2.1 pthread_mutex_init

功能: 初始化互斥锁

参数: 互斥锁对象

锁的属性

**返回值:**成功返回0 失败返回错误码

3.2.2 pthread_mutex_lock/trylock

功能: pthread_mutex_lock:以阻塞方式等待锁

pthread_mutex_trylock:以非阻塞方式等待锁

**返回值:**成功返回0 失败返回错误号

3.2.4 pthread_mutex_unlock(*mutex)

功能:解锁

**返回值:**成功返回0 失败返回错误号

3.2.5 pthread_mutex_destroy(*mutex)

**功能:**销毁锁

示例代码:模拟atm取钱程序:

cpp 复制代码
pthread_mutex_t mutex;
int atm = 3;
void * pfun(void *arg)
{
    while(1){
        //访问atm公共资源,先加锁
        pthread_mutex_lock(&mutex);
        if(atm > 0){
            printf("atm = %d\n",atm);
            atm--;
            printf("--[%ld]:draw money---\n",pthread_self());
            pthread_mutex_unlock(&mutex);//解锁后,取钱
            sleep(rand() % 3 + 1);
            pthread_mutex_lock(&mutex);//加锁,再访问atm
            atm++;
            pthread_mutex_unlock(&mutex);//解锁
            return NULL;
        }
        else{
            pthread_mutex_unlock(&mutex);
        }
    }
}
int main()
{
    pthread_t tid[10];
    int ret;
    int i = 0;

    srand(time(NULL));
    pthread_mutex_init(&mutex, NULL);
    //10个线程去atm取钱
    for(i = 0; i < 10; i++){
        ret = pthread_create(&tid[i], NULL, pfun, NULL);
        if(ret != 0){
            fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
            exit(1);
        }
    }
    for(i = 0; i < 10; i++){
        ret = pthread_join(tid[i], NULL);
        if(ret != 0){
            fprintf(stderr,"pthread_join error:%s\n",strerror(ret));
            exit(1);       
    }
    }
    pthread_mutex_destroy(&mutex);
    printf("main:draw money finish!\n");
    return 0;
}

四. 多线程模块的封装

tasks.c

cpp 复制代码
/创建多个线程
int creat_thread_tasks(Task_t *tasks, int len)
{
    for(int i = 0; i < len; i++){
        int ret = pthread_create(&(tasks[i].tid), NULL, tasks[i].pfun, NULL);
        if(ret != 0 ){
            fprintf(stderr, "pthread_create error:%s\n",strerror(ret));
            return -1;
        }
    }
    return 0;
}
/销毁多个线程
void destory_thread_tasks(Task_t *tasks, int len)
{
    for(int i = 0; i < len; i++){
        int ret = pthread_join(tasks[i].tid, NULL);
    }
}

tasks.h

cpp 复制代码
#ifndef __TASKS_H__
#define  __TASKS_H__
#include<pthread.h>

typedef struct task{
    pthread_t tid;
    void *(*pfun)(void*);
}Task_t;
extern int creat_thread_tasks(Task_t *tasks, int len);
extern void destory_thread_tasks(Task_t *tasks, int len);
#endif

main.c

cpp 复制代码
#include<stdio.h>
#include "tasks.h"
#include<unistd.h>
#include<stdlib.h>
//#include<pthread.h>
void *main_ctl_task(void *arg)
{
    while(1){
        printf("[%ld]:main_ctl_task is running\n",pthread_self());
        sleep(1);
    }
}
void *get_usr_cmd_task(void *arg)
{
    while(1){
        printf("[%ld]:get_usr_cmd_task is running\n",pthread_self());
        sleep(1);
    }
}
void *exec_usr_cmd_task(void *arg)
{
    while(1){
        printf("[%ld]:exec_usr_cmd_task is running\n",pthread_self());
        sleep(1);
    }
}
void * get_pic_task(void *arg)
{
    while(1){
        printf("[%ld]:get_pic_task is running\n",pthread_self());
        sleep(1);
    }
}
void *send_pic_task(void *arg)
{
    while(1){
        printf("[%ld]:send_pic_task is running\n",pthread_self());
        sleep(1);
    }
}
/Linux支持的部分初始化方式
Task_t tasks[] = 
{
    {
        .pfun = main_ctl_task,
    },
    {
        .pfun = get_usr_cmd_task,
    },
    {
        .pfun = exec_usr_cmd_task,
    },
    {
        .pfun = get_pic_task,
    },
    {
        .pfun = send_pic_task,
    },
};
int main()
{
    int ret = creat_thread_tasks(tasks,sizeof(tasks)/sizeof(tasks[0]));
    if(ret == -1){
        fprintf(stderr, "creat_thread_tasks error\n");
        exit(1);
    }
    destory_thread_tasks(tasks,sizeof(tasks)/sizeof(tasks[0]));
    return 0;
}
相关推荐
拾贰_C1 小时前
【Ubuntu | VSCode | SSH | 远程连接 | Linux】VSCode 怎么实现ssh远程连接
linux·vscode·ubuntu
一叶知秋dong2 小时前
llama.cpp 启动脚本
linux·服务器·llama
桌面运维家2 小时前
校园机房vDisk IDV云桌面建设方案价格参考
linux·服务器·数据库
Cx330❀2 小时前
【MySQL基础】库与表的全面操纵指南
linux·服务器·网络·数据库·c++·mysql
凡人叶枫2 小时前
Effective C++ 条款03:尽可能使用 const
linux·开发语言·c++·嵌入式开发
程序员佳佳2 小时前
我在 Windows 和低配 Linux 上做 RAG:Milvus、FAISS、向量 API 中转的中立实测
linux·人工智能·windows·gpt·aigc·milvus·faiss
加成BUFF2 小时前
第六天 ROS 《Action 通信实验》
linux·机器人·ros
ShineWinsu2 小时前
对于Linux:进程信号的解析—下
linux·运维·服务器·面试·笔试·进程·信号
YIN_尹2 小时前
【Linux系统编程】基础IO第二讲——文件描述符
android·linux·服务器