IO进程线程(八)线程(pthread_t)

文章目录

  • 一、线程(LWP)概念
  • 二、线程相关函数
    • (一)创建 pthread_create
      • 1. 定义
      • 2. 使用(不传参)
      • 3. 使用(单个参数)
      • 4. 使用(多个参数)
      • 5. 多线程执行的顺序
      • 6. 多线程内存空间
    • (二)获取线程号 pthread_self
    • (三)退出线程 pthread_exit
    • (四)线程 pthread_join
    • (五)标记分离态 pthread_detach
    • (六)取消线程pthread_cancel
    • (七)多线程文件拷贝
      • 进阶版:线程数由命令行传参

一、线程(LWP)概念

轻量级的进程,进程是资源分配的基本单位,线程是系统调度的基本单位。

线程不会分配内存空间,一个进程中的多个线程是共用进程的内存空间的(0-3G)
多线程没有多进程安全,但是多线程的效率更高。

每个线程都有自己的task_struct结构体,每个线程都独立的参与时间片轮转

多线程的函数是第三方库实现的。
编码时需要加头文件 #include <pthread.h>
编译时 需要链接 线程库 -lpthread

线程之间是相互平等的

二、线程相关函数

(一)创建 pthread_create

1. 定义

#include <pthread.h>

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

功能:创建线程

参数:
    thread:线程id  tid  
    attr:线程属性 NULL 表示默认属性
    start_routine:线程体(线程处理函数)
    arg:给线程处理函数传参的 如果没有参数可传 可以置NULL

返回值:
    成功  0
    失败  错误码
  • 注:
  • pthread_t是一个long类型的数
  • start_routine是返回值和参数都是void*的函数指针
  • 主线程结束,整个进程结束

2. 使用(不传参)

#include <my_head.h>

//线程处理函数
void *task_func(void *arg){
    printf("我是子线程\n");
}

int main(int argc, const char *argv[])
{
    pthread_t tid = 0;
    int ret = 0;
    if(0 != (ret = pthread_create(&tid, NULL, task_func, NULL))){
        printf("pthread_create : %s\n", strerror(ret));
        exit(-1);
    }
    printf("我是主线程\n");
    return 0;
}
  • 注:在不传参时,最后一个参数可以置NULL

3. 使用(单个参数)

#include <my_head.h>

void *print_num(void* num){
#if 0
    //方式1:此处定义一个指针
    int *p=(int *)num;
    printf("num = %d\n",*p);//此种方式在多线程时指向的是同一块内存空间
#else
    //方式2:此处定义一个变量
    int p=* (int*) num;
    printf("num = %d\n",p);//此种方式在多线程时指向的是各自的局部变量
#endif
}

int main(int argc, char const *argv[])
{
    pthread_t tid=0;
    int num=0;
    printf("please input a number:");
    scanf("%d",&num);
    pthread_create(&tid,NULL,print_num,&num);
    
    pthread_join(tid,NULL);
    return 0;
}
  • 注:
  • 线程处理函数种中是以void*的类型传进函数中的,所以在使用之前需要进行强制类型转换。

关于:int p=* (int*) num;int p=(int)(*num)的区别。

在上述例子中使用int p=(int)(*num)会报错。
在这里插入图片描述
因为num是void*类型的指针,上述警告就是说明正在对一个void*类型的指针进行解引用;而错误是因为强制类型转换的表达式是一个void表达式。所以不能使用这种方式。

4. 使用(多个参数)

#include <my_head.h>

typedef struct _data{
    int a;
    int b;
    char c;
}data_t;

void *print_num(void* num){
#if 1
    //方式1:此处定义一个指针
    data_t *p=(data_t *)num;
    printf("a = %d;b = %d;c = %c\n",p->a,p->b,p->c);//此种方式在多线程时指向的是同一块内存空间
#else
    //方式2:此处定义一个变量
    data_t p=* (data_t *) num;
    printf("a = %d;b = %d;c = %c\n",p.a,p.b,p.c);//此种方式在多线程时指向的是各自的局部变量
#endif
}

int main(int argc, char const *argv[])
{
    pthread_t tid=0;
    data_t data = {1,3,'a'};

    pthread_create(&tid,NULL,print_num,&data);
    
    pthread_join(tid,NULL);
    return 0;
}

