线程的概念+线程函数API

news2024/10/1 7:30:12

C线程

有关线程的简单实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//定义线程函数(固定)--void *
void *pth_fun(void *pth_arg){
    while(1){
        printf("pthread\n");
        sleep(1);
    }
    return NULL;
}
int main(){
    pth_fun(NULL);//先将线程函数作为普通函数调用
    return 0;
}
guojiawei@ubantu-gjw:~/Desktop/pthread$ gcc pthread.c 
guojiawei@ubantu-gjw:~/Desktop/pthread$ ./a.out 
pthread
pthread
pthread
pthread
pthread
^Xpthread
pthread
^Z
[1]+  已停止               ./a.out

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <pthread.h>
//定义线程函数(固定)--void *
void *pth_fun(void *pth_arg){
    while(1){
        printf("child_pthread\n");
        sleep(1);
    }
    return NULL;
}
int main(){
    /*将函数注册为线程函数*/
    //int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
                          void *(*start_routine) (void *), void *arg);
    pthread_t tId=0;
    //函数名就是地址
    pthread_create(&tId,NULL,pth_fun,NULL);//主线程
    while(1){
        printf("father\n");
        sleep(2);
    }
    return 0;
}

程序结果:

可以自己链接线程库-pthread,但是现在可以不加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Kq6cFYd-1669961351681)(/home/guojiawei/.config/Typora/typora-user-images/image-20221129145844582.png)]

主线程和次线程是并发运行的,主线程不运行,次线程无法运行

  • 其实主线程是一个进程,目的为了带动线程运行
  • 线程就是一个函数,有的语言会封装

学习C线程意义

  • c程序经常涉及到多线任务的问题,所以c线程在实际开发中会被经常用到

    使用多线程的话,必须要OS支持

  • 所有的线程库的原理和使用方式都是类似

    理解了C线程函数,在学习其它语言的线程库函数就会容易很多

为什么会有线程

为了弥补进程的缺点

进程最大的优点:拥有独立的进程空间

可以很好地保证每一个进程的安全,不被其它进程所攻击或者干扰,

进程的缺点:(也是优点带来的)

  • 进程间切换的计算机资源开销很大,切换效率非常低
  • 进程间数据共享的开销也很大

为什么进程之间切换资源开销大

​ OS是通过虚拟内存机制来实现进程空间独立的

  • 进程在并发运行时需要相互间的切换,切换时必然需要涉及虚拟内存机制的控制

  • 但是虚拟内存机制比较复杂,所以在进行进程间切换时,会耗费高昂的cpu、缓存(cache)、内存等计算机资源,也非常耗费切换时间

进程通信的开销大

​ 当程序涉及多进程时,往往会涉及到进程间的通信,由于进程空间的独立性,OS提供了各种各样的通信机制:

这些通信机制共同原理:通过OS来转发进程间的数据

但是调用OS提供的这些通信机制的函数时,这些OS函数的运行也是需要消耗相当cpu、内存等计算机资源的,同时也很耗费时间


对于有OS的计算机来说:

虽然进程是必不可少的,但是进程确又不能太多,进程太多会导致计算机资源被剧烈消耗,此时你会发现你的计算机非常的卡

创建多进程目的

目的1:创建子进程,执行新程序

目的2:创建子进程得到多进程,通过多进程并发实现多线任务

多线任务例子

  1. 同时阻塞的读鼠标和键盘时,如果单线的话会想互影响,需要两线任务来实现

    比如:父进程读鼠标,子进程读键盘

  2. 读写管道时,读操作是阻塞的,为了避免读操作影响写操作,也需要两线任务同时进行

    父子进程一个读管道,一个写管道

  • 第一种目的,执行新程序时必须创建子进程,这个无法逃避的

  • 第二种目的,使用多进程来实现就存在巨大的问题

    因为几乎所有的程序都涉及多线任务的操作

多线任务使用多进程的问题

程序往往都是十几个任务以上,如果此时使用多进程来实现多线任务的,这就大致大量进程的产生

比如计算机运行了100个程序,假设每个程序平均10多个任务,如果全部采用多进程来实现,计算机最终要运行的进程就多达上100个

总结:

进程切换和进程间通信的计算机资源开销又很大,往往导致计算机非常卡顿,程序的运行效率非常低

