Linux线程基础:控制和封装

news2024/11/15 21:29:28

本节重点:

1. 了解线程概念,理解线程与进程区别与联系。

2. 学会线程控制,线程创建,线程终止,线程等待。

3. 了解线程分离与线程安全概念。

Linux线程概念

在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序 列”

一切进程至少都有一个执行线程

线程在进程内部运行,本质是在进程地址空间内运行

内核视角:进程是承担分配系统资源的基本实体,线程是CPU调度的基本单位。

虚拟内存决定了进程看到的内存资源,线程中的资源由进程划分。

CPU执行调度线程需要:线程ID,状态,优先级,上下文,栈……(进程与线程有重叠,所以Linux不给线程设计新的数据结构,复用PCB模拟线程——轻量级进程)。

由此可以得出,不同平台底层线程实现不一样。

OS要管理线程——先描述,再组织。

windows   TCB  (Thread   contrl  block)

Linux        轻量级进程

 从前我们创建的进程只有一个执行流,但可以有多个执行流。

 总结一下就是,进程整体申请资源,线程想进程申请资源。

Linux中没有真正意义上的线程。

OS和程序员只认线程,而Linux只能提供创建轻量级进程的接口,Linux通过原生线程库<pthread.h>来解决:

 通过原生线程库的封装,用户可以不关心底层,仅仅通过调用线程接口实现调用轻量级进程接口。

Linux线程创建

遵守POSIX线程库标准, 错误码通过返回值返回。

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的

要使用这些函数库,要通过引入头文件<pthread.h>

链接这些线程函数库时要使用编译器命令的“-lpthread”选项

功能:创建一个新的线程

原型

        int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数

        thread:返回线程ID

        attr:设置线程的属性,attr为NULL表示使用默认属性

        start_routine:是个函数地址,线程启动后要执行的函数

        arg:传给线程启动函数的参数

返回值:

        成功返回0;失败返回错误码

传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。

pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通 过返回值返回

pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误, 建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<string>
using namespace std;

void* route_run(void* args)
{
    while(true)
    {
        sleep(1);
        cout << "this is new thread running ......" << endl;
    }
    return nullptr;
}



int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, route_run, nullptr);

    while(true)
    {
        sleep(1);
        cout << "this is main thread running ......" << endl;
    }
    return 0;
}

查看线程:  ps   -aL

LWP (light  weight  process) 

主线程    PID =  LWP

线程一旦创建几乎所有资源都是共享的(环境变量、文件描述符表、当前工作目录、用户ID)。

CPU调度以LWP为调度ID标识。 

那么什么资源应该是线程私有的呢?

私有栈、PCB、要有一定的私有上下文。

进程切换VS线程切换

 由于线程许多资源是共享的,当CPU进行线程切换时,需要改变的数据更少,高速缓存中储存了许多的热点数据,线程切换后这些数据大概率仍然有效(命中率高),不需要从内存中重新读取。

线程的优点

创建一个新线程的代价要比创建一个新进程小得多

与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

线程占用的资源要比进程少很多

能充分利用多处理器的可并行数量

在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点

性能损失 :一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的 同步和调度开销,而可用的资源不变。

健壮性降低: 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

缺乏访问控制: 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

编程难度提高: 编写与调试一个多线程程序比单线程程序困难得多

进程与线程的关系

进程信号是发给进程的。

线程共享进程数据,但也拥有自己的一部分数据:

线程ID

一组寄存器

errno

信号屏蔽字

调度优先级

线程的私有栈

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID 不是一回事。

前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要 一个数值来唯一表示该线程。

pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

返回当前线程ID:

pthread_t   pthread_self(void);

pthread_t类型的线程ID,本质 就是一个进程地址空间上的一个地址。

void* route_run(void* args)
{
    while(true)
    {
        sleep(1);
        char tid[128];
        snprintf(tid, sizeof(tid), "%p", (long long)pthread_self());
        cout << "this is new thread running : " << string(tid) << endl;
    }
    return nullptr;
}

 线程的栈是私有的,clone接口(用作创建子进程(fork)或者轻量级进程(vfork)),我们用不到。

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

