欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Linux系统编程(线程)

Linux系统编程(线程)

2025/2/13 8:38:48 来源:https://blog.csdn.net/jxcklfn/article/details/145591110  浏览:    关键词:Linux系统编程(线程)

文章目录

    • 线程
      • 概念
      • 线程的基本编程操作
    • 多线程相关API
    • 线程属性获取和设置
      • 线程属性说明:

线程

概念

​ 线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源(栈),但它可与同属一个进程的其它线程共享进程所拥有的全部资源。其中包括初始化数据段(initialized data)、未初始化数据段

(uninitialized data),以及堆内存段(heap segment)。同一进程中的多个线程之间可以并发执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。在单个程序中同时运行多个线程完成不同的工作,称为多线程程序设计
在这里插入图片描述
上图中:

  • 左边是一个含有单个线程的进程,它拥有自己的一套完整的资源。
  • 右边是一个含有两条线程的进程,线程彼此间共享进程内的资源

线程的基本编程操作

1.创建

2.退出

3.取消

4.等待

5.非正常退出时,线程清理工作

多线程相关API

函数:pthread_create

头文件:#include < pthread.h>

函数原型:int pthread_create(pthread_t* threadid,const pthread_attr_t *attr,void*(*start_routine)(void*),void * arg);

功能:创建线程。在编译时注意加上-lpthread参数,以调用链接库

参数:

1.threadid:[OUT]指向线程标识符的指针。pthread_t的类型为unsigned long int

2.attr:设置线程属性。通常取值为NULL。

3.start_routin:线程运行函数的起始地址。是一个以指向void的指针作为参数和返回值的函数指针。

4.arg:运行函数的参数。也就是传给start_routine的参数。

返回值:成功则返回0,否则返回出错编号。函数在执行错误时的错误信息将作为返回值返回,并不修改系统全局变量errno,当然也无法使用perror()打印错误信息。

示例:

head.h

#ifndef __HEADER_H
#define __HEADER_H//引入最常用的头
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//fork,vfork, exec族函数, pipe, mkfifo, access, read, write, lseek, stat
//msgget , kill
#include <unistd.h>
#include <sys/types.h>
//wait, waitpid
#include <sys/wait.h>//mkfifo
#include <sys/stat.h>//open, lseek, stat, getpwuid
#include <fcntl.h>//shmget, shmxxx, msgget 
#include <sys/ipc.h>
#include <sys/shm.h>//semget, semxxx
#include <sys/sem.h>
//msgget 
#include <sys/msg.h>//信号相关的函数:signal, kill, 信号集相关的函数,sigprocmask
#include <signal.h>//线程相关的函数头
#include <pthread.h>#include <errno.h>#endif
#include <stdio.h>
#include "head.h"//错误提示函数
void my_perror(char *msg)
{if(errno <=0){return ; }char *errstr = strerror(errno);// 格式化拼接char errmsg[50] = {0};snprintf(errmsg, 50, "%s: %s\n", msg, errstr);puts(errmsg);
}//定义子线程执行的任务函数
void* routine(void* arg)
{int* p = (int*)arg;printf("%lu--routine:a = %d\n",pthread_self(),*p);
}int main(int argc, char const *argv[])
{//打印主线程idprintf("main1:%lu\n",pthread_self());int a = 10;//定义线程id的变量pthread_t t1;//调用函数创建线程(子线程)int r = pthread_create(&t1,NULL,routine,&a);if (r != 0){my_perror("pthread create failed");}//等待子线程的推出int r2 = pthread_join(t1,NULL);if (r2 != 0){my_perror("pthread join failed");}return 0;
}

在这里插入图片描述

这是添加了进程阻塞函数的
在这里插入图片描述
这是未添加了进程阻塞函数的,结果就没法执行子进程了

函数:pthread_self

头文件:#include < pthread.h>

函数原型:pthread_t pthread_self(void);

功能:获得线程自身的ID。pthread_t的类型为unsigned long int,所以在打印的时候要使用%lu方式,否则显示结果出问题。

参数:无

返回值:当前调用线程的线程ID,该函数返回值不会出错

函数:pthread_exit

头文件:#include < pthread.h>

函数原型:void pthread_exit(void* retval);

功能:终止调用它的线程并返回一个指向某个对象的指针

参数:1.retval:用户定义的指针,是线程结束时的返回值,指向某个对象的指针。

返回值:无返回值

示例:

#include <stdio.h>
#include "head.h"#define PI 3.1415
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*/void my_perror(char *msg,int errum)
{if(errum <=0){return ; }char *errstr = strerror(errum);// 格式化拼接char errmsg[50] = {0};snprintf(errmsg, 50, "%s: %s\n ", msg, errstr);puts(errmsg);
}void* task(void* arg)
{int num = *(int*)arg;//打印当前线程id和传递进来的数据printf("task:id=%lu,arg=%d\n",pthread_self(),*(int*)arg);//定义变量存储面积double area = 0;int i = 0;//遍历1~100之间的数据for (int i = 0; i < 100; i++){//计算圆形面积area = PI*i*i;if (area >= 100){//break;//调用pthread_exit()函数将数据带给主线程double* p = (double*)malloc(sizeof(double));*p = area;pthread_exit(p);}printf("i=%d\n",i);}//pthread_exit调用导致线程提前结束,下面的代码不会被执行printf("i2=%d\n",i);//通过任务函数的返回值,返回大于100的面积// double* p = (double*)malloc(sizeof(double));//*p = area;return NULL;
}int main(int argc, char const *argv[])
{printf("main1---start:id=%lu\n",pthread_self());//定义数据并传递给子线程int a = 100;//定义线程id的变量pthread_t t1;int r = pthread_create(&t1,NULL,task,&a);if (r != 0){my_perror("pthread_create",r);}//定义变量接收子线程的返回值double* retval = NULL;//等待子线程执行结束pthread_join(t1,(void**)&retval);//打印子线程返回的数据printf("main--retval:%lf\n",*retval);printf("main1---end:id=%lu\n",pthread_self());//回收堆内存free(retval);return 0;
}

在这里插入图片描述

函数:pthread_join

头文件: #include < pthread.h>

函数原型: int pthread_join(pthread_t thread, void **retval);

功能:

以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。代码中如果没有pthread_join,主线程会很快结束,从而使整个进程结束,这样会使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待,直到等待的线程结束,自己才结束,使创建的线程有机会执行。

参数:

1.thread:线程标识符,即线程ID,标识唯一线程。

2.retval:用户定义的指针,用来存储被等待线程的返回值(不为NULL时)

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

函数:pthread_cancel

头文件:#include <pthread.h>

函数原型:int pthread_cancel(pthread_t thread);

功能:发送一个取消请求给另一个正在运行的线程,使之终止该函数并不能完全保证让另一个线程退出,只是给其发送请求

函数:pthread_setcancelstate

头文件:#include <pthread.h>

函数原型:int pthread_setcancelstate(int state, int* oldstate);

功能:设置可取消状态

参数:

1.state:要设置的新的状态值,state的合法取值如下: PTHREAD_CANCEL_DISABLE,忽略cancel请求。 PTHREAD_CANCEL_ENABLE,针对目标线程的取消请求将被接受。在创建新线程时,state 默认状态为PTHREAD_CANCEL_ENABLE

2.oldstate:存储原来的状态,没有可以写NULL

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

函数:pthread_setcanceltype

头文件:#include < pthread.h>

函数原型:int pthread_setcanceltype(int type, int * oldtype);

功能:

设置线程取消类型,即允许被取消的线程在接收到取消请求后是立即中止还是在取消点终止 。在创建一个线程时,其取消类型默认设置为PTHREAD_CANCEL_DEFERRED。该函数只有在pthread_setcancelstate 中设置取消状态为 PTHREAD_CANCEL_ENABLE时才会生效。

参数:

1.type:调用线程新的可取消性。type的合法取值如下: PTHREAD_CANCEL_ASYNCHRONOUS,立即执行取消请求,退出线程。 PTHREAD_CANCEL_DEFERRED,收到取消请求后,线程继续运行,直到下一个退出点时退出。

2.oldtype:存储原来的类型。

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

函数:thread_cleanup_push/pthread_cleanup_pop

头文件:#include < pthread.h>

函数原型:void pthread_cleanup_push(void (*rtn)(void *)

void *arg);void pthread_cleanup_pop(int execute);

功能:

pthread_cleanup_push :设置线程退出时执行的清理程序,必须和pthread_cleanup_pop成对使用。且两个函数的调用次数要严格对等

参数:

1.rtn:指向线程清理函数的指针。

2.arg:传递给rtn函数的参数。

rtn将会在以下情况被执行:

1).在运行至pthread_cleanup_pop之前调用了pthread_exit 函数;

2).响应其他线程的 pthread_cancel请求;3).运行至pthread_cleanup_pop函数,并传递了非0 的参数给该函数;