线程的发明目的:弥补多进程实现多线任务的缺点。

线程为什么可以弥补进程的缺点

线程与进程一样,线程和进程会被OS统一调度,所以线程和进程都是一起并发运行的

实现程序的多线任务的前提就是:并发执行

有了线程以后,凡是程序涉及到多线任务时,都使用多线程来实现

  • 线程间的切换和数据通信的开销非常低
  • 线程还有另一个名称,叫“轻量级的进程”-----开销非常低

说白了线程就是为了多线任务而生的

为什么线程切换的开销低

使用多线程来实现多线任务时:

线程本质上它只是程序(进程)的一个函数,与普通函数的区别是:普通函数时单线的运行关系,而线程函数被注册为线程后,是多线并发运行。

简单理解就是:
1.普通函数的执行是:堆栈,也就是遇到子函数去执行子函数,再遇到子函数,切换到调用的子函数,执行完就返回到调用函数,直至结束,所以是单线
2.而线程是并发的,不存在相互调用,只能时间片切换,所以是多线
--
线程函数也可以去调用普通函数
  • 对于普通函数来说,只有当相互调动时才会涉及函数间的切换
  • 对于线程函数来说,只要运行的时间片到了就会切换

不管是函数间的切换,还是线程的切换都是进程内部的事情

不涉及进程间的切换,故而省去了进程间切换的巨大开销

但是两个线程分别存在于不同的进程,此时两个线程切换依然需要进程切换

  • 线程的切换其实就是函数间的切换,函数切换当然也需要开销,但是这些开销非常小

为什么线程间数据共享的开销很低

线程的本质就是函数,所以线程通信属于函数通信

函数间通信有两种方式:
 (1)具有相互调用关系函数来说
				         使用函数传参来通信。
 (2)对于没有调用关系的函数来说
				         使用全局变量来通信。
	               A函数 ————> 全局变量 ————> B函数
全局变量的作用是什么?

用来实现无调用关系的函数间通信


进程中所有的线程函数除了相互并发运行外,没有调用关系

所以线程函数间想要数据共享的话,就使用全局变量来通信


线程为什么不能替代进程

线程的本质是函数,函数运行需要内存空间,线程运行的内存空间就是进程的内存空间

线程运行时必须依赖于进程的存在,如果没有进程所提供的内存空间这个资源,线程根本无法运行

线程作为函数,只是进程的一个部分而已,线程是不可能脱离进程而独立存在

  • 同一个进程中的所有线程,都是运行在相同的进程空间中的
  • 换句话说同一个进程中所有线程共相同的进程空间

既然进程空间是共享的,那么所有的函数,自然就能共享访问在共享空间中所开辟出来的全局变量

线程独立的属性

线程共享进程空间的资源
  • 全局变量
  • 工作目录(进程的当前工作目录)
  • 打开的文件描述符
  • 子函数
  • 进程uid、gid,进程PID等等
线程的独立属性

并发运行的就是一个单独执行体

  • 每个线程拥有自己独立的线程ID(TID)

  • 每个线程有独立的切换状态

    • 在切换时,当前线程被中断的那条指令的地址
    • 线程切换时相关的运行状态

    说白了就是:保存现场,便于还原

  • 有自己独立的函数栈

    每一个函数都有自己的函数栈,所有的函数栈都开辟于进程空间的进程栈

    函数栈的作用就是用来保存函数局部变量

  • 自己独立的错误号

    线程函数出错时,错误号并不是通过设置errno实现的,而是直接将错误号返回

  • 每一个线程有自己独立的信号屏蔽字未决信号集

  • 每个线程有自己独立的tack_struct结构体

    • 管理线程时:也会为线程开辟一个task_struct变量
    • 只不过适用于存放的是线程的管理信息

创建子进程执行新程序,大多都是OS操心的事

比如:父进程(命令行、图形界面)创建子进程并加载执行新程序

但是我们自己的程序很少使用多进程,一般使用多线程

线程控制函数

  • pthread_create()
  • pthread_join()
  • pthread_detach()
  • pthread_cancel()
  • pthread_exit等

线程函数由谁提供

进程控制的fork、exec等函数都是由os系统提供的

为了不给OS增加负担,同时也为了提高线程的灵活性:

后来的线程不由OS提供,而是由单独的线程库来提供

不过线程库在实现时,也是调用了相应的系统API的

线程库

c线程库并不是C标准库,而是POSIX C库的一部分

java、c++、c#的线程函数由他们自己的线程库来实现的

  • java、c++的线程函数会比c的线程复杂一些

线程库和OS系统API的关系

线程库函数实际上也是封住OS的相应API来实现的

线程库运行在Linux,通过调用Linux的clone()等系统函数实现

将线程函数注册为线程时:
其实就是通过调用这类系统API,然后去模拟我们的进程来实现的
正是因为是:模拟进程来实现的,所以线程函数才能进程一样,一起被并发运行

可以自己调用系统API,然后封装做出自己的c/c++/java线程库

----不过没啥价值和意义,因为有现成的

C线程控制函数

pthread_create()

#include <pthread.h>
//把第三个参数的函数注册成为一个线程函数
//该函数一旦注册成功,这个函数就以次线程的方式开始并发运行
 int pthread_create(pthread_t *thread, const pthread_attr_t *attr,\
                     void *(*start_routine) (void *), void *arg);
/*返回值
		成功返回0,失败返回非零错误号*/

Compile and link with -pthread

目的主要是实现并发,而且该注册函数是次进程

  • 凡是使用pthread_create注册的线程都是次线程,次线程会和主线程一起并发运行。

void* (*start_routine) (void *):参数和返回值都是void *,函数名就是地址

参数
  1. thread------注意传指针,需要取地址

  2. attr----(attibute属性)

    是个重命名的结构体,用于设置线程属性

    • 设置线程属性是为了实现某些特殊功能
    • 设置为NULL,表示不设置特有的属性,使用线程默认属性所提供的功能

    正常情况下,线程默认属性所提供的功能就已经够用了

  3. 要注册为线程的函数地址

如果不注册,线程函数就是一个普通的函数

//线程函数需要我们自己定义
void *pth_fun(void *pth_arg){
		...//线程要做的事情
		}
//pth_fun和pth_arg的命名由自己决定。
  1. arg—传递给线程函数的参数

传递给线程函数的参数,这个参数会传递给pth_arg

  • 如果参数很多的话,定义一个结构体,然后把结构体变量的地址传过去
  • 如果不传递参数,设置为NULL

总结:pthread(进程号地址,设置属性(一般NULL),函数名地址,线程函数的传参)

程序----两个次进程向文件写数据

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define SECOND_PTH_NUMS 2
void pth_print_err(char * str,int err){
    printf("%s:%s",str,strerror(err));
    exit(-1);
}
//次线程结构体参数
typedef struct pthread_arg{
      pthread_t tid;
      int pth_no;//线程编号
      int fd;//打开的文件描述符
}ptharg;
//线程普通函数
void *pth_fun(void *pth_arg){
    int fd=((ptharg *)pth_arg)->fd;
    while(1){
        printf("%d",((ptharg *)pth_arg)->pth_no);
        write(fd,"hello ",6);
        write(fd,"world\n",6);
        sleep(1);
    }
    return NULL;
}
int main(){
    int i,ret;
    int fd=open("./mmm.txt",O_CREAT|O_TRUNC|O_RDWR,0664);
    if(fd==-1) pth_print_err("open fails\n",errno);
    /*将函数注册为线程函数,传建两个次线程
        int pthread_create(线程号地址,设置属性,函数地址,线程函数传参);*/
    ptharg pth_arg[SECOND_PTH_NUMS];
    for(int i=0;i<SECOND_PTH_NUMS;i++){
        pth_arg[i].fd=fd;
        pth_arg[i].pth_no=i;
        ret= pthread_create(&pth_arg[i].tid,NULL,\
                            pth_fun,(void *)&pth_arg[i]);
        if(ret!=0){
            pth_print_err("pthread_create fails\n",ret);
        }
    }
    while(1){
        printf("father\n");
        sleep(2);
    }
    return 0;
}

在这里插入图片描述