5. 多线程执行的顺序

多线程执行也没有先后顺序,也是时间片轮转,上下文切换

6. 多线程内存空间

同一个进程中的多个线程共用进程内存空间
全局区、堆区、字符串常量区都是公用的
只有栈区是每个线程独立的(作用域问题)
线程之间通信简单,使用全局变量即可,但是不安全

(二)获取线程号 pthread_self

#include <pthread.h>

pthread_t pthread_self(void);

功能:获取调用线程的线程号 tid

参数:无

返回值:总是会成功 返回tid

ps -eLf 此时显示的线程号经过了优化方便人查看,并非实际的线程号

(三)退出线程 pthread_exit

#include <pthread.h>
void pthread_exit(void *retval);
功能:退出当前的线程
参数:retval:退出线程时返回的状态值(其他线程用pthread_join来接收)
返回值:无

线程退出的四种情况:

  1. 线程处理函数执行结束
  2. 线程中调用pthread_exit退出
  3. 同一进程中的任一线程中调用exit或者主函数return
  4. 接收到发送的pthread_cancel取消信号时

(四)线程 pthread_join

结合态线程:结束时必须由其他线程(并没有要求必须创建这个线程的线程来调用)调用pthread_join来回收资源,如果结合态的线程结束后没有回收资源,默认属性是结合态。
分离态线程:结束时由操作系统自动回收其资源

#include <pthread.h>

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

功能:
    等待指定的结合态的线程结束 为其回收资源

参数:
    thread:等待结束的线程号
    retval:线程结束时的退出状态值 如果不关心可以传NULL

返回值:
    成功  0
    失败  错误码
  • 注:
  • 该函数是阻塞等待
  • 第二个参数一般传NULL,不关心返回值
  • 注意不能返回局部变量的地址,可以返回全局变量的地址,或者static修饰的局部变量的地址,或者malloc在堆区分配的地址(但是要注意回收资源的线程要free)

(五)标记分离态 pthread_detach

#include <pthread.h>

int pthread_detach(pthread_t thread);

功能:标记一个线程为分离态 结束时由操作系统自动回收资源

参数:thread:线程号

返回值:
    成功 0
    失败 错误码
  • 注:在主线程和子线程本身中标记子线程为分离态均可以,但是在子线程中标记自己更好,因为线程执行不分先后顺序,在子线程中标记,可以确保在子线程未执行结束前可以标记成功。
  • 一般在子线程开头进行标记,当作是线程属性的一种

(六)取消线程pthread_cancel

#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
    给线程发一个取消的请求
    目标线程是否以及何时响应这个请求取决于线程的两种属性:statu和type
参数:thread:线程id
返回值:
    成功 0
    失败 错误码
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
//设置线程是否可被取消
PTHREAD_CANCEL_ENABLE  可被取消  ----默认属性
PTHREAD_CANCEL_DISABLE  不可被取消
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
//设置线程的取消类型
PTHREAD_CANCEL_DEFERRED   延时取消   ----默认属性
                        直到线程下一次调用能作为取消点的函数时才会被取消
PTHREAD_CANCEL_ASYNCHRONOUS   立即取消

不可被取消,不接收取消信号
可被取消,立即取消,在接收到取消信号后立即取消;
可被取消,延时取消,在接收到取消信号先将当前命令执行完后,如果再次遇到可作为取消点的函数时被取消;如果遇不到就无法取消

  • 注:默认是可被取消,延时取消

(七)多线程文件拷贝

功能需求
使用多线程拷贝文件

代码实现

//实现多线程拷贝文件
#include <my_head.h>

typedef struct _msg{
    char *src_file;
    char *dest_file;
    int offset;
    int len;
}msg_t;

//初始化:保证有一个清空的目标文件,获取源文件的长度
int init_cp(const char *src_file, const char *dest_file){
    //打开一个目标文件,不存在就创建,存在就清空
    FILE *dest_fp=fopen(dest_file,"w");
    if(NULL == dest_fp) ERR_LOG("open dest file error");
    fclose(dest_fp);

    //获取源文件长度
    FILE *src_fp=fopen(src_file,"r");
    if(NULL == src_fp) ERR_LOG("open src file error");
    fseek(src_fp,0,SEEK_END);
    int size = ftell(src_fp);
    fclose(src_fp);

    return size;
}

