Linux系统编程(十一)线程、线程控制

线程

一、线程概念:

线程概念

  • ps -eLf 查看线程号(cpu 执行的最小单位)

ps_Lf

最小执行单元

二、Linux内核线程实现原理

线程实现原理

三、三级映射(三级页表)

进程PCB-->页面(可看成数组,首地址位于PCB中)--》页表--》页目录(物理页表)--》内存单元

参考:《Linux内核源代码情景分析》 ---毛德操

三级映射

线程实现原理-2

四、线程共享资源

线程共享资源

五、线程非共享资源

线程非共享资源

六、线程优缺点

线程优缺点

线程控制原语

一、pthread_self函数

线程ID:用来表示线程身份的id号

线程号(LWP):内核用来将线程作为进程看待

pthread_t pthread_self(void);	
	
	获取线程id。 线程id是在进程地址空间内部,用来标识线程身份的id号。

	返回值:本线程id(unsigned long类型)

二、pthread_create函数

int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*start_rountn)(void *), void *arg); //创建子线程。

	参1:传出参数,表新创建的子线程 id

	参2:线程属性。传NULL表使用默认属性。(线程状态:大小、优先级等)

	参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
	
	参4:参3的参数。没有的话,传NULL

	返回值:成功:0

		失败:返回errno
	
	编译时需要引入动态库 -lpthread

示例1:循环创建N个子线程:

void *tfn(void* arg)
{
        int i = (int)arg;
        sleep(i);
        if(i == 2)
         pthread_exit(NULL);//退出当前线程
        printf("--%dth pthread,pid=%d,tid=%lu\n", i+1,getpid(),pthread_self());
        return NULL;
}
int main(int argc, char* argv[])
{
        int i;
        int ret;
        pthread_t tid;
        for(i=0; i<5; i++)
        {
                ret = pthread_create(&tid,NULL,tfn,(void *)i);// 栈是不共享的,将 int 类型 i, 强转成 void *, 传参采用值传递
                if(ret != 0)
                {
                        perror("pthread_create error");
                        exit(1);
                }
        }
        sleep(i);
        printf("main,pid=%d,tid=%lu\n", getpid(),pthread_self());
        return 0;
}

循环创建子线程-错误分析

示例2:线程共享全局变量

线程默认共享数据段、代码段等地址空间,常用的是全局变量。

进程不共享全局变量,只能借助mmap

int var = 100;
void* ftn(void* arg)
{
        printf("child,pid=%d, tid=%lu, var=%d\n", getpid(),pthread_self(),var);
        var = 200;
        pthread_exit(NULL);
}
int main(int argc, char* argv[])
{
        pthread_t tid;
        int ret;

        ret = pthread_create(&tid,NULL,ftn,NULL);
        if(ret!=0)
        {
                perror("pthread_create error");
                exit(1);
        }
        sleep(1);
        printf("main,var = %d\n",var);//全局变量共享
        return 0;
}

三、pthread_exit函数

void pthread_exit(void *retval);  退出当前线程。

	retval:退出值:返回线程,通常传NULL。
	
三种退出的区别:

	exit();	退出当前进程。//exit(0)表示正常退出

	return: 返回到函数调用者那里去。

	pthread_exit(): 退出当前线程。

注意:
	
	(1)多线程环境中,应尽量不使用exit函数,应该使用pthread_exit函数。
	
	(2)其它线程未结束,主线程不能return或exit。
	
	(3)在子线程中使用pthread_exit或return返回的指针所指向的内存单元必须是全局的或者malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时,线程函数已经退出了。

四、pthread_join函数

int pthread_join(pthread_t thread, void **retval); //阻塞等待并回收线程,获取线程退出状态。

	thread: 待回收的线程id

	retval:传出参数。 回收的子线程的return退出值。

		线程异常借助,值为 -1。

	返回值:成功:0

		失败:errno

示例1:pthread_join函数的使用及注意事项

//pthread_join.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

struct thrd {
    int var;
    char str[256];
};

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}
/*
void *tfn(void *arg)
{
    struct thrd *tval;

    tval = malloc(sizeof(tval));//1、在堆区创建变量
    tval->var = 100;
    strcpy(tval->str, "hello thread");

    return (void *)tval;
}
*/
/*
void *tfn(void *arg)
{
     struct thrd tval;              //2、局部变量地址,不可做返回值

    tval.var = 100;
    strcpy(tval.str, "hello thread");

    return (void *)&tval;
}
*/ 
void *tfn(void *arg)
{
    struct thrd *tval = (struct thrd *)arg;

    tval->var = 100;
    strcpy(tval->str, "hello thread");

    return (void *)tval;
}

