linux|多线程(一)

主要介绍了为什么要有线程 和线程的调用 和简单的对线程进行封装。

背景知识

a.重谈地址空间

我们知道物理内存的最小单元大小是4kB
物理内存是4G那么这样的单元友1M个
操作系统先描述再组织struct page[1M]
对于32位数据字长的机器,页表有2^32条也就是4G条,每一条光保存两个地址加一个标记位 这个页表就得有36G这样大
实际上比如 0010 0010 0010 0010 0010 0010 0010 0010
在这里插入图片描述
虚拟地址>> 12 得到 页框号
虚拟地址&&oxfff 得到 页内偏移量

线程的概念

线程:在进程内部运行,是cpu调度的基本单位
进程承担系统资源的基本实体
在这里插入图片描述
进程承担系统资源的基本实体 怎么理解这句话呢?可以把进程看作家庭,国家的最小单位国是千万家~~家是社会资源分配的最小单位,把线程看作个人,线程是进程的一部分

os要单独设计线程 --新建?暂停?销毁?调度?线程要不要和进程产生关联

在windows 有 专门管理线程的数据结构

struct tcb
{
// 线程id
// 线程 优先级
// 状态上下文
// 链接属性
};

在这里插入图片描述
但是linux 中没有专门的线程,Linux 是用进程模拟的线程
linux中的执行流,我们称为轻量级进程
线程:在进程内部运行,是cpu调度的基本单位
在cpu看来被调用的都是轻量级进程

澄清进程和线程

进程 是 只有一个执行流的线程~

见一见线程

在这里插入图片描述
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

pthread_t *thread: 一个指向pthread类型的指针 , pthread 这个类型用于保存的线程的标识符
const pthread_attr_t *attr: 线程属性对象的引用,可以用来设置线程的优先级、调度策略等。如果不需要设置特殊属性,可以传入 NULL 使用默认属性。
start_routine: 指向线程开始执行的函数的指针这个函数的类型应为 void ()(void*),意味着它接受一个 void * 类型的参数,并返回 void * 类型的结果。
arg: 传递给 start_routine 函数的参数,类型为 void *。这使得你可以在创建线程时向线程函数传递数据。

#include <iostream>
#include <unistd.h>

void *threadStart(void * args)
{
    while(true)
    {
        
        std::cout<<"new thraed running..."<<",pid:"<<getpid()<<std::endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    // 新线程
    pthread_create(&tid, nullptr, threadStart,(void *) "thread-new"); // 相当于把页表分成了两部分
    // 主线程
    while(true)
    {
        sleep(1);
        std::cout<<"main thraed running..."<<",pid:"<<getpid()<<std::endl;
    }
    return 0;
}

有了多进程为什么还要有多线程

线程不用再创建页表等,只需要一个pcb 创建的成本低
运行期间线程,的调度成本低,因为不需要切换页表
删除线程,比删除一个进程的成本低,只需要删除一个pcb就行了

线程的调度成本为什么更低呢?

我们切换页表只需要修改对应寄存器中的值就可以了,这个不是主要原因
最主要的原因是
在这里插入图片描述
数据会被缓存在cache中,一切换进程,cache中的数据就作废了,就需要重新cache了

为什么还要有进程呢?

线程的健壮性比较低,一个线程出错整个程序都退出了

哪些东西在多线程中是共享的

因为同一地址空间,所以代码段,数据段都是共享的,文件描述符表,每种信号的处理方式,工作目录。

哪些东西是各自都有一份的呢?

线程id,错误码,调度优先级,调度的上下文,信号屏蔽字
**最重要的:**一组寄存器,保存硬件上下文数据 线程是在调度运行的
栈:线程的栈主要用于存储函数调用的局部变量、函数参数以及返回地址。每当线程调用一个新的函数时,相关的局部变量和函数参数就会被压入该线程的栈中,形成一个新的栈帧。当函数返回时,相应的栈帧就会被弹出,恢复之前的调用环境。由于每个线程有独立的栈,它们可以并发地进行函数调用,而不会相互干扰。

几个基本的问题

问题1:主线程和新线程谁先运行?

系统会将这个新线程加入到就绪队列中等待调度。调度器会根据其自身的算法(比如时间片轮转、优先级等)来决定哪个线程获得 CPU 时间片并开始执行。

问题2:我们期望谁最后退出? main thread,你如何保证呢?

我们希望主线程后退出,如果主线程先退出,主线程退出了整个进程就结束了,可能此时新线程还没有完成任务呢。
我们用pthread_join 来保证 主线程后退出

void * RunThread(void * args)
{
    std:: string name;
    name = (const char *)args;
    std::cout <<name << std::endl;
    return args;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, RunThread,(void *)"thread - 1");
    
    int n = pthread_join(tid,nullptr);
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

在这里插入图片描述

问题3: tid 是什么样子的?是什么呢?虚拟地址! 为什么?

void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}