//线程处理函数
void *func_cp(void *argv1){
    printf("子线程函数:%ld\n",pthread_self());
    
    //使用结构体接收参数
    msg_t t_argv=*(msg_t *)argv1;
    //打开文件
    int src_fd = open(t_argv.src_file,O_RDONLY);
    if(-1 == src_fd) ERR_LOG("open src file error");
    int dest_fd = open(t_argv.dest_file,O_WRONLY);
     if(-1 == dest_fd) ERR_LOG("open dest file error");
    //将两个文件的指针移动到offset位置
    lseek(src_fd,t_argv.offset,SEEK_SET);
    lseek(dest_fd,t_argv.offset,SEEK_SET);
    //复制函数
    int w_byte=0;//记录写入的字节数
    int r_byte=0;//记录本次读到的字节数
    char buff[10];//缓冲区
    while(0 < (r_byte=read(src_fd,buff,sizeof(buff)))){
        w_byte+=r_byte;
        if(w_byte>=t_argv.len){
            write(dest_fd,buff,t_argv.len-(w_byte-r_byte));
            break;
        }
        write(dest_fd,buff,r_byte);
    }
    //自己就结束了
}

int main(int argc, char const *argv[])
{
    if(3 != argc){
        printf("Usage:%s src dest\n",argv[0]);
        exit(-1);
    }
    //初始化
    int size = init_cp(argv[1],argv[2]);
    char src_path[20]={0};
    char dest_path[20]={0};
    strcpy(src_path,argv[1]);
    strcpy(dest_path,argv[2]);

    //创建线程
    msg_t argv2={src_path,dest_path,0,size/2};
    pthread_t tid1=0;
    int sta=0;
    if(sta = pthread_create(&tid1,NULL,func_cp,(void *)&argv2))
    {
        printf("pthread_create error:%s\n",strerror(sta));
    }

    msg_t argv1={src_path,dest_path,size/2,size-size/2};
    pthread_t tid2=0;
    if(sta = pthread_create(&tid2,NULL,func_cp,(void *)&argv1))
    {
        printf("pthread_create error:%s\n",strerror(sta));
    }

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    return 0;
}

进阶版:线程数由命令行传参

//实现多线程拷贝文件
#include <my_head.h>

typedef struct _msg{
    char src_file[20];
    char dest_file[20];
    int offset;
    int len;
}msg_t;

//初始化:保证有一个清空的目标文件,获取源文件的长度
int init_cp(const char *src_file, const char *dest_file){
    //打开一个目标文件,不存在就创建,存在就清空
    FILE *dest_fp=fopen(dest_file,"w");
    if(NULL == dest_fp) ERR_LOG("open dest file error");
    fclose(dest_fp);

    //获取源文件长度
    FILE *src_fp=fopen(src_file,"r");
    if(NULL == src_fp) ERR_LOG("open src file error");
    fseek(src_fp,0,SEEK_END);
    int size = ftell(src_fp);
    fclose(src_fp);

    return size;
}

//线程处理函数
void *func_cp(void *argv1){
    //printf("子线程函数:%ld\n",pthread_self());
    
    //使用结构体接收参数
    msg_t t_argv=*(msg_t *)argv1;
    //打开文件
    int src_fd = open(t_argv.src_file,O_RDONLY);
    if(-1 == src_fd) ERR_LOG("open src file error");
    int dest_fd = open(t_argv.dest_file,O_WRONLY);
     if(-1 == dest_fd) ERR_LOG("open dest file error");
    //将两个文件的指针移动到offset位置
    lseek(src_fd,t_argv.offset,SEEK_SET);
    lseek(dest_fd,t_argv.offset,SEEK_SET);
    //复制函数
    int w_byte=0;//记录写入的字节数
    int r_byte=0;//记录本次读到的字节数
    char buff[10];//缓冲区
    while(0 < (r_byte=read(src_fd,buff,sizeof(buff)))){
        w_byte+=r_byte;
        if(w_byte>=t_argv.len){
            write(dest_fd,buff,t_argv.len-(w_byte-r_byte));
            break;
        }
        write(dest_fd,buff,r_byte);
    }
    //自己就结束了
}

