Unix\Linux多线程复健

news2024/9/24 7:20:44

线程是程序中完成一个独立任务的完整执行序列(是一个可调度的实体)

一个进程可以包含多个线程

查看指定进程的线程号:

ps -Lf pid

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位

分类:

内核线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态

用户线程:用户级线程是指不需要内核支持而在用户程序中实现的线程,它的内核的切换是由用户态程序自己控制内核的切换,不需要内核的干涉。但是它不能像内核级线程一样更好的运用多核CPU

为什么要使用线程:

一个进程可以包含多个线程

  • 进程间的信息是难以共享的,父子进程除去只读代码外并没有共享内存所以需要采用通信方式来实现信息交换
  • fork()的代价较高,即使是写时复制,也需要复制内存页表,文件描述符表等
  • 线程之间可以方便的共享信息,只需将数据复制到共享(全局或堆)变量中
  • 创建线程开销更小更快,线程间共享虚拟地址空间,无需写时复制来复制内存,无需复制页表

创建一个线程并没有复制原来进程的虚拟地址空间,而是共享

image

每个线程有独立的寄存器,上下文切换:复用cpu时间片时将上一个状态保存,以便之后切换回来继续运行

Linux中现在一般使用NPTL线程库

使用getconf GNU_LIBPTHREAD_VERSION查看线程库版本

root@ziggy-virtual-machine:~# getconf GNU_LIBPTHREAD_VERSION
NPTL 2.23

线程创建/结束

如果想要同时运行多个函数,或对一个函数同时调用多次,则就需要多线程了

#include <pthread.h>

   int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                      void *(*start_routine) (void *), void *arg);//void*是一个泛型,如果想传多个数据可以定义一个结构体
//用于创建线程,第一个参数为线程的地址(为传出参数),第二个为线程属性的指针,第三个为执行的函数名称

//第三个参数所指的函数的参数和返回值都为类型为void*的指针,用来允许他们指向任何类型的值
//第四个为第三个参数函数的参数

pthread_t pthread_self(void);//返回当前线程id,无符号长整形

//pthread_t:
typedef unsigned long int pthread_t
    //事实上,Linux上几乎所有资源标识符都是一个整形数
    一个用户可以打开的线程数量不能大于RLIMIT_NPROC限制
    所有用户能创建的线程总谁不能大于:/proc/sys/kernel/threads-max

在一个进程中调用此函数,此时这个程序就有了两个线程:主线程,子线程1

成功返回0,失败返回errcode(使用strerror(int errnum)接收,返回一个字符串)

int pthread_join(pthread_t thread, void **retval);
//main函数用于等待线程执行路线的返回,第二个参数如果不是NULL,则会将线程的返回值存储其中
//此函数成功返回0,失败返回errcode


#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>

#define NUMS 5
void *print_msg(void *m)
{
    char *cp = (char*)m;
    for(int i = 0;i<NUMS;i++)
    {
        printf("%s",cp);
        fflush(stdout);
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t t1,t2;
    pthread_create(&t1,NULL,print_msg,(void*)"hello");
    pthread_create(&t2,NULL,print_msg,(void*)"world\n");
    pthread_join(t1,NULL);//等待线程结束(防止主线程抢占cpu,子线程还未执行)
    pthread_join(t2,NULL);
    return 0;
}

主线程首先创建子线程,子线程被创建后执行任务函数callback,主线程继续向下执行

下面的程序中主线程没有等待子线程执行结束,主线程执行结束就释放虚拟地址空间

#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>

void* callback(void* arg){
    for(int i = 0;i < 5;i++){
        printf("子线程:i = %d\n",i);
    }
    printf("子线程:%ld\n",pthread_self());
    return NULL;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,callback,NULL);
    for(int i = 0;i < 5;i++){
        printf("主线程:%d\n",i);
    }
    printf("主线程id:%ld\n",pthread_self());
    return 0;
}