int main(int argc, char *argv[])
{
    pthread_t tid;

    struct thrd arg;//3、可以在main函数创建变量
    struct thrd *retval;

    int ret = pthread_create(&tid, NULL, tfn, (void *)&arg);
    if (ret != 0)
        sys_err("pthread_create error");

    //int pthread_join(pthread_t thread, void **retval);
    ret = pthread_join(tid, (void **)&retval);
    if (ret != 0)
        sys_err("pthread_join error");

    printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);
    
    pthread_exit(NULL);

}

示例2:使用pthread_join函数将循环创建的多个子线程回收(使用数组)

//pthrd_loop_join.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

int var = 100;

void *tfn(void *arg)
{
    int i;
    i = (int)arg;
    
    sleep(i);
    if (i == 1) {
        var = 333;
        printf("var = %d\n", var);
        return (void *)var;

    } else  if (i == 3) {
        var = 777;
        printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);
        pthread_exit((void *)var);

    } else  {
        printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);
        pthread_exit((void *)var);
    }

    return NULL;
}

int main(void)
{
    pthread_t tid[5];
    int i;
    int *ret[5];  

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i], NULL, tfn, (void *)i);

    for (i = 0; i < 5; i++) {
        pthread_join(tid[i], (void **)&ret[i]);
        printf("-------%d 's ret = %d\n", i, (int)ret[i]);
    }
        
    printf("I'm main pthread tid = %lu\t var = %d\n", pthread_self(), var);

    sleep(i);
    return 0;
}

五、pthread_detach函数

int pthread_detach(pthread_t thread);		设置线程分离

	thread: 待分离的线程id

	返回值:成功:0

		失败:errno	--- 线程报错直接返回错误号,不能使用perror,需使用strerror函数。

(1)线程分离状态:指定该状态,线程主动与主线程断开关系。线程结束后,其退出状态不由其它线程获取,而直接自己自动释放。网络、多线程服务器常用。

(2)进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统重,导致内核认为该进程仍存在。

(3)也可使用`pthread_create`函数的参2(线程属性)来设置线程分离。

(4)不能对一个已经处于detach状态的线程调用`pthread_join`,这样的调用将返回`EINVAL`无效错误。

(5)使用pthread_join回收失败,说明分离成功

示例:pthread_detach的使用

//pthread_detach.c
void *tfn(void *arg)
{
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, tfn, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
        exit(1);
    }
    ret = pthread_detach(tid);              // 设置线程分离` 线程终止,会自动清理pcb,无需回收
    if (ret != 0) {
        fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
        exit(1);
    }

    sleep(1);

    ret = pthread_join(tid, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
        exit(1);
    }

    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());

    pthread_exit((void *)0);
}

六、pthread_cancel函数

int pthread_cancel(pthread_t thread);//杀死一个线程。

	thread: 待杀死的线程id
	
	返回值:成功:0,成功被 pthread_cancel()杀死的线程,无法使用pthread_join回收,返回 #define PTHREAD_CANDELED ((void *)-1)(表示非正常死亡)。使用pthead_join 回收

		失败:errno
		
注意:

	(1)线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。如果,子线程没有到达取消点, 那么 pthread_cancel 无效。

	(2)** 可粗略认为一个系统调用(进入内核)即为一个取消点。

	(3)我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();

示例:pthread_cancel的使用

//pthread_cancel.c
void* ftn(void* arg)
{
        while(1)
        {
                printf("child,tid=%lu\n", pthread_self());
                sleep(1);
                pthread_testcancel();//可以使用这个函数进行测试,进内核
        }
}

int main(int argc, char* argv[])
{
        pthread_t tid;
        int ret;
        ret = pthread_create(&tid,NULL,ftn,NULL);
        if(ret != 0)
        {
                fprintf(stderr,"pthread_create err:%s",strerror(ret));
        }
        sleep(5);
        ret = pthread_cancel(tid);//需要到达一个取消点才能杀死线程,进内核
        if(ret != 0)
        {
                fprintf(stderr, "pthread_cancel err:%s", strerror(ret));
        }
         pthread_join(tid,&tret);
         printf("pthread_join,tret=%d\n",(int)tret);//cancel后,无法使用join回收,返回-1

        return 0;
}

七、检查错误返回

检查出错返回:  线程中,只能使用strerror函数

fprintf(stderr, "xxx error: %s\n", strerror(ret));

八、进程和线程控制原语对比

线程控制原语进程控制原语用途
pthread_create()fork();创建
pthread_self()getpid();获取
pthread_exit()exit()/return退出
pthread_join()wait()/waitpid()回收
pthread_cancel()kill()杀死
pthread_detach()分离

九、设置线程属性(通过函数设置属性)

线程属性

pthread_attr_t attr  	创建一个线程属性结构体变量