我们打出来发现是一个地址
在这里插入图片描述
为什么是一个地址呢?我们等下再说

问题4:全面看待线程函数传参: 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!

class ThreadData
{
public:
    
    int x;
    int y;
};
void * RunThread(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
    std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    return args;
}
void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}
int main()
{
    pthread_t tid1;
    ThreadData x1;
    x1.x = 10;
    x1.y =20;
   
    pthread_create(&tid1, nullptr, RunThread,(void *)&x1);
  
   
   // PrintToHEX(tid);
    int n = pthread_join(tid1,nullptr);
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

问题6:注意刚刚我们x1的写法其实是不标准的 为什么?

#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:
    
    int x;
    int y;
};
void * RunThread1(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
    data->x = 5;
    data -> y = 10;
    std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    return args;
}
void * RunThread(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
  
    std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    return args;
}
void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}
int main()
{
    pthread_t tid1;
    ThreadData x1;
    x1.x = 10;
    x1.y =20;
   
    pthread_create(&tid1, nullptr, RunThread,(void *)&x1);
    pthread_t tid2;
   
    pthread_create(&tid2, nullptr, RunThread1,(void *)&x1);
    int n = pthread_join(tid1,nullptr);
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

比如说这样,我们原本想要打印 10 ,20 5,10 的 结果却这样。
在这里插入图片描述
因为main 栈上的变量 这两个线程都可以看到并修改,且这个变量只有一份,当第一个线程正准备打印的时候,第二个线程修改了这两个变量的值。导致第一个线程打印出来就和第二个线程一样了。 根本原因就是 变量只有一份,我们多创建一份就好了。

而且我们不推荐直接 将main栈上的变量给新线程因为 main函数栈的变量生命周期是随main函数的,可能新线程早都结束了,但是main栈上的变量就是不结束
我们用new 可以在新线程中方便管理 传过来的变量的生命周期。

问题5: 全面看待线程函数返回

它也可以传递自定义类型哦

#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:
    
    int x;
    int y;
    int Excute()
    {
        return x + y; 
    }
};
class ThreadResult
{
public:
    int x;
    int y;
    int ans;
    void print()
    {
        std::cout<<std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(ans)<<std::endl;
    }
};

void * RunThread(void * args)
{
    std:: string name;
    ThreadData * data = (ThreadData *)args;
  
    //std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;
    ThreadResult * result = new ThreadResult();
    result->ans = data->Excute();
    result->x = data->x;
    result->y = data->y;

    return (void*)result;
}
void  PrintToHEX(pthread_t tid)
{
    char message[1024];
    snprintf(message,sizeof(message),"0x%lx",tid);
    std::cout<<message<<std::endl;
}
int main()
{
    pthread_t tid1;
    ThreadData * x1 = new ThreadData();
    x1->x = 10;
    x1->y = 20;
   
    pthread_create(&tid1, nullptr, RunThread,(void *)x1);
    ThreadResult * result =nullptr;
    int n = pthread_join(tid1,(void**)&result);
    result->print();
    if(n == 0)
    {
        std::cout<<"wait success"<<std::endl;
    }
    
    return 0;
}

问题6: 如何创建多线程呢?

我们通过循环的方式 创建多线程哦~~

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
void * RunThread(void * args)
{
    std::string name = (const char *) args;
    std::cout<<name<<std::endl;
    return args;
}
int main()
{
    for(int i = 0; i < 10; i++)
    {
        pthread_t tid;
        char name[1024];
        snprintf(name,sizeof(name),"this is %d new thread",i+1);
        pthread_create(&tid, nullptr , RunThread,name);
    }
    sleep(100);
    return 0;
}


在这里插入图片描述
我们发现结果并不符合预期。 因为name 只有一份,主线程 运行的时候不停在覆盖 name。
我们用 new ,循环 new 十次 主线程分配的时候每一个线程都得到 一个地址,那个地址指向一个独立的堆空间,线程之间就不互相影响了。
在这里插入图片描述
ok 完美解决~~!!!

问题7: 新线程如何终止?

1.我们可以在新线程中用return
2.我们可以在新线程中用pthread_exit 注意exit 是终止整个进程。
3.我们可以在新线程中使用pthrad_cancel
线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>

void *RunThread(void *args)
{
    while (true)
    {
        std::string name = (const char *)args;
        std::cout << name << std::endl;
        sleep(3);
    }

    return args;
}