结果:
root@ziggy-virtual-machine:~/unix/ch11# ./thread1 
主线程:0
主线程:1
主线程:2
主线程:3
主线程:4
主线程id:139930043443008

pthread_exit

#include <pthread.h>

       void pthread_exit(void *retval);

可以保证 线程安全干净的退出

通过val向pthread_join(此线程的回收者)传递退出信息,执行后不会返回到调用者,且永远不会失败

在哪个线程调用此函数之后,哪个线程就会终止

主线程退出,并不影响其他在运行的线程,所有的线程都终止进程才能退出,下面代码中子线程变为僵尸线程

#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>

void* callback(void* arg){
    for(int i = 0;i < 5;i++){
        printf("子线程:i = %d\n",i);
    }
    printf("子线程:%ld\n",pthread_self());
    return NULL;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,callback,NULL);
    printf("主线程id:%ld\n",pthread_self());
    pthread_exit(NULL);
    return 0;
}
#include<stdio.h>
#include<pthread.h>
#include<string.h>

void* callback(void* arg){
    printf("child thread id:%ld\n",pthread_self());
}

int main()
{
    pthread_t t1;
    int ret = pthread_create(&t1,NULL,callback,NULL);
    if(ret!=0){
        char *err = strerror(ret);
        printf("%s\n",err);
    }
    for(int i = 0;i<5;i++){
        printf("%d\n",i);
    }
    printf("tid :%ld,main thread id:%ld\n",t1,pthread_self());
    pthread_exit(NULL);
    return 0;
}
/*
root@ziggy-virtual-machine:~/unix_linux/chapter14/pthread# ./ExitDemo 0
1
2
3
4
child thread id:139903656199936
tid :139903656199936,main thread id:139903664531200
*/
int pthread_equal(pthread_t t1, pthread
_t t2);
//不同操作系统中pthread_t的实现可能不同

pthread_join 线程连接

一个进程中的所有线程都可以调用此函数来回收其他线程,类似wait,waitpid,阻塞,等待要回收的线程结束,成功返回0,失败返回errcode

子线程退出时其内核(用户区的在退出时就释放了)资源主要是主线程来回收,此函数被调用一次只能回收一个子线程

#include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);
//成功返回0
//retval指向一级指针的地址,保存了pthread_exit()传递出的数据
/*
errcode:
EDEADLK: 可能造成死锁,例如两个进程互相对对方调用此函数,或者自己对自己调用
EINVAL:目标线程是不可回收的,或已经有其他线程在回收目标线程
ESRCH:目标线程不存在
*/

如果主线程需要等待子线程结束之后再结束,而在具体的逻辑上主线程是早于子线程结束的,这时候就要用到此函数

默认情况下我们创建的线程都是非分离的,如果一个线程非分离且没有对其pthread_join,这个线程结束之后会变为僵尸线程(结束后不会释放其内存空间)

#include<stdio.h>
#include<pthread.h>
#include<string.h>

int num = 5;
void* callback(void* arg){
    printf("child thread id:%ld\n",pthread_self());
    // int num = 5;//注意不能用局部变量,因为临时变量在栈空间退出作用域会被销毁
    // printf("num address:%p\n",(void*)&num);
    pthread_exit((void*)&num);
}

int main()
{
    pthread_t t1;
    int ret = pthread_create(&t1,NULL,callback,NULL);
    if(ret!=0){
        char *err = strerror(ret);
        printf("%s\n",err);
    }
    for(int i = 0;i<5;i++){
        printf("%d\n",i);
    }
    
    printf("tid :%ld,main thread id:%ld\n",t1,pthread_self());
    int *retval;    //存储的是地址
    pthread_join(t1,(void**)&retval);
    printf("%d\n",*retval);
    return 0;
}
struct test{
    int num;
    int age;
};
struct test t;//全局变量
//或在堆创建
void* callback(void* arg){
    for(int i = 0;i < 5;i++){
        printf("子线程:i = %d\n",i);
    }
    //局部变量为栈空间,退出时已经还回去了
    printf("子线程:%ld\n",pthread_self());
    t.age = 1;
    t.num = 2;
    pthread_exit(&t);
    return NULL;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,callback,NULL);
    printf("主线程id:%ld\n",pthread_self());
    void* ptr;
    pthread_join(tid,&ptr);//ptr一级指针的地址指向参数&t的地址
    struct test* pt = (struct test*)ptr;
    printf("num:%d,age:%d\n",pt->num,pt->age);
    return 0;
}