int main(int argc, char const *argv[])
{
    if(4 != argc){
        printf("Usage:%s src_file dest_file thread_num\n",argv[0]);
        exit(-1);
    }
    //初始化
    int size = init_cp(argv[1],argv[2]);
    //定义参数接一下命令行参数
    char src_path[20]={0};
    char dest_path[20]={0};
    strcpy(src_path,argv[1]);
    strcpy(dest_path,argv[2]);

    int thread_num=atoi(argv[3]);

    int sta=0;//记录创建进程状态
    int nbytes=0;
    msg_t *p_argv=(msg_t *)malloc(sizeof(msg_t) * thread_num); //参数数组
    if(NULL == p_argv){
        printf("分配参数空间失败\n");
        exit(-1);
    }
    pthread_t *tid=(pthread_t *)malloc(sizeof(pthread_t) * thread_num);//存放tid
    if(NULL == tid){
        printf("分配tid空间失败\n");
        exit(-1);
    }

    //创建线程
    for(int i=0;i<thread_num;i++){
        strcpy(p_argv[i].src_file,src_path);
        strcpy(p_argv[i].dest_file,dest_path);
        p_argv[i].offset=size/thread_num*i;
        if(i == thread_num-1){
            p_argv[i].len=size-nbytes;
        }
        else{
            p_argv[i].len=size/thread_num;
        }
        nbytes+=p_argv[i].len;
        
        if(sta = pthread_create(tid+i,NULL,func_cp,(void *)(p_argv+i)))
        {
            printf("pthread_create error:%s\n",strerror(sta));
        }

    }

    for(int i=0;i<thread_num;i++){
        pthread_join(tid[i],NULL);
    }

    free(p_argv);
    free(tid);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/697685.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BUUCTF---web---[GYCTF2020]Blacklist

1、来到题目连接页面 2、测试单引号和双引号&#xff0c;单引号报错&#xff0c;双引号没报错 1 1" 3、使用万能句式 4、使用堆叠注入测试&#xff0c;查看数据库名 1;show databases;# 5、查看表名 1;show tables;# 6、查看FlagHere中字段名 1;show columns from FlagH…

Python | Leetcode Python题解之第143题重排链表

题目&#xff1a; 题解&#xff1a; class Solution:def reorderList(self, head: ListNode) -> None:if not head:returnmid self.middleNode(head)l1 headl2 mid.nextmid.next Nonel2 self.reverseList(l2)self.mergeList(l1, l2)def middleNode(self, head: ListNo…

Webpack 从入门到精通-基础篇

一、webpack 简介 1.1 webpack 是什么 webpack 是一种前端资源构建工具&#xff0c;一个静态模块打包器(module bundler)。 在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。 它将根据模块的依赖关系进行静态分析&#xff0c;打包生成对应的…

CA证书及PKI

文章目录 概述非对称加密User Case: 数据加密User Case: 签名验证潜在问题 CACA证书的组成CA签发证书流程CA验证签名流程CA吊销证书流程 PKI信任链证书链 概述 首先我们需要简单对证书有一个基本的概念&#xff0c;以几个问题进入了解 ❓ Question1: 什么是证书&#xff1f; 证…

数据可视化——pyecharts库绘图

目录 官方文档 使用说明&#xff1a; 点击基本图表 可以点击你想要的图表 安装&#xff1a; 一些例图&#xff1a; 柱状图&#xff1a; 效果&#xff1a; 折线图&#xff1a; 效果&#xff1a; 环形图&#xff1a; 效果&#xff1a; 南丁格尔图&#xff08;玫瑰图&am…

Mysql的InnoDB介绍

目录 show engines查看搜索殷勤&#xff0c;默认InnoDB。 Mysql为什么使用InnoDB作为默认存储引擎 InnoDB主要包括内存结构和磁盘结构 内存结构包含: 磁盘结构中包括: 为什么设计成内存结构和磁盘结构两部分 使用InnoDB存储引擎创建的表&#xff0c;对应的数据文件在哪里…

Android14之向build.prop添加属性(二百一十九)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

uniapp原生插件开发实战——集成Android端的Twitter登陆

Android集成Twitter登陆的官方教程:https://github.com/twitter-archive/twitter-kit-android/wiki 项目创建 首先可以先看下uniapp原生插件开发教程 uniapp原生插件类型分为两种: Module模式:能力扩展,无嵌入窗体的UI控件,类似于功能插件。Component模式:在窗体中内嵌…

php 混合xml js,html 代码报错 ,结束标签关闭, short_open_tag 的作用,php关闭文件结束判断

结束标签关闭, short_open_tag 的作用&#xff0c;php关闭文件结束判断 有时候我们我们会将php&#xff0c;xml&#xff0c;js&#xff0c;html 混合编写 php文件只要开始标签而不要结尾标签? 混合代码看代码 直接运行 yntax error, unexpected version (T_STRING) in php…

STM32智能家居项目esp8266上云OneNet【附源码+详细教程】

目录 一、硬件选材 二、OneNet使用教程 三、代码修改教程 四、添加数据流方法 五、项目工程&#xff08;源码元件清单教程&#xff09; 小白也能做&#xff0c;项目工程在后文可下载。 一、硬件选材 二、OneNet使用教程 拿到代码后肯定是连不上网的&#xff0c;因为源码…

Android.基本用法学习笔记

设置文本的内容 先在strings.xml声明变量 方法1. 方法2. 设置文本的大小 1.单位dp&#xff0c;大家可以去学一下有关的单位换算 2. 设置文本颜色 1. 2. 4.设置文本背景颜色 1. 2. 设置视图的宽高 与上级视图一致&#xff0c;也就是上一级有多宽就有多少 1. 2. 3. 4. 设置视图…

生成式人工智能重置:从初期热潮到战略扩展

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

cs61C | lecture4

cs61C | lecture4 C 语言内存布局 ### Stack 在最顶部&#xff0c;向下增长。包含局部变量和 function frame information。 > Each stack frame is a contiguous block of memory holding the local variables of a single procedure. > A stack frame includes: > …

C语言详解(文件操作)2

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

浅析嵌入式实时系统中信号量的概念

目录 概述 1. 认识信号量 1.1 定义信号量 1.2 信号量的类型 1.2.1 二值信号量 1.2.2 计数信号量 1.2.3 互斥信号量 1.2.3.1 认识互斥信号量 1.2.3.2 互斥信号量的其他特性 2 典型信号量的使用 2.1 等待和信号同步 2.2 多任务等待和信号同步 2.3 信用跟踪同步 2.…

C++ - Clion安装Qt msvc2017版本教程,基础环境配置clion+ Qt5.12.12 msvc2017 + VS2019

背景&#xff1a;平时代码开发使用clion&#xff0c;但使用项目要制定mscv2017版本Qt。先装过mingw版本Qt无法运行&#xff0c;但msvc版本依赖装有Visual Studio&#xff0c;本地装的又是2019版。就出现了这个大坑&#xff0c;需要配置好clion Qt msvc2017 VS2019。 文章目录 …

深度学习中embedding层的理解

Embedding层作用 在深度学习领域中&#xff0c;Embedding层扮演着至关重要的角色&#xff0c;尤其在处理文本数据或类别数据。Embedding层的功能有两个&#xff1a; 1. 将高维稀疏的输入数据&#xff08;如单词、类别标签等&#xff09;转换为低维稠密的向量表示&#xff0c;…

LNMP配置

文章目录 一、相关概念CGI的由来FastCGIPHP-FPM 二、编译安装编译安装nginxyum安装mysql编译安装php配置nginx支持php解析增加数据库安装论坛 一、相关概念 CGI的由来 最早的Web服务器只能简单地响应浏览器发来的HTTP请求&#xff0c;并将存储在服务器上的HTML文件返回给浏览器…

Python | Leetcode Python题解之第144题二叉树的前序遍历

题目&#xff1a; 题解&#xff1a; class Solution:def preorderTraversal(self, root: TreeNode) -> List[int]:res list()if not root:return resp1 rootwhile p1:p2 p1.leftif p2:while p2.right and p2.right ! p1:p2 p2.rightif not p2.right:res.append(p1.val)…

DeepSpeed MoE

MoE概念 模型参数增加很多&#xff1b;计算量没有增加&#xff08;gating小FNN&#xff0c;比以前的大FNN计算量要小&#xff09;&#xff1b;收敛速度变快&#xff1b; 效果&#xff1a;PR-MoE > 普通MoE > DenseTransformer MoE模型&#xff0c;可视为Sparse Model&…