int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < 10; i++)
    {
        pthread_t tid;
        // char name[1024];
        char *name = new char[1024];
        snprintf(name, 1024, "this is %d new thread", i + 1);
        pthread_create(&tid, nullptr, RunThread, name);
        tids.push_back(tid);
    }
    for (auto tid : tids)
    {
        // pthread_detach(tid);
        pthread_cancel(tid);
        void *ret = nullptr;

        int n = pthread_join(tid, &ret);
        std::cout << (long long int)ret << " quit.." << std::endl;
        // std::cout<<"n="<<n<<std::endl;
    }
    return 0;
}

在这里插入图片描述

pthread_cancel 是请求取消另一个线程,而 pthread_exit 是线程自身决定退出。

在这里插入图片描述
在这里插入图片描述
某个线程调用了exit 整个进程就结束了~
exit 你走错片场了啊。要用pthread_exit

void * RunThread(void * args)
{
    std::string name = (const char *) args;
    std::cout<<name<<std::endl;
    pthread_exit(args);
    return args;
}

在这里插入图片描述

问题8: 可以不可以不join线程,让他执行完就退出呢??可以!

我们用pthread_detach 就可以了,然后线程变成unjoinable 状态,如果此时再等待就会出错 然后终止整个进程。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>

void *RunThread(void *args)
{
    while (true)
    {
        std::string name = (const char *)args;
        std::cout << name << std::endl;
        sleep(3);
    }

    return args;
}

int main()
{
    std::vector<pthread_t> tids;
    for (int i = 0; i < 10; i++)
    {
        pthread_t tid;
        // char name[1024];
        char *name = new char[1024];
        snprintf(name, 1024, "this is %d new thread", i + 1);
        pthread_create(&tid, nullptr, RunThread, name);
        tids.push_back(tid);
    }
      for (auto tid : tids)
    {
        pthread_detach(tid); // 主线程分离新线程,新线程必须存在
    }
    for (auto tid : tids)
    {
        // pthread_detach(tid);
        pthread_cancel(tid);
        void *ret = nullptr;

        int n = pthread_join(tid, &ret);
        std::cout << "n:"<<n<<std::endl;
        // std::cout<<"n="<<n<<std::endl;
    }
    return 0;
}

在这里插入图片描述

封装线程

#include <string>
#include <pthread.h>
#include <functional>
namespace ThreadModel
{   
 
    class Thread
    {
    typedef void  (*func_t)(std::string); // ?
   // using func_t = std::function<void()>;
    public:
        Thread(const std::string & name,func_t func)
        :_name(name),_func(func)
        {}
        ~Thread()
        {
            
        }
        void Excute()
        {
            _isrunning = true;
            _func(_name); // 这里传了线程名
            _isrunning = false;

        }
        static void * Routine(void * args) // 不用static 第一个参数是this
        {
            
            Thread * self  = static_cast<Thread*>(args);
          //  self->_func();
            self->Excute();
            return nullptr;
        }

        void start()
        {
            pthread_create(&_tid,nullptr,Routine,(void *)this);
        }
        void join()
        {
           // std::cout<<_isrunning<<std::endl;
          //  std::cout<<true<<std::endl;
            if(_isrunning) // 只有在运行中的线程才需要被等待
            {
                //std::cout<<"..."<<std::endl;
                pthread_join(_tid,nullptr);
            }
          
        }
        // 终止线程
        void stop()
        {
            if(_isrunning)
            {
                _isrunning = false;
                pthread_cancel(_tid);
            }
           
        }
        
    private:
        std::string _name;
        pthread_t _tid;
        func_t _func; 
        bool _isrunning;  // 主要 用于线程终止时 只有启动了的线程 才能终止      
    };
}

相关推荐

  1. Linux线

    2024-07-18 11:24:02       72 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 11:24:02       70 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 11:24:02       74 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 11:24:02       62 阅读
  4. Python语言-面向对象

    2024-07-18 11:24:02       72 阅读

热门阅读

  1. VDI 和 DaaS 的区别

    2024-07-18 11:24:02       22 阅读
  2. react + pro-components + ts完成单文件上传和批量上传

    2024-07-18 11:24:02       25 阅读
  3. MongoDB 基本查询语句

    2024-07-18 11:24:02       22 阅读
  4. ubuntu 源码安装postgresql16.0

    2024-07-18 11:24:02       24 阅读
  5. 【Tomcat9正确配置server.xml请求头信息】

    2024-07-18 11:24:02       20 阅读
  6. MYSQL设计索引一般需要考虑哪些因素?

    2024-07-18 11:24:02       24 阅读
  7. 华为OD机考题(典型题回顾)

    2024-07-18 11:24:02       20 阅读
  8. 手写实现简单Redis命令客户端功能

    2024-07-18 11:24:02       18 阅读
  9. Leetcode 238. 除自身以外数组的乘积

    2024-07-18 11:24:02       23 阅读
  10. qt listview 列表文字显示不全,如何用悬浮显示?

    2024-07-18 11:24:02       17 阅读