上面代码用到了全局变量

也可以使用主线程的栈空间:在主线程创建结构体,通过pthread_create的参数传递

void* callback(void* arg){
    for(int i = 0;i < 5;i++){
        printf("子线程:i = %d\n",i);
    }
    printf("子线程:%ld\n",pthread_self());
    struct test* t3 = (struct test*)arg; 
    t3->age = 1;
    t3->num = 2;
    pthread_exit(t3);
    return NULL;
}

int main()
{
    pthread_t tid;
    struct test t2;
    pthread_create(&tid,NULL,callback,&t2);
    printf("主线程id:%ld\n",pthread_self());
    void* ptr;
    pthread_join(tid,&ptr);//ptr一级指针的地址指向参数&t的地址
    struct test* pt = (struct test*)ptr;
    printf("num:%d,age:%d\n",pt->num,pt->age);
    return 0;
}

线程之间默认是不共享栈空间的,但是在这里主线程主动给子线程传递了地址,本身线程都在同一块地址空间内

为什么要传入二级指针:
pthread_exit其实在此线程中相当于return

而pthread_join的第二个参数用于在pthread_join函数体中接收这个void*类型的值

如果传入的是一个一级指针,这就和在c语言,swap函数中传入int型的形参没什么区别,传入的是一个指针的副本,所以函数结束后,传入的参数不会有改变

所以要传二级指针,因为pthread_exit返回的不是一个值,而是一个地址,所以不能用一级指针

线程的分离

#include <pthread.h>

       int pthread_detach(pthread_t thread);

主线程有自己要做的事,不能因为为了回收子线程资源而用join子线程没退出就一直被阻塞

通过此函数分离的线程,在结束时,会自动释放资源给系统,无需pthread_join

主线程退出后,子线程也就不存在了(地址空间没了)

detach后不需要主线程显式回收,其内核资源被其他进程回收

不要多次分离一个线程(文档里说可能会发生不可知的结果)

且分离后不要再对此线程连接

//分离自动回收资源,且使用pthread_exit防止影响子线程执行
void* callback(void* arg){
    for(int i = 0;i < 5;i++){
        printf("子线程:i = %d\n",i);
    }
    printf("子线程:%ld\n",pthread_self());
    return NULL;
}

int main()
{
    pthread_t tid;
    struct test t2;
    pthread_create(&tid,NULL,callback,&t2);
    printf("主线程id:%ld\n",pthread_self());
    pthread_detach(tid);
    pthread_exit(NULL);
    
    return 0;
}

线程的取消 pthread_cancel

在一个线程中杀死另一个线程

线程A中调用pthread_cancel杀死B,要在B中进行一次系统调用从用户区切换到内核区B才会被真正杀死

注意标准C函数等内部调用系统调用的也算

#include <pthread.h>

       int pthread_cancel(pthread_t thread);

取消点在文档pthread(7)中

看Linux高性能服务器编程补充此部分

void* work(void* arg){
    int j = 0;
    for(int i = 0;i<9;i++){
        j++;
    }
    printf("子线程id:%ld\n",pthread_self());
    for(int i = 0;i<9;i++){
        printf("child i:%d\n",i);
    }
    return NULL;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,work,NULL);
    printf("创建子线程id:%ld\n",tid);
    printf("主线程id:%ld\n",pthread_self());
    for(int i = 0;i<3;i++){
        printf("i = %d\n",i);
    }
    pthread_detach(tid);
    pthread_cancel(tid);
    pthread_exit(NULL);
    return 0;
}