2. 线程可以调用pthread_ exit终止自己。

3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

不能用exit终止某个子线程,整个进程都会崩溃。

void* route_run(void* args)
{
    while(true)
    {
        sleep(5);
        char tid[128];
        snprintf(tid, sizeof(tid), "%p", (long long)pthread_self());
        cout << "this is new thread running : " << string(tid) << endl;
        pthread_exit(nullptr);
    }
    return nullptr;
}

 线程取消

功能:

        取消一个执行中的线程

原型

         int pthread_cancel(pthread_t thread);

参数

        thread:线程ID

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

线程退出码为PTHREAD_CANCELED(-1)。

线程等待

功能:

        等待线程结束

原型

        int pthread_join(pthread_t thread, void **value_ptr);

参数

        thread:线程ID

        value_ptr:它指向一个指针,后者指向线程的返回值

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

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的 终止状态是不同的,总结如下:

1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。

2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED。

3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参 数。

4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

#include<pthread.h>
#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<string>
using namespace std;

void* route_run(void* args)
{
    while(true)
    {
        sleep(5);
        char tid[128];
        snprintf(tid, sizeof(tid), "%p", (long long)pthread_self());
        cout << "this is new thread running : " << string(tid) << endl;
        pthread_cancel(pthread_self());
    }
    return nullptr;
}



int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, route_run, nullptr);

    cout << "this is main thread running ......" << endl;
    void* ret = nullptr;
    pthread_join(tid, &ret);
    cout << "ret = " << (long long)(ret) << endl;

    return 0;
}

 如果不关心线程退出码,也可以:

pthread_join(tid, nullptr);

线程分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放 资源,从而造成系统泄漏。 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线 程资源。

分离线程(不想等待,不关心线程退出返回值),退出时自动释放资源。

void* route_run(void* args)
{
    while(true)
    {
        sleep(5);
        char tid[128];
        snprintf(tid, sizeof(tid), "%p", (long long)pthread_self());
        cout << "this is new thread running : " << string(tid) << endl;
        pthread_detach(pthread_self());
        pthread_cancel(pthread_self());
    }
    return nullptr;
}



int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, route_run, nullptr);

    cout << "this is main thread running ......" << endl;
    void* ret = nullptr;
    if(0 == pthread_join(tid, &ret))
    {
        cout << "wait success" << endl;
    }
    else
    {
        cout << "wait failed" << endl;
    }
    cout << "ret = " << (long long)(ret) << endl;

    return 0;
}

如果分离线程就不能再等待,但若先一步等待,线程等待仍会成功。

正常使用应该是:

void* route_run(void* args)
{
    while(true)
    {
        sleep(1);
        char tid[128];
        snprintf(tid, sizeof(tid), "%p", (long long)pthread_self());
        cout << "this is new thread running : " << string(tid) << endl;
        pthread_detach(pthread_self());
        pthread_cancel(pthread_self());
    }
    return nullptr;
}



int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, route_run, nullptr);

    cout << "this is main thread running ......" << endl;
    void* ret = nullptr;
    sleep(2);
    if(0 == pthread_join(tid, &ret))
    {
        cout << "wait success" << endl;
    }
    else
    {
        cout << "wait failed" << endl;
    }
    cout << "ret = " << (long long)(ret) << endl;

    return 0;
}

在全局变量前加__thread,可以将一个内置类型设为线程局部储存(每个线程都有一个独立的变量)。

线程库的简单封装

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include <pthread.h>

class Thread;
//context传递对象的地址,帮助静态成员函数可以使用类内成员变量
class context
{
public:
    context(Thread* t = nullptr, void* args = nullptr)
    :_this(t)
    ,_args(args)
    {}
    Thread* _this;
    void* _args;
};

class Thread
{
public:
    Thread(const std::function<void*(void*)>& f, void* args = nullptr, const std::string& name = "")
    :_func(f)
    ,_args(args)
    ,_name(name)
    {
        ++_num;
        if(_name.size() == 0)
        {
            _name += "thread  ";
            _name += std::to_string(_num);
        }
        //创建上下文结构体
        context* pcon = new context(this, _args);
        int n = pthread_create(&_tid, nullptr, route_start, pcon);
        assert(0 == n);
        (void)n;
    }
    static void* route_start(void* pcon)
    {
        context* cont = static_cast<context*>(pcon);
        void* ret = cont->_this->run(cont->_args);
        delete cont;
        return ret;
    }