需要强调的地方

  • gcc编译时,跟-pthread选项(现在可以不加)

  • 父子线程调用子函数不冲突

    • main函数调用子函数时,子函数属于主线程这条线
    • 次线程调用子函数时,子函数属于次线程这条线。
  • 主线程代表了整个进程的存在

    • 主线程结束了,整个进程也就结束了,进程都没了线程自然也没了
    • 所以主线程一定不能死
    • 次线程结束了,对整个进程没有任何影响
  • C线程函数的启动与c++/java线程函数启动的略微有所不同

    • c++/java的线程函数被注册为线程后不会立即启动,需要单独调用某个启动函数来启动

    c线程函数一旦被pthread_create注册为线程后会立即被启动运行

    • c++、java等面向对象的函数,都被封装在类里面,包括线程函数也是如此

      而c这种面向过程语言的函数,全部都裸露在外的

pthread_cancel()

#include <pthread.h>
//当次线程是死循环时,可以调动这个函数主动取消该线程
//返回值:成功返回0,失败返回非零错误号
int pthread_cancel(pthread_t thread);
//参数------thread:要取消线程的TID
//Compile and link with -pthread

//设置全局变量,供signal_fun使用
typedef struct thread_args{
    pthread_t thrId;
    int thrNo;
    int fd;
}threadArg;
threadArg thr[2];
//取消两个次线程(信号捕捉)
void signal_fun(int signo){
    if(SIGALRM==signo){
        int i=0;
        for(;i<PTH_NUMS;i++)
            pthread_cancel(thr[i].thrId);
    }
}
//alarm,10秒后取消
 signal(SIGALRM,signal_fun);
 alarm(10);

使用编号来结束

好处是,完整执行程序后,再去判断是否该杀死

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define PTH_NUMS 2
#define PTHEXIT  -1
typedef struct thread_args{
    pthread_t thrId;
    int thrNo;
    int fd;
}threadArg;
//结构体封装所有全局变量
struct global_vals{
      threadArg thr[2];
      int pth_exit_flag[PTH_NUMS];
}globalVal;//全局变量初始化为0
void print_err(int err,char* str){
     printf("%s:%s",str,strerror(err));
     exit(-1);
}
void *pthread_fun(void *arg){
   int fd=((threadArg *)arg)->fd;
   int thrNo=((threadArg*)arg)->thrNo;
   pthread_t thrId=((threadArg *)arg)->thrId;
   /*线程功能*/
   while(1){
       printf("线程号:%lu,线程编号:%d\n",thrId,thrNo);
       write(fd,"hello,",6);
       write(fd,"world\n",6);
       if(globalVal.pth_exit_flag[thrNo]) break;
       sleep(1);
   }
   return NULL;
}
void signal_fun(int signo){
    if(SIGALRM==signo){
        int i=0;
        for(;i<PTH_NUMS;i++){
            globalVal.pth_exit_flag[i]=PTHEXIT;
            pthread_cancel(globalVal.thr[i].thrId);
        }
    }
}
int main(void){
    int fd=open("./threadFile",O_CREAT|O_TRUNC|O_RDWR,0664);
    int i,ret;
    for(i=0;i<PTH_NUMS;i++){
           globalVal.thr[i].fd=fd;
           globalVal.thr[i].thrNo=i;
           ret=pthread_create(&globalVal.thr[i].thrId,NULL,\
                     pthread_fun,(void *)&globalVal.thr[i]);
           if(ret!=0)
               print_err(ret,"pthread_create fails\n");
        }
    //定时10s,时间到后取消次线程
    signal(SIGALRM,signal_fun);
    alarm(10);
    //父线程
    while(1){
        printf("father\n");
        sleep(2);
    }
    return 0;
}

pthread_exit()

#include <pthread.h>
//线程调用这个函数时,可以主动退出(终止)
//返回值:成功返回0,失败返回非零错误号
//参数----------retval:线程结束时的返回值。
void pthread_exit(void *retval);
/*如果返回值很多时,就封装成一个结构体,返回结构体变量的地址即可*/
  • 类似于exit函数,不过exit是终止整个进程的
  • pthread_exit是终止次线程

事实上:线程也可以通过调动return来退出(终止)

pthread_self()

#include <pthread.h>
//功能:线程获取自己的TID,类似于进程调用getpid()获取自己的PID一样
pthread_t pthread_self(void);
//返回值:成功返回线程TID,失败返回非零错误号。

使用:

 printf("fatherTid:%lu\n",pthread_self());//主线程TID

pthread_join()