结果:
创建子线程id:140123976349440
主线程id:140123984848704
i = 0
i = 1
i = 2
子线程id:140123976349440

线程的属性

一般系统默认为每个线程分配8MB的栈空间
即 8 MB = 8388608 字节imageimage

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

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

相关文章

代码随想录--字符串习题总结

代码随想录–字符串习题总结 1.LeetCode344 反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 示例 1&…

Spring AOP 详解

Spring AOP 详解一、什么是 Spring AOP二、为何要用 AOP三、Spring AOP3.1 AOP 组成3.1.1 切面 (Aspect)3.1.2 连接点 (Join Point)3.1.3 切点 (Pointcut)3.1.4 通知 / 增强方法 (Advice)3.1.5 小结3.2 Spring AOP 使用3.2.1 添加 AOP 框架支持3.2.2 定义切面和切点3.2.3 定义相…

Python---字典相关知识

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些python的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 字典思维导图字典是什么创建字典查找键key字典的…

十分钟入门Zigbee

大部分教程通常都是已Zigbee原理开始讲解和学习&#xff0c;各种概念让初学者难以理解。本教程从一个小白的角度出发&#xff0c;入门无需任何Zigbee底层原理知识&#xff0c;只需要基本的MCU研发经验就可以掌握&#xff0c;让您快速实现zigbee组网和节点之间通信。 本教程采用…

JDBC快速入门,如何使用JDBC操作数据库?

文章目录1. 前言2. JDBC 概述2.1 概念2.2 优点3. JDBC 快速入门Java编程基础教程系列1. 前言 在 Java 开发中&#xff0c;使用 Java 语言操作数据库是非常重要的一部分&#xff0c;那么 Java 语言是如何操作数据库的呢&#xff1f;我们需要使用不同厂商的数据库时&#xff0c;…

23种设计模式(二十二)——访问者模式【行为变化】

文章目录 意图什么时候使用访问者真实世界类比访问者模式的实现访问者模式的优缺点亦称:Visitor 意图 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于其内部各个元素的新操作。 什么时候使用访问者 1、如果你需要对一个复杂对象…

Redis脑裂为何会导致数据丢失?

1 案例 主从集群有1个主库、5个从库和3个哨兵实例&#xff0c;突然发现客户端发送的一些数据丢了&#xff0c;直接影响业务层数据可靠性。 最终排查发现是主从集群中的脑裂问题导致&#xff1a;主从集群中&#xff0c;同时有两个主节点都能接收写请求。 影响 客户端不知道应…

Python数模笔记-模拟退火算法(5)求解旅行商问题的联合算子模拟退火算法

Python数模笔记—求解旅行商问题的联合算子模拟退火算法&#xff08;完整例程&#xff09; 文章目录Python数模笔记—求解旅行商问题的联合算子模拟退火算法&#xff08;完整例程&#xff09;0 摘要1 引言2 模拟退火算法求解旅行商问题2.1 模拟退火算法2.2 多个新解的竞争机制2…

详解P431 塔防

题目说明gsy 最近在玩一个塔防游戏&#xff0c;但是这次她控制的是迷宫中的怪兽而非防御塔建造者游戏的地图是一个 n * m 的矩阵&#xff0c;起点在 (1,1) &#xff0c;终点在 (n,m) &#xff0c;gsy 每次可以选择上下左右四个方向移动 1 步这个地图上有很多的防御塔&#xff0…

“华为杯”研究生数学建模竞赛2005年-【华为杯】B题:空中加油问题的讨论(附获奖论文及C++代码)