3.execute: 运行至pthread_cleanup_pop函数,0 表示不执行rtn,非0表示执行rtn,但不管执行不执行都会清除rtn.

返回值:无

函数:strerror

头文件:#include <string.h>

函数原型:char *strerror(int errnum)

功能:将错误码编程字符串

参数:errnum – 错误号,通常是 errno

返回值:该函数返回一个指向错误字符串的指针,该错误字符串描述了错误 errnum

示例:

#include <stdio.h>
#include "head.h"
#include <string.h>
#include <errno.h>void my_perror(char *msg)
{char *errstr = strerror(errno);// 格式化拼接char errmsg[50] = {0};snprintf(errmsg, 50, "%s: %s\n", msg, errstr);puts(errmsg);
}int main(int argc, char const *argv[])
{int r = open("aaa/bbb", O_RDONLY);printf("num = %d\n",errno);char *str = strerror(errno);if (-1 == r){my_perror("open error");}printf("str:%s\n", str);return 0;
}

在这里插入图片描述

num = 2:是错误码

多线程执行相同函数示例:

  • 多线程在执行时,谁先执行谁后执行不确定,主要看谁抢占了CPU的执行权
#include <stdio.h>
#include "head.h"#define PI 3.1415
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*//*** 多线程在执行时,谁先执行谁后执行不确定,主要看谁抢占了CPU的执行权* */void my_perror(char *msg,int errum)
{if(errum <=0){return ; }char *errstr = strerror(errum);// 格式化拼接char errmsg[50] = {0};snprintf(errmsg, 50, "%s: %s\n ", msg, errstr);puts(errmsg);
}void* task(void* arg)
{int num = *(int*)arg;//打印当前线程id和传递进来的数据printf("task:id=%lu,arg=%d\n",pthread_self(),*(int*)arg);//定义变量存储面积double area = 0;int i = 0;//遍历1~100之间的数据for (int i = 0; i < 100; i++){//计算圆形面积area = PI*i*i;if (area >= 100){//break;//调用pthread_exit()函数将数据带给主线程double* p = (double*)malloc(sizeof(double));*p = area;pthread_exit(p);}printf("i=%d\n",i);}//pthread_exit调用导致线程提前结束,下面的代码不会被执行printf("i2=%d\n",i);//通过任务函数的返回值,返回大于100的面积// double* p = (double*)malloc(sizeof(double));//*p = area;return NULL;
}//两个子线程执行同一个任务函数
int main(int argc, char const *argv[])
{printf("main1---start:id=%lu\n",pthread_self());//定义数据并传递给子线程int a = 100;//定义线程id的变量pthread_t t1;int r = pthread_create(&t1,NULL,task,&a);if (r != 0){my_perror("pthread_create",r);}//定义线程id的变量pthread_t t2;int r1 = pthread_create(&t2,NULL,task,&a);if (r1 != 0){my_perror("pthread_create",r);}//定义变量接收子线程的返回值double* retval = NULL;//等待子线程执行结束pthread_join(t1,(void**)&retval);//打印子线程返回的数据printf("main1-----t1-retval:%lf\n",*retval);//等待子线程执行结束pthread_join(t2,(void**)&retval);//打印子线程返回的数据printf("main2----t2--retval:%lf\n",*retval);printf("main---end:id=%lu\n",pthread_self());//回收堆内存free(retval);return 0;
}

在这里插入图片描述
多线程执行不同函数示例:

#include <stdio.h>
#include "head.h"#define PI 3.1415
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*//*** 多线程在执行时,谁先执行谁后执行不确定,主要看谁抢占了CPU的执行权* */void my_perror(char *msg,int errum)
{if(errum <=0){return ; }char *errstr = strerror(errum);// 格式化拼接char errmsg[50] = {0};snprintf(errmsg, 50, "%s: %s\n ", msg, errstr);puts(errmsg);
}void* fun2(void* arg)
{printf("fun2----\n");return  NULL;
}void* task(void* arg)
{int num = *(int*)arg;//打印当前线程id和传递进来的数据printf("task:id=%lu,arg=%d\n",pthread_self(),*(int*)arg);//定义变量存储面积double area = 0;int i = 0;//遍历1~100之间的数据for (int i = 0; i < 100; i++){//计算圆形面积area = PI*i*i;if (area >= 100){//break;//调用pthread_exit()函数将数据带给主线程double* p = (double*)malloc(sizeof(double));*p = area;pthread_exit(p);}printf("i=%d\n",i);}//pthread_exit调用导致线程提前结束,下面的代码不会被执行printf("i2=%d\n",i);//通过任务函数的返回值,返回大于100的面积// double* p = (double*)malloc(sizeof(double));//*p = area;return NULL;
}//两个子线程执行同一个任务函数
int main(int argc, char const *argv[])
{printf("main1---start:id=%lu\n",pthread_self());//定义数据并传递给子线程int a = 100;//定义线程id的变量pthread_t t1;int r = pthread_create(&t1,NULL,task,&a);if (r != 0){my_perror("pthread_create",r);}//定义线程id的变量pthread_t t2;int r1 = pthread_create(&t2,NULL,fun2,&a);if (r1 != 0){my_perror("pthread_create",r);}//定义变量接收子线程的返回值double* retval = NULL;//等待子线程执行结束pthread_join(t1,(void**)&retval);//打印子线程返回的数据printf("main1-----t1-retval:%lf\n",*retval);//等待子线程执行结束pthread_join(t2,(void**)&retval);//打印子线程返回的数据//printf("main2----t2--retval:%lf\n",*retval);printf("main---end:id=%lu\n",pthread_self());//回收堆内存free(retval);return 0;
}

在这里插入图片描述

线程属性获取和设置

线程有许多属性,可以在终端中查看跟线程属性相关的函数

man pthread_attr_
敲入如下命令后连续按两下tab键

在这里插入图片描述
线程的属性多种多样,可以归总为如下表格:

获取属性API功能设置属性API功能
pthread_attr_getdetachstate( )获取分离属性pthread_attr_setdetachstate( )设置分离属性
pthread_attr_getguardsize( )获取栈警戒区大小pthread_attr_setguardsize( )设置栈警戒区大小
pthread_attr_getinheritsched( )获取继承策略pthread_attr_setinheritsched( )设置继承策略
pthread_attr_getschedpolicy( )获取调度策略pthread_attr_setschedpolicy( )设置调度策略
pthread_attr_getschedparam( )获取调度参数pthread_attr_setschedparam( )设置调度参数
pthread_attr_getscope( )获取竞争范围pthread_attr_setscope( )设置竞争范围
pthread_attr_getaffinity_np( )获取CPU亲和度pthread_attr_setaffinity_np( )设置CPU亲和度
pthread_attr_getstack( )获取栈指针和栈大小pthread_attr_setstack( )设置栈的位置和栈大小
pthread_attr_getstacksize( )获取栈大小pthread_attr_setstacksize( )设置栈大小

这些属性可以在创建线程的时候,通过属性变量统一设置,有少部分可以在线程运行之后再进行设置(比如分离属性

线程属性说明:

  1. pthread_attr_t 结构体

线程属性结构如下:

typedef struct

{

int detachstate;

//线程的分离状态

int schedpolicy;

//线程调度策略

struct sched_param schedparam; //线程的调度

参数

int inheritsched;

//线程的继承性

int scope;

//线程的作用域

size_t guardsize;

//线程栈尾警戒缓冲区大小

int stackaddr_set;

void * stackaddr; //

线程栈的位置

size_t stacksize;

// 线程栈的大小

}pthread_attr_t;

**detachstate, **线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是

非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join() 函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是如果对一些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我 们就无须等待管理,只要线程自己结束了,自己释放资源就可以。

detachstate有两种取值:

PTHREAD_CREATE_DETACHED: 分离状态启动

PTHREAD_CREATE_JOINABLE: 正常启动线程

schedpolicy,表示新线程的调度策略,主要包括:

SCHED_OTHER(正常、非实时)

SCHED_RR(实时、轮转法)

SCHED_FIFO(实时、先入先出)

缺省为SCHED_OTHER,

schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示

线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,

/usr/include /bits/sched.h

struct sched_param

{

int sched_priority; //!> 参数的本质就是优先级

};

注意:大的权值对应高的优先级!

系统支持的最大和最小的优先级值可以用函数:

sched_get_priority_max和sched_get_priority_min得到!

inheritsched,继承性决定调度的参数是从创建的进程中继承还是使用在

schedpolicy和schedparam属性中显式设置的调度信息,

可设置参数:

PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数!

PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于

schedpolicy和schedparam属

性中显式

设置的调度信息!

scope,表示线程间竞争资源的范围

POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,

目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

scope有两种取值:

PTHREAD_SCOPE_SYSTEM: 与系统中所有线程一起竞争资源 PTHREAD_SCOPE_PROCESS:

仅与同进程中的线程竞争CPU

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com