#include <pthread.h>
/*功能:
阻塞等待tid为thread的次线程结束,结束时该函数会回收次线程所占用的所有资源(存储空间)
*/
//返回值:成功返回0,失败返回错误号
int pthread_join(pthread_t thread, void **retval);

这个函数只对次线程有意义,对主线程没有意义

  • 因为主线程结束时整个进程就结束了,整个进程资源会由父进程回收
  • 这个函数一般都是由主线程调用,以回收相关次线程的资源
  • 当然次线程也是可以调用这个函数来回收其它次线程资源的
回收次线程资源原因
  • 有些程序(进程)一旦运行后将会长期运行,不会结束
  • 如果不回收,每结束一个次线程就导致一部分资源被占用
  • 慢慢累积会使得整个进程资源越用越少,最好导致进程崩溃

说白了就是:次进程不断占据运行的进程的资源,最后过多而使得进程崩溃

参数:
(a)thread:指定要回收次线程的TID
(b)retval:次线程函数返回的返回值

``参数二取的是void * 返回值的地址,所以是两个*`

使用
 //次进程函数里面的返回值
//return NULL;
   pthread_exit((void *)10);
//循环接收次进程资源(阻塞)
    void *retval=NULL;
    for(i=0;i<PTH_NUMS;i++){
        pthread_join(globalVal.thr[i].thrId,&retval);
        printf("%p\n",retval);
   }

​ 如果线程是被pthread_cancel取消掉的,自动返回-1

pthread_detach()

#include <pthread.h>
/*功能:
	如果次线程的资源不希望别人调用pthread_join函数来回收的话,而是希望自己在结束时,自动回收资源的话,就可以调用这个函数。
*/
//返回值:成功返回0,失败返回错误号
//参数:thread:你要分离的那个次线程的TID。
int pthread_detach(pthread_t thread);

这个函数的功能就是分离次线程,让次线程在结束时自动回收资源

使用:

 //次进程资源自动回收
   pthread_detach(globalVal.thr[i].thrId);
  • pthread_joinpthread_detach作为两种不同的线程资源回收方式,只能二选一
注释代码的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gv5A57j-1669961351684)(/home/guojiawei/.config/Typora/typora-user-images/image-20221130184244462.png)]

注册进程退出处理函数

  • 进程程在退出时会自动调用atexit();,实现进程的扫尾处理
回顾进程处理函数:atexit
void process_deal(){
    printf("\ndealing process!!\n");
}
//函数无返回值无传参
/*进程处理函数*/
    atexit(process_deal);

把ctrl+c变成正常退出

void signal_fun(int signo){
 if(SIGALRM==signo){
     int i=0;
     for(;i<PTH_NUMS;i++){
         globalVal.pth_exit_flag[i]=PTHEXIT;
         pthread_cancel(globalVal.thr[i].thrId);
     }
 }
 else if(signo==SIGINT){
     exit(0);
 }
}

signal(SIGINT,signal_fun);

  • 线程也是这样
 #include <pthread.h>
//注册函数
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
  1. pthread_cleanup_push
  • 将类型为void (*routine)(void *)函数注册为“线程退出处理函数”

    注意第一个没*,第二个有*

  • arg:传递给退出处理函数的参数

  1. pthread_cleanup_pop

执行这个函数时,参数:

  • 如果参数写!0

    会将压入栈中的推出处理函数地址弹出,然后调用退出函数进行线程的扫尾处理。

  • 如果参数写0

    不弹出调用

    注册的原理就是将处理函数地址压入线程栈
----可以反复调用该函数注册多个退出处理函数,但是一般一个就够了

----注册了多个线程退出处理函数的话,由于栈先进后出的特点
    注册压栈的顺序与弹栈调动的顺序刚好相反

有一个pthread_cleanup_push,就必须要对应有一个pthread_cleanup_pop

就算这个函数调用不到也必须写,否则编译时不通过

就像{}是成对出现的,缺一个都会报错

例子
/*线程退出处理函数*/
void pth_exit_deal(void* t_arg){
  printf("pthread %lu exit\n",((threadArg*)t_arg)->thrId);
}
/*进程函数*/
void *pthread_fun(void *arg){
    //注册 线程退出处理 函数
   pthread_cleanup_push(pth_exit_deal,arg);
   pthread_cleanup_pop(!0);
   pthread_exit((void *)10);
}

弹栈线程退出处理函数的几种条件

(a)调用thread_cleanup_pop(!0),主动弹栈

(b)如果线程是被别人调用pthread_cancel取消的,也会弹栈

(c)如果线程是调用pthread_exit函数退出的,也会弹栈

如果线程时调用return退出的话,是不会自动弹栈的
要弹栈的话,必须主动调动thread_cleanup_pop(!0)			

线程的属性设置

就是pthreadcreate()的第二个参数不要写NULL

可以设置的线程属性
  • 设置绑定属性
  • 设置分离属性
  • 设置线程堆栈属性
  • 设置线程调度优先级属性

事实上默认属性所提供的功能就已经足够我们使用了,

C线程中设置属性的函数非常多,基本每一种属性都有独立的设置函数
例子:设置分离属性

将线程分离有两种方法:

  • 调用pthread_detach函数实现

  • 通过设置分离属性实现

    事实上使用pthread_detach()更方便些

分离属性设置的步骤:

  1. 定义一个变量来存放新属性

    pthread_attr_t attr;//一个结构体,被typedef重命名了
    
  2. 调用pthread_attr_init(&attr);初始化一下attr结构体变量

    int pthread_attr_init(pthread_attr_t *attr);
    //成功返回0,失败返回错误号
    
  3. 调用pthread_attr_setdetachstate预设分离属性

    想设置什么类型属性就选择什么函数

    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
    
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    

    指定PTHREAD_CREATE_DETACHED宏后:

    pthread_attr_setdetachstate函数会自动的将:

    分离属性相关的数据设置到attr结构体变量的各个成员中

  4. 调动pthread_create创建线程时,将attr传递给pthread_create,将线程分离

  5. 删除属性设置

    int pthread_attr_destroy(pthread_attr_t *attr);
    //成功返回0,失败返回错误号
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    #include <signal.h>
    #define PTH_NUMS 2
    #define PTHEXIT  -1
    typedef struct thread_args{
        pthread_t thrId;
        int thrNo;
        int fd;
    }threadArg;
    //结构体封装所有全局变量
    struct global_vals{
          threadArg thr[2];
          int pth_exit_flag[PTH_NUMS];
          pthread_attr_t attr;//设置线程属性
    }globalVal;//全局变量初始化为0
    void print_err(int err,char* str){
         printf("%s:%s",str,strerror(err));
         exit(-1);
    }
    /*线程退出处理函数*/
    void pth_exit_deal(void* t_arg){
      printf("pthread %lu exit\n",((threadArg*)t_arg)->thrId);
    }
    void *pthread_fun(void *arg){
       int fd=((threadArg *)arg)->fd;
       int thrNo=((threadArg*)arg)->thrNo;
       pthread_t thrId=((threadArg *)arg)->thrId;
       //pthread_detach(pthread_self());
       //注册 线程退出处理 函数
       pthread_cleanup_push(pth_exit_deal,arg);
       /*线程功能*/
       while(1){
           printf("线程号:%lu,线程编号:%d\n",thrId,thrNo);
           write(fd,"hello,",6);
           write(fd,"world\n",6);
           if(globalVal.pth_exit_flag[thrNo]) break;
           sleep(1);
       }
       //pthread_exit((void *)10);
       return NULL;
       pthread_cleanup_pop(!0);
    }
    void signal_fun(int signo){
        if(SIGALRM==signo){
            int i=0;
            for(;i<PTH_NUMS;i++){
                globalVal.pth_exit_flag[i]=PTHEXIT;//退出标志
                //pthread_cancel(globalVal.thr[i].thrId);//主动
            }
        }
        else if(signo==SIGINT){
            pthread_attr_destroy(&globalVal.attr);
            exit(0);
        }
    }
    void process_deal(){
        printf("\ndealing process!!\n");
    }
    int main(void){
        /*进程处理函数*/
        atexit(process_deal);
        int fd=open("./threadFile",O_CREAT|O_TRUNC|O_RDWR,0664);
        int i,ret;
        //属性初始化
        ret=pthread_attr_init(&globalVal.attr);
        if(ret!=0) print_err(ret,"attr fails\n");
        //设置分离属性
        pthread_attr_setdetachstate(&globalVal.attr,PTHREAD_CREATE_DETACHED);
        //线程初始化
        for(i=0;i<PTH_NUMS;i++){
               globalVal.thr[i].fd=fd;
               globalVal.thr[i].thrNo=i;
               ret=pthread_create(&globalVal.thr[i].thrId,\
                                  &globalVal.attr,\
                         pthread_fun,(void *)&globalVal.thr[i]);
                //次进程资源自动回收
                pthread_detach(globalVal.thr[i].thrId);
               if(ret!=0)
                   print_err(ret,"pthread_create fails\n");
            }
        //信号异常终止变成正常终止
        signal(SIGINT,signal_fun);
        //定时10s,时间到后取消次线程
        signal(SIGALRM,signal_fun);
        alarm(3);
        #if 0
        //循环接收次进程资源(阻塞)
        void *retval=NULL;
        for(i=0;i<PTH_NUMS;i++){
            pthread_join(globalVal.thr[i].thrId,&retval);
            printf("%ld\n",(long)retval);
        }
        #endif
        //父线程
        while(1){
            printf("fatherTid:%lu\n",pthread_self());//主线程TID
            sleep(2);
        }
        return 0;
    }
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s学习笔记-完整版

文章目录第一章 kubernetes介绍应用部署方式演变kubernetes简介kubernetes组件kubernetes概念第二章 集群环境搭建环境规划集群类型安装方式主机规划环境搭建主机安装环境初始化安装docker安装kubernetes组件准备集群镜像集群初始化安装网络插件服务部署第三章 资源管理资源管理…

docker入门级使用

文章目录dockerdocker概述出现原因官网虚拟化与容器化docker架构图docker安装阿里云镜像加速底层原理Docker常用命令帮助命令镜像命令容器命令常用其他命令安装nginx安装Tomcatdocker 越学习越觉得自己的无知 谦卑,不傲慢,厚积而薄发 docker概述 出现原因 一次编译,到处报错开…

跨境电商如何减少客户流失率:成功的5种保留策略

关键词&#xff1a;跨境电商、客户流失率 经营一家跨境电商企业常常感觉就像一个漏水的容器。无论您在顶部倾注了多少客户&#xff0c;这始终是一个不断耗尽底部的百分比。 这被称为客户流失——它使可持续增长成为品牌面临的主要挑战。 客户流失与客户满意度密切相关。如果您的…

常见七大SMD器件布局基本要求,你掌握了几点?

SMD器件布局的一般要求 细间距器件推荐布置在PCB同一面&#xff0c;也就是引脚间距不大于0.65mm的表面组装器件&#xff1a;也指长X宽不大于1.6mmX0.8mm(尺寸编码为1608)的表面组装元件。 SMD器件的回流焊接器件布局要求 同种贴片器件间距要求≥12mil&#xff08;焊盘间&…

HTML+CSS大作业:旅游网页设计与实现——旅游风景网站6页HTML+CSS+JavaScript实训大作业 HTML+CSS大作业 HTML期末大作业

&#x1f468;‍&#x1f393;静态网站的编写主要是用 HTML DⅣV CSSJS等来完成页面的排版设计&#x1f469;‍&#x1f393;&#xff0c;一般的网页作业需要融入以下知识点&#xff1a;div布局、浮动定位、高级css、表格、表单及验证、js轮播图、音频视频Fash的应用、uli、下拉…

[LINUX]linux基本指令大总结

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

塔望3W消费战略全案丨绿力冬瓜茶 三十年饮料老品牌,两年复兴战全国

绿力 冬瓜茶 客户&#xff1a;台湾味丹食品 上海皇品食品有限公司 品牌&#xff1a;绿力 服务&#xff1a;3W消费战略 绿力冬瓜茶品牌全案 &#xff08;2019、2020、2021、2022年度全案&#xff09; 项目背景 1992年台湾味丹食品建立上海味丹食品有限公司&#xff0c;后更…

Cadence Allegro PCB设计88问解析(二十) 之 Allegro中格点设置(一)

一个学习信号完整性仿真的layout工程师 布局是PCB设计中比较重要的一步&#xff0c;一个好的布局&#xff0c;不仅看起来美观而且也方便后期的走线&#xff0c;避免了一些信号完整性问题。在布局时就会涉及到格点设置&#xff0c;一个好的格点设置有利于器件的摆放和走线方便。…

Android—百度地图的简单使用

目录百度地图创建应用获取开发版SHA1获取发布版SHA1设置包名获得一个应用创建模块下载SKDHelloBaiDuMap1.配置AndroidManifest.xml文件2.在布局文件中添加地图容器3.地图初始化4.创建地图Activity&#xff0c;管理MapView生命周期切换地图类型百度地图 百度地图 Android SDK是…

「RocketMQ」消息的刷盘机制

刷盘策略 CommitLog的asyncPutMessage方法中可以看到在写入消息之后&#xff0c;调用了submitFlushRequest方法执行刷盘策略&#xff1a; public class CommitLog {public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {// …

计算机SCI期刊是如何审稿的? - 易智编译EaseEditing

事实上已经有部分国际期刊采用双盲评审模式进行同行评审工作&#xff0c;审稿人看不到作者信息&#xff0c;作者也无法获取审稿人信息。​​​​​​​ 双盲模式或许能保证一定的客观性&#xff0c;但一种观点是&#xff0c;就算是实施双盲评审&#xff0c;一些高水平的科学家…

燃爆一生的拿破仑有多传奇?

拿破仑波拿马&#xff0c;一个凡是学过历史的都知道的名字&#xff0c;他巅峰的时候差点统一整个欧洲&#xff0c;差点就成为欧洲的秦始皇。 他用自己的人生告诉我们一个道理&#xff1a;浓缩的&#xff0c;才是精华&#xff01; 他也向世界证明了&#xff0c;经过几个世纪之…

【Hbase】第二章——安装部署( 快速入门)

百度网盘资料如下&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1Q_OH6w1ynqrd07jfqyBDQA?pwdy69u 提取码&#xff1a;y69u 文章目录1. Zookeeper 安装2. HBase 安装部署1.1 Zookeeper正常部署1.2 Hadoop 正常部署1.3 HBase 的解压1.4 HBase 的配置文件1.5 HBase 远程…

LeetCode 704. 二分查找

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 704. 二分查找&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 二、题目要求 三…

robocode 相关的总结

基础知识 1. heading 角度系 如图所示&#xff0c;所谓heading角&#xff0c;即从Y轴出发&#xff0c;然后顺时针绕回Y轴的这么个角度区间&#xff0c;取值范围&#xff1a; [0,360] 2. bearing角度系 所谓bearing 角&#xff0c;即从Y轴顺、逆时针出发&#xff0c;绕半圈回到…

[附源码]计算机毕业设计智能衣橱APPSpringboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

svg路径动画

前言 最近在开发大屏看板&#xff0c;UI让做一个这样的效果 本来也简单&#xff0c;UI给个git动图放上就好了。但是UI给的图有四五十m&#xff0c;实在是太大了。后来想到了svg路径动画&#xff0c;之前从来没有搞过&#xff0c;就研究了下&#xff0c;由于svg没怎么研究过&a…

考试周刊杂志考试周刊杂志社考试周刊编辑部2022年第39期目录

教育教学研究《考试周刊》投稿&#xff1a;cn7kantougao163.com 新课程标准下高中体育课堂教学的有效性研究 张子龙; 1-6 减负政策下小学科学作业设计对策研究 董婷; 7-11 “双新”背景下高中通用技术学科技术试验活动教学路径分析 洪晓云; 12-16 浅析小学科学…

学习二十大奋进新征程线上知识答题活动回顾

学习二十大奋进新征程线上知识答题活动回顾 活动背景 开展直播宣讲、组织知识竞赛答题……各地通过多种形式广泛开展学习宣传活动&#xff0c;一起学。 为深入学习宣传贯彻二十大精神&#xff0c;近日&#xff0c;我市开展“奋进新征程&#xff0c;共创强国业”学习二十大精神…

大型系统技术架构之服务架构(进阶版)

原版参照系统架构之服务器架构图https://blog.csdn.net/qq_36632174/article/details/102460730?spm1001.2014.3001.5502 目录 单体架构 第一次进阶&#xff1a;应用与数据库分离 第二次进阶&#xff1a;引入本地缓存和分布式缓存 第三次进阶&#xff1a;引入反向代理实现…