赛题描述 对飞行中的飞机进行空中加油,可以大大提高飞机的直航能力。为了简化问题,便于讨论,我们作如下假设。 少辅机架数两种情况给出你的作战方案。 论文 一. 问题重述 空中加油技术可以大大提高飞机的直航能力。作战飞机称为主机,加油机称 为辅机。已知:( 1 )主…

[创业之路-50] :动态股权机制 -5- 创业公司股权分配常见的坑

1、 分工不清晰&#xff0c;决策不清晰&#xff0c;理念不一致分工必须要简单明晰初创公司的合伙人一般是三到五个&#xff0c;最合理的架构一开始最好是三个人&#xff0c;相互之间需要一段时间的磨合&#xff0c;了解清楚各自的特长&#xff0c;工作经历等等&#xff0c;不要…

微信小程序开发uni-app

一、uni-app简介官网&#xff1a;https://uniapp.dcloud.io/PC端&#xff1b;移动端&#xff1a;&#xff08;APP&#xff0c;WebApp&#xff09;&#xff1b;纯原生&#xff1a;&#xff08;IOS,Android &#xff09; 应用商店&#xff1b;H5Hybrid 模式&#xff08;混合&…

活动星投票最美农商人网络评选微信的投票方式线上免费投票

“最美农商人”网络评选投票_视频投票的相关评选_投票统计_微信不记名免费评选投票用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务…

树莓派Python虚拟环境、PyQt5、PySide2

要从头设置好一台可用于开发的树莓派&#xff0c;可以参考树莓派 4B 无屏幕&#xff0c;连接WiFi、SSH、VNC&#xff0c;系统换源、pip换源&#xff0c;安装中文输入法 Python虚拟环境 树莓派&#xff08;或者说arm平台&#xff09;使用Python虚拟环境的正确方式是使用pipenv…

【MyBatis】| 使⽤javassist⽣成类、面向接口的方式进行CRUD

目录 一&#xff1a;使⽤javassist⽣成类 1. Javassist的使⽤ 2. 动态生成类并实现接口 3. MyBatis中接⼝代理机制及使⽤ 二&#xff1a;面向接口的方式进行CRUD 一&#xff1a;使⽤javassist⽣成类 Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤…

SSH原理与运用

SSH原理与运用 SSH原理与运用&#xff08;一&#xff09;&#xff1a;远程登录 SSH原理与运用&#xff08;二&#xff09;&#xff1a;远程操作与端口转发 一. 什么是SSH&#xff1f; 简单说&#xff0c;SSH是一种网络协议&#xff0c;用于计算机之间的加密登录。需要指出的…

7个流行的强化学习算法及代码实现

目前流行的强化学习算法包括 Q-learning、SARSA、DDPG、A2C、PPO、DQN 和 TRPO。 这些算法已被用于在游戏、机器人和决策制定等各种应用中&#xff0c;并且这些流行的算法还在不断发展和改进&#xff0c;本文我们将对其做一个简单的介绍。 1、Q-learning Q-learning&#xff1…

23种设计模式(十九)——迭代器模式【数据结构】

文章目录 意图什么时候使用迭代器真实世界类比迭代器模式的实现迭代器模式的优缺点亦称:Iterator 意图 提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。 什么时候使用迭代器 1、当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 …

[QMT]05-获取基础行情信息

函数&#xff1a;获取合约基础信息get_instrument_detail(stock_code)1释义获取合约基础信息参数stock_code - string 合约代码返回 dict 数据字典&#xff0c;{ field1 : value1, field2 : value2, ... }&#xff0c;找不到指定合约时返回NoneExchangeID - string 合约市场代码…

零基础学JavaWeb开发(二十)之 spring框架(3)

SpringBean的AOP 1、AOP基本的概念 AOP(Aspect Oriented Programming)是一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面&#xff0c;即解剖对象的内部&#xff0c;将那些影响了多个类的公共行为抽取到一个可重用模块里&#xff0c;减少系统的重复代码&#xff…