    void* run(void* args)
    {
        return _func(args);
    }

    void join()
    {
        int n = pthread_join(_tid, nullptr);
        assert(n == 0);
        (void)n;
    }

    ~Thread()
    {
        --_num;
    }
private:
    pthread_t  _tid;
    std::string _name;
    void* _args;
    std::function<void*(void*)> _func;
    static int _num;
};
int Thread::_num = 0;

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

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

相关文章

机器学习 00 归一化/标准化

目录 一、归一化/标准化 1.1 为什么我们要进行归一化/标准化? 二、归一化 2.1 定义 2.2 公式 2.3 归一化总结 三、标准化 3.1 定义 3.2 公式 3.3 标准化总结 一、归一化/标准化 1.1 为什么我们要进行归一化/标准化? 特征的单位或者大小相差较大&#xff0c;或者某…

我的软件研发套路

春节回来之后&#xff0c;调整到一个新的团队工作。 团队&#xff0c;是已有的&#xff1b;所用的技术栈&#xff0c;不熟悉&#xff1b;所做的业务领域&#xff0c;也涉猎甚少。挑战比较大。 管理层对团队的产出不满。我的首要任务&#xff0c;是提升团队的效能。 目前团队…

左中右 三栏式 布局

一、中间 自适应&#xff0c;左右两边 宽度固定 方法1&#xff1a;box容器 Flex布局&#xff0c;center设置为 flex:1 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible&q…

神经网络/深度学习(一)

感知机 多层感知机&#xff08;神经网络&#xff09; 误差逆传播&#xff08;error BackPropagation&#xff0c;简称BP&#xff09;算法 深度学习 卷积神经网络&#xff08;Convolutional Neural Networks, CNN&#xff09; 递归&#xff08;循环&#xff09;神经网络&#xf…

sonar覆盖率、代码覆盖率、分支覆盖率的计算方式

代码质量的覆盖率分为三种&#xff0c;覆盖率、代码覆盖率、分支覆盖率&#xff0c;那每一种的计算方式是怎么样的呢&#xff1f; 举例&#xff1a; 上面最有疑惑的是覆盖率&#xff0c;不知道怎么算出了来的&#xff0c;后面再说。 通过sonarqube可以分析出&#xff1a; 指标…

232:vue+openlayers选择左右两部分的地图,不重复,横向卷帘

第232个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers项目中自定义js实现横向卷帘。这个示例中从左右两个选择框中来选择不同的地图,做了不重复的处理,即同一个数组,两部分根据选择后的状态做disabled处理,避免重复选择。 直接复制下面的 vue+openlayers…

springmvc入门和两个配置类放置时的问题

springmvc 替换之前的servlet&#xff0c;用注解型标记进行操作的servlet类&#xff08;就是之前servlet类上面的Webservlet注解中参数&#xff1a;当前类的访问路径名&#xff09;&#xff0c;然后响应也用注解&#xff0c;据体如下&#xff1a; 先创建web项目 再导入需要的包…

边学边记——Java数据结构☞树和二叉树

目录 一.树 1.定义 2.一些基本概念 3.树的表示形式 二.二叉树 1.概念 2.两种特殊的二叉树 3.二叉树的性质 4.二叉树的存储 5.二叉树的遍历&#xff08;The traversal of A binary Tree&#xff09; 一.树 1.定义 树是一种非线性的数据结构&#xff0c;它是由n&#x…

1676_MIT 6.828 xv6中的CPU alarm_资料翻译整理

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 我觉得看了几个MIT的课程之后让我觉得我的大学四年有点浪费时光&#xff0c;看起来MIT的课程的确是很有饱满度。 这里&#xff0c;再整理一份课程中的作业要求。 …

每天分享五款工具,让你的工作办公更顺心

