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

一、线程(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;
}

相关推荐

  1. iOS ------ 多线 pthread,NSThread

    2024-06-12 01:54:06       8 阅读
  2. 23111 IO进程线 day6

    2024-06-12 01:54:06       35 阅读
  3. IO进程线 day7

    2024-06-12 01:54:06       37 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-12 01:54:06       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-12 01:54:06       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-12 01:54:06       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-12 01:54:06       18 阅读

热门阅读

  1. Android应用图标到应用显示过程

    2024-06-12 01:54:06       13 阅读
  2. element-plus ui的使用说明

    2024-06-12 01:54:06       14 阅读
  3. 数据分析------统计学知识点(四)

    2024-06-12 01:54:06       11 阅读
  4. C++构建MVC学生信息管理系统

    2024-06-12 01:54:06       10 阅读
  5. GIT生成SSH公钥图文教程

    2024-06-12 01:54:06       14 阅读
  6. SSID简介

    2024-06-12 01:54:06       9 阅读
  7. Web前端开发缺点:深入剖析与反思

    2024-06-12 01:54:06       9 阅读
  8. vue调用百度api时跨域问题的解决方案

    2024-06-12 01:54:06       8 阅读
  9. Django自定义CSS

    2024-06-12 01:54:06       6 阅读
  10. python连接mysql数据库、FastAPI、mysql-connector-python

    2024-06-12 01:54:06       7 阅读