pthread_attr_init(pthread_attr_t *attr);	初始化线程属性

pthread_attr_getdetachstate(pthread_attr_t *attr, int detachstate);//获取线程属性

pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//设置线程属性

detachstate:
	
	PTHREAD_CREATE_DETACHED--分离线程
	
	PTHREAD_CREATE_JOINABLE--非分离线程(默认)

pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程

pthread_attr_destroy(pthread_attr_t *attr);	销毁线程属性

示例:pthread_create中分离属性的使用

//pthrd_attr_detach.c
void *tfn(void *arg)
{
    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());

    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid;

    pthread_attr_t attr;

    int ret = pthread_attr_init(&attr);
    if (ret != 0) {
        fprintf(stderr, "attr_init error:%s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);      // 设置线程属性为 分离属性
    if (ret != 0) {
        fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_create(&tid, &attr, tfn, NULL);
    if (ret != 0) {
        perror("pthread_create error");
    }

    ret = pthread_attr_destroy(&attr);
    if (ret != 0) {
        fprintf(stderr, "attr_destroy error:%s\n", strerror(ret));
        exit(1);
    }

    ret = pthread_join(tid, NULL);
    if (ret != 0) {
        fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
        exit(1);
    }

    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());

    pthread_exit((void *)0);
}

线程同步问题: 如果设置一个线程为分离线程,而这个线程运行又非常快,它可能在pthread_create函数返回之前就终止了;它终止之后可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,设置一段等待时间,是在多线程编程里常用的方法。但注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

十、线程使用注意事项

线程使用注意事项
多线程fork进程

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

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

相关文章

Silanna UV光荣推出了一款革命性的高功率远紫外线LED

这款令人瞩目的光源&#xff0c;拥有令人震撼的235nm波长&#xff0c;并被巧妙地封装在紧凑的6.8mm结构中&#xff0c;其魅力与实力兼具。 今年六月&#xff0c;在苏格兰圣安德鲁斯大学举行的盛大2024年远紫外科学和技术国际大会&#xff08;ICFUST&#xff09;上&#xff0c;S…

C# BindingSource 未完BindingNavigator

数据绑定导航事件数据验证自定义示例示例总结 在 C#中&#xff0c; BindingSource 是一个非常有用的控件&#xff0c;它提供了数据绑定的基础设施。 BindingSource 允许开发者将数据源&#xff08;如数据库、集合、对象等&#xff09;与用户界面控件&#xff08;如文本框、下…

集成学习模型对比优化—银行业务

1.Data Understanding 2.Data Exploration 3.Data Preparation 4.Training Models 5.Optimization Model 集成学习模型对比优化—银行业务 1.Data Understanding import pandas as pd from matplotlib import pyplot as plt import seaborn as sns df pd.read_csv(&quo…

《TCP/IP网络编程》(第十四章)多播与广播

当需要向多个用户发送多媒体信息时&#xff0c;如果使用TCP套接字&#xff0c;则需要维护与用户数量相等的套接字&#xff1b;如果使用之前学习的UDP&#xff0c;传输次数也需要和用户数量相同。 所以为了解决这些问题&#xff0c;可以采用多播和广播技术&#xff0c;这样只需要…

pxe自动装机:

pxe自动装机&#xff1a; 服务端和客户端 pxe c/s模式&#xff0c;允许客户端通过网络从远程服务器&#xff08;服务端&#xff09;下载引导镜像&#xff0c;加载安装文件&#xff0c;实现自动化安装操作系统。 无人值守 无人值守&#xff0c;就是安装选项不需要人为干预&am…

当前 Python 版本中所有保留字keyword.kwlist

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 当前 Python 版本中 所有保留字 keyword.kwlist [太阳]选择题 根据给定的Python代码&#xff0c;哪个选项是正确的&#xff1f; import keyword print("【执行】keyword.kwlist"…

vue面试题2-根据以下问题回答

以下是针对提供的关于Vue的问题的回答&#xff1a; Vue的基本原理&#xff1a; Vue.js是一个流行的JavaScript框架&#xff0c;用于构建用户界面和单页面应用。其基本原理包括响应式数据、模板、组件系统、指令、生命周期钩子和虚拟DOM。 双向数据绑定的原理&#xff1a; Vue通…

自动化测试-Selenium(一),简介

自动化测试-Selenium 1. 什么是自动化测试 1.1 自动化测试介绍 自动化测试是一种通过自动化工具执行测试用例来验证软件功能和性能的过程。与手动测试不同&#xff0c;自动化测试使用脚本和软件来自动执行测试步骤&#xff0c;记录结果&#xff0c;并比较预期输出和实际输出…

第十一篇——信息增量:信息压缩中的保守主义原则

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 通过信息中的保守主义&#xff0c;我想到了现实中人的保守主义一样&#…

【InternLM实战营第二期笔记】07:OpenCompass :是骡子是马,拉出来溜溜

文章目录 课程实操 课程 评测的意义是什么呢&#xff1f;我最近也在想。看到这节开头的内容后忽然有个顿悟&#xff1a;如果大模型最终也会变成一种基础工具&#xff08;类比软件&#xff09;&#xff0c;稳定或可预期的效果需要先于用户感知构建出来&#xff0c;评测 case 就…

项目方案:社会视频资源整合接入汇聚系统解决方案(五)

目录 一、概述 1.1 应用背景 1.2 总体目标 1.3 设计原则 1.4 设计依据 1.5 术语解释 二、需求分析 2.1 政策分析 2.2 业务分析 2.3 系统需求 三、系统总体设计 3.1设计思路 3.2总体架构 3.3联网技术要求 四、视频整合及汇聚接入 4.1设计概述 4.2社会视频资源分…

javascript动态绑定

介绍 先来看看ai的解释 动态绑定机制是面向对象编程中的一个核心概念&#xff0c;特别是在Java这样的语言中。它允许在运行时根据对象的实际类型来决定调用哪个方法&#xff0c;而不是在编译时。这是多态性的关键特性之一。 在Java中&#xff0c;动态绑定是通过方法调用和方法…

安卓事件交互(按键事件、触摸事件、手势识别、手势冲突处理)

本章介绍App开发常见的以下事件交互技术&#xff0c;主要包括&#xff1a;如何检测并接管按键事件&#xff0c;如何对触摸事件进行分发、拦截与处理&#xff0c;如何根据触摸行为辨别几种手势动作&#xff0c;如何正确避免手势冲突的意外状况。 按键事件 本节介绍App开发对按…

人脸考勤项目实训

第一章 Python-----Anaconda安装 文章目录 第一章 Python-----Anaconda安装前言一、Anaconda是什么&#xff1f;二、Anaconda的前世今生二、Windows安装步骤1.官网下载2.安装步骤安装虚拟环境 总结 前言 工欲善其事必先利其器&#xff0c;项目第一步&#xff0c;安装我们的环境…

Mysql的底层实现逻辑

Mysql5.x和Mysql8性能的差异 整体性能有所提高&#xff0c; 在非高并发场景下&#xff0c;他们2这使用区别不大&#xff0c;性能没有明显的区别。 只有高并发时&#xff0c;mysql8才体现他的优势。 2. Mysql数据存储结构Innodb逻辑结构 数据选用B树结构存储数据&#xff0…

基于STM32的595级联的Proteus仿真

文章目录 一、595级联1.题目要求2.思路3.仿真图3.1 未仿真时3.2 模式A3.2 模式B3.3 故障模式 二、总结 一、595级联 1.题目要求 STM32单片机&#xff0c;以及三个LED灯对应红黄绿灯&#xff0c;IIC的OLED显示屏&#xff0c;温湿度传感器DHT11&#xff0c;两个独立按键和两个5…

深度学习的实用性探究:虚幻还是现实?

深度学习的实用性探究&#xff1a;虚幻还是现实&#xff1f; 深度学习作为人工智能领域的一个热点&#xff0c;已经在学术和工业界引起了广泛的关注。尽管深度学习技术显示出惊人的性能和潜力&#xff0c;但有时它们给人的感觉是“虚”的&#xff0c;或许是因为它们的抽象性和…

react修改本地运行项目的端口

一、描述 如果你想让项目在你想要的端口打开的话&#xff0c;就需要进行设置 二、代码 设置一下pages.json文件就可以了&#xff0c;如下&#xff1a; 如果想打开项目不需要点击下面的链接地址&#xff0c;让他运行npm run dev之后自己直接打开到浏览器的话&#xff0c;在后…

猫头虎分享已解决Bug || Uncaught TypeError: Cannot set property ‘innerHTML‘ of null**

猫头虎分享已解决Bug || Uncaught TypeError: Cannot set property ‘innerHTML’ of null** 原创作者&#xff1a; 猫头虎 作者微信号&#xff1a; Libin9iOak 作者公众号&#xff1a; 猫头虎技术团队 更新日期&#xff1a; 2024年6月6日 博主猫头虎的技术世界 &#x…

盘点2024年5月Sui生态发展,了解Sui近期成长历程!

2024年5月是Sui的第一个生日月&#xff0c;Sui迎来了它的上线一周年纪念日。在过去的一年中Sui在技术进步与创新、生态系统的扩展、社区发展与合作伙伴关系以及重大项目和应用推出方面取得重要进展&#xff0c;展示了其作为下一代区块链平台的潜力。 以下是Sui的近期成长历程集…