快乐不是在于拥有什么,而在于我们和别人分享什么。每天分享五款工具&#xff0c;让大家工作办公更顺心就是我最大的快乐。 1.绘画——Krita Krita是一款免费的开源绘画软件&#xff0c;适合专业和业余的绘画爱好者&#xff0c;支持多种画笔和图层。你可以使用Krita来创作各种…

HTTP与TCP区别

1、TCP对应与传输层、而HTTP对应于应用层&#xff0c;所以HTTP协议是建立在TCP协议之上的&#xff1b; 2、HTTP底层是利用TCP协议传输的&#xff0c;所以支持http也就一定支持TCP&#xff1b; 3、TCP是网络传输协议&#xff0c; HTTP是超文本传输协议&#xff1b; TCP是底层协…

Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例

场景 Java中创建线程的方式有三种 1、通过继承Thread类来创建线程 定义一个线程类使其继承Thread类&#xff0c;并重写其中的run方法&#xff0c;run方法内部就是线程要完成的任务&#xff0c; 因此run方法也被称为执行体&#xff0c;使用start方法来启动线程。 2、通过实…

盘点手机Type-c充电接口5个实用功能

目录 1、手机通过Type-c转HDMI&#xff0c;高清输出电视 2、通过OTG Type-c扩展手机功能 3、实现手机通过网卡有线上网 4、电脑通过手机Type-C有线上网 5、手机通过Type-C收听高清音频 今天给大家聊聊手机Type-c充电接口的5个实用功能&#xff0c;希望对大家日常使用…

Java每日一练(20230410)

目录 1. 二叉树的锯齿形层序遍历 &#x1f31f;&#x1f31f; 2. 从中序与后序遍历序列构造二叉树 &#x1f31f;&#x1f31f; 3. 平衡二叉树 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专…

UE4 Niagara 烟花制作工程

效果图&#xff1a; 第一个做的是网上射出的粒子&#xff1a; 用了事件&#xff0c;把ID给启用&#xff0c;不然会报错 第一个发射的炮弹粒子制作完成 第二制作炮弹的拖尾&#xff1a; 很明显会用到Ribbon 让宽度从宽到窄 让位置和颜色与第一个做的粒子一致&#xff0c;所以…

【分享】集简云审批支付助手,实现OA付款单自动到招商银行支付

场景描述 支付管理是企业财务管理中的重要一环&#xff0c;直接涉及企业现金流的管理和资金的运用。 在现代商业环境下&#xff0c;企业支付管理越来越复杂&#xff0c;许多公司都存在支付流程不规范、支付环节复杂的问题&#xff0c;导致企业支付效率低下。一方面&#xff0…

CocosCreator实战篇 |CocosCreator实现《飞机大战》

&#x1f4e2;博客主页&#xff1a;肩匣与橘 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由肩匣与橘编写&#xff0c;首发于CSDN&#x1f649; &#x1f4e2;生活依旧是美好而又温柔的&#xff0c;你也…

论文赏析——约翰·科斯塔斯:线性系统编码

© 1952 J. P. Costas © 2023 Conmajia 作者简介 约翰彼得科斯塔斯&#xff08;1923-2008&#xff09;&#xff0c;美国电气工程师&#xff0c;曾发明科斯塔斯环和科斯塔斯数组。科斯塔斯参加过第二次世界大战&#xff0c;并在战后进入麻省理工学院攻读博士学位&#…

Java反序列化漏洞及实例详解

目录 一、序列化和反序列化 序列化 用途 二、Java反序列化漏洞 数据出现 函数接口 漏洞发现 漏洞利用 三、Java序列化反序列化演示 四、靶场演示 一、序列化和反序列化 序列化 把 Java 对象转换为字节序列&#xff08;字节流&#xff09;的过程。 反序列化 把字节序…

WebRTC 系列(一、简介)

一、什么是 WebRTC WebRTC 全称是 Web RealTime Communication&#xff0c;是一个用于实时通讯的技术&#xff0c;Google 公司在 2010 年用 6829 万美元将其从 Global IP Solutions 公司收购&#xff0c;并于 2011 年开源&#xff0c;不得不说 Google 确实是一家伟大的公司&am…