错误码与处理
错误码和错误信息字符串的对应关系
C 标准库 - <errno.h>
简介
C 标准库的 errno.h 头文件定义了整数变量 errno,它是通过系统调用设置的,在错误事件中的某些库函数表明了什么发生了错误。该宏扩展为类型为 int 的可更改的左值,因此它可以被一个程序读取和修改。
<errno.h> 是 C 标准库中的一个头文件,提供了一种在程序中报告和处理错误的机制。这个头文件定义了宏和变量,用于指示和描述运行时错误的类型。
errno.h 头文件定义了一系列表示不同错误代码的宏,这些宏应扩展为类型为 int 的整数常量表达式。
errno 是一个全局变量,用于存储最近发生的错误代码。这个变量的类型为 int。当一个库函数发生错误时,它通常会设置 errno 以指示错误类型。
例如,在文件操作中,如果 fopen 函数因为文件不存在而失败,它会将 errno 设置为 ENOENT。
线程相关函数运行正常返回 0,运行错误返回错误码,没有设置全局变量 errno, 需要我们自己
封装函数处理错误码
示例代码
c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "header.h"
void my_perror(char* msg, int errnum)
{
char* err_str = strerror(errnum);
//格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str );
puts(errMsg);
}
void my_perror2(char* msg)
{
char* err_str = strerror(errno);
//格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str );
puts(errMsg);
}
int main(int argc, char const *argv[])
{
int r = open("aaa/bbb", O_RDONLY);
printf("num=%d\n", errno);
my_perror("open err", errno);
my_perror2("open error");
if (r == -1)
{
perror("open failed");
return -1;
}
// open failed: No such file or directory
/*
No such file or directory 2
*/
// char* str = strerror(errno);
// printf("str: %s\n", str);
return 0;
}
线程
概念
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源(栈),但它可与同属一个进程的其它线程共享进程所拥有的全部资源。同一进程中的多个线程之间可以并发执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。在单个程序中同时运行多个线程完成不同的工作,称为多线程程序设计。
使用理由
1.和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间,据统计一个进程的开销大概是一个线程开销的30倍左右。
2.线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,比如线程间同步和互斥。
线程的基本编程操作
1.创建
线程创建实际上就是在确定线程函数调用的入口地址。
具体实现: pthread_create 函数
- 退出
线程创建完成后开始执行,执行完成后就退出了,但还有一种退出线程的方法
具体实现: pthread_exit 函数
- 取消
在很多线程应用中,经常会遇到在一个线程中要终止另一个线程的问题,此时就需要调用 pthread_cancel 函数来取消另一个线程,但在被取消的线程内部需要调用 pthread_setcancelstate 函数和 pthread_setcanceltype
4.等待
由于在一个进程中,线程是共享数据段的,退出线程所占用的资源并不会随着线程终止而得到释放,正如进程用wait来等待终止并释放资源一样,线程也有类似机制,
具体实现 pthread_join函数
5.非正常退出时,线程清理工作:
一般来说,线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。
不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。
pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源
线程相关函数基本使用
pthread_self()在那个线程中,即获取那个线程的id并返回
pthread_create的形式参数:
arg1: 输出型参数,返回当前创建的线程id arg2: 创建该线程要设置或者获取线程的属性,
如果不关注属性,可以给NULL arg3: 线程要执行的任务函数 arg4: 任务函数执行时的实
际参数----通过该参数可以实现线程间的数据传递(主线程数据传递给子线程) 返回值需要
使用pthread_join的第二个参数获取---实现线程间的数据传递(子线程的数据传递给主线程)
代码示例
c
#include <stdio.h>
#include "header.h"
/*
pthread_self()在那个线程中,即获取那个线程的id并返回
pthread_create的形式参数:
arg1: 输出型参数,返回当前创建的线程id
arg2: 创建该线程要设置或者获取线程的属性,如果不关注属性,可以给NULL
arg3: 线程要执行的任务函数
arg4: 任务函数执行时的实际参数----通过该参数可以实现线程间的数据传递(主线程数据传递给子线程)
返回值需要使用pthread_join的第二个参数获取---实现线程间的数据传递(子线程的数据传递给主线程)
*/
void my_perror(char *msg, int errnum)
{
if (errnum <= 0)
{
return;
}
char *err_str = strerror(errnum);
// 格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str);
puts(errMsg);
}
// 定义子线程执行的任务函数
void *routine(void *arg)
{
int *p = (int *)arg;
printf("%lu--routine: a=%d\n", pthread_self(), *p);
/*
注意:不能返回局部变量的地址
1.可以返回堆内存空间的地址
2.返回静态局部变量的地址----静态变量生命周期太长,建议少用
*/
double b = 123.321;
// 申请堆内存
double *retval = (double *)calloc(1, sizeof(double));
// 转存数据
*retval = b;
return retval;
}
int main(int argc, char const *argv[])
{
// 打印主线程id
printf("main1: %lu\n", pthread_self());
int a = 10;
// 定义线程id的变量
pthread_t t1;
// 调用函数创建线程(子线程)
int r = pthread_create(&t1, NULL, routine, &a);
printf("t1=%lu\n", t1);
if (r != 0)
{
my_perror("pthread create failed", r);
}
// 等待子线程的退出
// int r2 = pthread_join(t1, NULL);
double* pvalue = NULL;
int r2 = pthread_join(t1, (void**)&pvalue);
//打印子线程返回给主线程的数据
printf("value=%lf\n", *pvalue);
my_perror("pthread_join failed", r2);
//释放堆内存空间
free(pvalue);
return 0;
}
指针函数不能返回局部变量的地址
- 可以返回堆内存空间的地址
- 返回静态局部变量的地址----静态变量生命周期太长,建议少用
任务函数中提前结束线程
//调用pthread_exit()函数结束当前线程,并携带数据给其他线程 //pthread_join等待子线程执
行结束, 并用第二个参数获取线程结束携带的数据
示例代码
c
#include <stdio.h>
#include "header.h"
#define PI 3.1415
void my_perror(char *msg, int errnum)
{
if (errnum <= 0)
{
return;
}
char *err_str = strerror(errnum);
// 格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str);
puts(errMsg);
}
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*/
void* task(void* arg)
{
int num = *(int*)arg;
//打印当前线程id和传递进来的数据
printf("task: id=%lu, *arg=%d\n", pthread_self(), num);
//定义变量存储面积
double area = 0;
//遍历1~100之间的数据
int i;
for(i = 1; i<=num; i++)
{
//计算圆形面积
area = PI * i * i;
if (area > 100)
{
//break;
//通过任务函数的返回值,返回大于100的面积
double * p = (double*)malloc(sizeof(double));
*p = area;
//调用pthread_exit()函数结束当前线程,并携带数据给其他线程
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 p;
*/
return NULL;
}
int main(int argc, char const *argv[])
{
printf("main-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 failed", r);
return -1;
}
//定义变量接收子线程的返回值
double* retval = NULL;
//等待子线程执行结束
pthread_join(t1, (void**)&retval);
//打印子线程返回的数据
printf("main--*retval: %lf\n", *retval);
//回收堆内存
free(retval);
printf("main-end: id=%lu\n", pthread_self());
return 0;
}
多线程执行的特点
创建多个线程,可以执行同一个任务函数,也可以执行不同的任务函数 多线程在执行时,谁
先执行谁后执行不确定。主要看谁抢占到了CPU的执行权
取消点有哪些呢?
1:通过pthread_testcancel 调用已编程方式建立线程取消点
2:线程等待pthread_cond_wait或pthread_cond_timewait中的
特定条件
3:被sigwait阻塞的函数
4:一些标准的库调用。通常这些调用包括线程可基于阻塞的函数
根据POSIX标准
pthread_join、pthread_testcancel,pthread_cond_wait、
pthread_cond_timedwait、sem_wait、sigwait等函数以及
read()、write()等会引起阻塞的系统调用都是取消点.
c
#include <stdio.h>
#include "header.h"
#define PI 3.1415
/*
多线程在执行时,谁先执行谁后执行不确定。主要看谁抢占到了CPU的执行权
*/
void my_perror(char *msg, int errnum)
{
if (errnum <= 0)
{
return;
}
char *err_str = strerror(errnum);
// 格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str);
puts(errMsg);
}
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*/
void *task(void *arg)
{
int num = *(int *)arg;
// 打印当前线程id和传递进来的数据
printf("task: id=%lu, *arg=%d\n", pthread_self(), num);
// 定义变量存储面积
double area = 0;
// 遍历1~100之间的数据
int i;
for (i = 1; i <= num; i++)
{
// 计算圆形面积
area = PI * i * i;
if (area > 100)
{
// break;
// 通过任务函数的返回值,返回大于100的面积
double *p = (double *)malloc(sizeof(double));
*p = area;
// 调用pthread_exit()函数结束当前线程,并携带数据给其他线程
pthread_exit(p);
}
printf("id=%lu, i=%d\n", pthread_self(), i);
}
// pthread_exit函数调用导致线程提前结束,下面的代码不会被执行
printf("i2=%d\n", i);
/*
//通过任务函数的返回值,返回大于100的面积
double * p = (double*)malloc(sizeof(double));
*p = area;
return p;
*/
return NULL;
}
//多个线程执行不同的函数
void* fun2(void* arg)
{
printf("fun2.....\n");
return NULL;
}
//两个子线程执行同一个任务函数
int main(int argc, char const *argv[])
{
printf("main-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 failed", r);
return -1;
}
// 定义线程id的变量
pthread_t t2;
int r2 = pthread_create(&t2, NULL, task, &a);
if (r2 != 0)
{
my_perror("pthread_create failed", r);
return -1;
}
printf("t1=%lu, t2=%lu\n", t1, t2);
// 定义变量接收子线程的返回值
double *retval = NULL;
// 等待子线程执行结束
pthread_join(t1, (void **)&retval);
// 打印子线程返回的数据
printf("main--t1--*retval: %lf\n", *retval);
// 等待子线程执行结束
pthread_join(t2, (void **)&retval);
// 打印子线程返回的数据
printf("main--t2--*retval: %lf\n", *retval);
// 回收堆内存
free(retval);
printf("main-end: id=%lu\n", pthread_self());
return 0;
}
这段代码是一个多线程程序。主要的逻辑是创建两个子线程,每个子线程执行一个任务函数task。任务函数的功能是打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止。子线程执行完任务后,通过pthread_exit函数结束线程,并返回面积值。
在主函数中,首先创建了两个子线程t1和t2并分别调用任务函数task。然后通过pthread_join函数等待子线程执行结束,并接收子线程的返回值。最后,打印子线程返回的面积值,并释放堆内存。
需要注意的是,多线程在执行时,谁先执行谁后执行是不确定的,主要取决于谁抢占到了CPU的执行权。因此,在打印子线程返回的面积值时,可能是先打印t1的面积值,再打印t2的面积值,也可能反过来。
另外,在任务函数task中,如果面积大于100,使用动态内存分配返回面积值,并通过pthread_exit函数结束线程。如果不大于100,任务函数会继续执行直到循环结束。
线程的取消特点
多线程执行时,默认情况下:pthread_cancel它的执行把握不住:不能确定它是否有效 可
以在任务函数中调用 pthread_setcancelstate函数设置取消状态: pthread_setcancelstate
(PTHREAD_CANCEL_DISABLE, NULL); --- 设置任务的取消状态:不可取消--
PTHREAD_CANCEL_DISABLE pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); --- 设置任务的取消状态:可取消---PTHREAD_CANCEL_ENABLE (默认值)
c
#include <stdio.h>
#include "header.h"
#define PI 3.1415
/*
多线程在执行时,谁先执行谁后执行不确定。主要看谁抢占到了CPU的执行权
*/
void my_perror(char *msg, int errnum)
{
if (errnum <= 0)
{
return;
}
char *err_str = strerror(errnum);
// 格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str);
puts(errMsg);
}
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*/
void *task(void *arg)
{
//设置任务的取消状态:不可取消---PTHREAD_CANCEL_DISABLE
//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
//设置任务的取消状态:可取消---PTHREAD_CANCEL_ENABLE(默认值)
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
int num = *(int *)arg;
// 打印当前线程id和传递进来的数据
printf("task: id=%lu, *arg=%d\n", pthread_self(), num);
// 定义变量存储面积
double area = 0;
// 遍历1~100之间的数据
int i;
for (i = 1; i <= num; i++)
{
// 计算圆形面积
area = PI * i * i;
if (area > 100)
{
// break;
// 通过任务函数的返回值,返回大于100的面积
double *p = (double *)malloc(sizeof(double));
*p = area;
// 调用pthread_exit()函数结束当前线程,并携带数据给其他线程
pthread_exit(p);
}
printf("id=%lu, i=%d\n", pthread_self(), i);
}
// pthread_exit函数调用导致线程提前结束,下面的代码不会被执行
printf("i2=%d\n", i);
/*
//通过任务函数的返回值,返回大于100的面积
double * p = (double*)malloc(sizeof(double));
*p = area;
return p;
*/
return NULL;
}
//多个线程执行不同的函数
void* fun2(void* arg)
{
printf("fun2.....\n");
return NULL;
}
//两个子线程执行同一个任务函数
int main(int argc, char const *argv[])
{
printf("main-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 failed", r);
return -1;
}
// 定义线程id的变量
pthread_t t2;
int r2 = pthread_create(&t2, NULL, task, &a);
if (r2 != 0)
{
my_perror("pthread_create failed", r);
return -1;
}
printf("t1=%lu, t2=%lu\n", t1, t2);
//调用pthread_cancel函数取消线程
//多线程执行时,默认情况下:pthread_cancel它的执行把握不住:不能确定它是否有效
pthread_cancel(t1);
pthread_cancel(t2);
// 定义变量接收子线程的返回值
double *retval = NULL;
// 等待子线程执行结束
pthread_join(t1, (void **)&retval);
// 打印子线程返回的数据
//printf("main--t1--*retval: %lf\n", *retval);
// 等待子线程执行结束
pthread_join(t2, (void **)&retval);
// 打印子线程返回的数据
//printf("main--t2--*retval: %lf\n", *retval);
// 回收堆内存
//free(retval);
printf("main-end: id=%lu\n", pthread_self());
return 0;
}
这段代码主要是通过多线程计算圆形的面积,当面积大于100时停止计算。代码中定义了一个任务函数task,该函数通过传入的参数计算圆形的面积,当面积大于100时,使用pthread_exit函数结束当前线程,并通过返回值返回面积值给主线程。主函数中创建了两个子线程,分别执行任务函数task,并通过pthread_cancel函数取消子线程的执行。之后使用pthread_join函数等待子线程执行结束,并获取子线程的返回值打印出来。
需要注意的是,在子线程中调用pthread_exit函数结束线程时,需要动态分配内存来存储返回值,否则主线程无法获取到正确的返回值。此外,在主函数中还定义了一个自定义错误处理函数my_perror,用于打印错误信息。
在多线程执行时,并不能确定哪个线程会先执行,因为线程的执行是由CPU抢占执行权决定的。
线程属性获取和设置
可以在 pthread_create 函数之前设置线程的属性,也可以在该函数之后获取线程的属性 设置
属性的函数:pthread_attr_setxxx , 获取属性值的函数:pthread_attr_getxxx, xxx 表示属性的名
称,具体看下图和 txt 文档:
c
#include <stdio.h>
#include "header.h"
#define PI 3.1415
/*
多线程在执行时,谁先执行谁后执行不确定。主要看谁抢占到了CPU的执行权
*/
void my_perror(char *msg, int errnum)
{
if (errnum <= 0)
{
return;
}
char *err_str = strerror(errnum);
// 格式化拼接
char errMsg[50] = {0};
snprintf(errMsg, 50, "%s: %s\n", msg, err_str);
puts(errMsg);
}
/*
子线程中打印半径为1~100之间的整数,并计算圆形的面积,当面积大于100时停止
*/
/*
在创建线程之前可以设置线程的属性
detachstate:
0 : 非分离状态:主线程需要调用join函数等待子线程的执行结束;否则当主线程执行结束时,会先终止子线程,然后退出;(主线程会处理子线程回收子线程的资源)
1 : 分离状态: 主线程和子线程没有关联关系;子线程自己会执行完毕,子线程自己回收资源;
注意:
当主线程执行完毕后,主进程会退出,进程的虚拟内存空间会被销毁,整个程序就结束了
所有的线程共享进程的虚拟内存空间,当进程虚拟内存空间被销毁,所有线程数据也无法保留,线程也就结束了
导致:
子线程没有执行完就停止了
子线程执行了,但看不到执行的效果,数据仍然是残缺的----线程创建不用分离属性
当一个进程执行时,系统默认会打开三个文件描述符:0,1,2
*/
void *task(void *arg)
{
for(int i = 0; i<20; i++)
{
printf("task: i=%d\n", i);
}
return NULL;
}
// 多个线程执行不同的函数
void *task2(void *arg)
{
for(int i = 0; i<20; i++)
{
printf("task2: i=%d\n", i);
}
return NULL;
}
void *task3(void *arg)
{
for(int i = 0; i<20; i++)
{
printf("task3: i=%d\n", i);
}
return NULL;
}
//设置线程的属性
void set_attr2(pthread_attr_t* attr)
{
struct sched_param param = {0};
param.sched_priority = 10;
pthread_attr_setschedparam(attr, ¶m);
pthread_attr_setschedpolicy(attr, SCHED_FIFO);
}
void set_attr3(pthread_attr_t* attr)
{
struct sched_param param = {0};
param.sched_priority = 99;
pthread_attr_setschedparam(attr, ¶m);
pthread_attr_setschedpolicy(attr, SCHED_FIFO);
}
// 两个子线程执行同一个任务函数
int main(int argc, char const *argv[])
{
printf("main-start: id=%lu\n", pthread_self());
pthread_attr_t attr = {0};
//设置分离属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); //非分离属性
//pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//分离属性
//设置调度策略
// pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
// pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
//设置调用参数---优先级
struct sched_param param = {0};
param.sched_priority = 50;
pthread_attr_setschedparam(&attr, ¶m);
//设置线程的作用域:system. precess
// pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS);
pthread_t t1, t2, t3;
int r = pthread_create(&t1, &attr, task, NULL);
pthread_attr_t attr2 = {0};
set_attr2(&attr2);
pthread_create(&t2, &attr2, task2, NULL);
pthread_attr_t attr3 = {0};
set_attr3(&attr3);
pthread_create(&t3, &attr3, task3, NULL);
//获取分离属性
int state = -1;
pthread_attr_getdetachstate(&attr, &state);
printf("state: %d\n", state);
//获取线程的调用策略: 多个线程执行的先后顺序
int policy = -2;
pthread_attr_getschedpolicy(&attr, &policy);
printf("policy: %d\n", policy);
//获取调度参数:sched_priority线程执行的优先级:CPU执行的可能性
//线程的优先级更高,CPU执行这个线程的可能性更大,但不一定就先执行完某个优先级高的才执行某个优先级低的
//struct sched_param param = {0};
pthread_attr_getschedparam(&attr, ¶m);
printf("sched_priority: %d\n", param.sched_priority);
int max = sched_get_priority_max(policy);
int min = sched_get_priority_min(policy);
printf("min=%d, max=%d\n", min, max);
int scope = -3;
pthread_attr_getscope(&attr, &scope);
printf("scope=%d\n",scope);
if (r!=0)
{
my_perror("pthread_create failed", r);
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
printf("main-end: id=%lu\n", pthread_self());
return 0;
}
这段代码主要是多线程的使用示例代码。首先定义了一个函数my_perror用于打印错误信息。然后定义了三个任务函数task、task2、task3,分别在子线程中执行。接下去是main函数,其中创建了三个线程t1、t2、t3,分别执行任务函数task、task2、task3。在创建线程之前,对线程的属性进行了设置,包括分离属性、调度策略、调度参数和作用域。然后使用pthread_create函数创建线程,并使用pthread_join函数等待线程的执行结束。最后打印出主线程的ID。