首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >初识Linux · 线程控制(2)

初识Linux · 线程控制(2)

作者头像
_lazy
发布2024-12-20 08:50:52
发布2024-12-20 08:50:52
1830
举报
文章被收录于专栏:Initial programmingInitial programming

前言:

在前面Linux的线程控制1中,我们介绍了如下的几个问题:主线程和子线程的执行顺序,主线程先退出还是子线程先退出,什么是tid,全面看待线程函数传参,全面看待线程函数的返回值。

以上是N个子问题的部分问题,本文,我们要学习N个子问题的剩余问题以及我们要简单的模拟实现一下线程。

那么话不多说,我们直接进入主题吧!


N个子问题

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

我们可以使用函数pthread_create创建线程,同样可以利用它创建多个线程,并且我们是不需要担心函数pthread_create创建的子线程会再次创建子线程的,因为子线程执行的方法是我们给定了的,所以在主线程里面我们就尽情的创建即可。

代码语言:javascript
复制
void* threadRun(void* args)
{
    std::string name = static_cast<const char*>(args);
    std::cout << "This is new thread, it's name is " << name << std::endl;
    sleep(3);
    return nullptr;
}

int main()
{
    // 问题六:如何创建多线程?
    std::vector<pthread_t> v;
    for(int i = 0;i < 10; i++)
    {
        pthread_t tid;
        char* name = new char[128];
        snprintf(name,128,"thread - %d", i);
        pthread_create(&tid, nullptr, threadRun, (void*)name);
        
        v.emplace_back(tid);
    }
    for(auto e : v)
    {
        pthread_join(e, nullptr);
    }

    return 0;
}

我们为了方便起见,将创建的子线程统一防在了顺序表里面,放在里面之后,就,欻欻欻的创建即可,名字这里稍微有点点麻烦,我们使用snprintf简单对name处理了一下,然后在命令行中使用ps -aL查看即可:

550b702014764602978ffc23989ca376.png
550b702014764602978ffc23989ca376.png
3093458118a84da7b958c76459622f6e.png
3093458118a84da7b958c76459622f6e.png

这个问题还是比较简单的,无非借助了个容器,其实如果我们不想要收集线程的id的话,不用数组也可以,只是后面操作没有那么顺畅罢了。

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

我们先不管是否终止,我们就利用上面的代码,简单看一下现象:

代码语言:javascript
复制
void* threadRun(void* args)
{
    std::string name = static_cast<const char*>(args);
    std::cout << "This is new thread, it's name is " << name << std::endl;
    // sleep(3);
    exit(1);
    // return nullptr;
}

我们将返回值简单替换成了exit,直接看现象:

ab58fd58dee5491585583e3f5f1fb854.png
ab58fd58dee5491585583e3f5f1fb854.png

第一点,我们明明创建了10个线程,可是为什么打印出来的只有这么几个呢?

第二点,在ps -aL打印出来的,我们甚至看不到有对应的线程出现了。

那么这里不废话,直接给结论,对于exit来说是用来终止进程的,而对于线程,也就是Linux中的轻量级进程来说,使用该函数是会直接报错的,所以如何终止一个特定的线程呢?

我们使用到的函数是pthread_exit:

31e9b9ae0f754c3b82500da098153dfd.png
31e9b9ae0f754c3b82500da098153dfd.png

其中的参数表示的是向其他进程传递线程的退出结果或状态。

代码语言:javascript
复制
void* threadRun(void* args)
{
    std::string name = static_cast<const char*>(args);
    std::cout << "This is new thread, it's name is " << name << std::endl;
    // exit(1);
    sleep(3);
    pthread_exit(args);
    // return nullptr;
}

如果我不想传递任何信息的话,直接设置为空就可以了:

f193f02fd7a546ed8be202761c2b744a.png
f193f02fd7a546ed8be202761c2b744a.png

此时就很beautiful了。

当然了,还可以使用函数pthread_cancel,这里就不演示了,同学们自己尝试。

问题八:可不可以让线程不用join,让它执行完自己就退出了?

对于这个问题来说,当然是可以的,但是我们需要注意的点是一个线程被创建的时候,默认是joinable的,那么如果我们想要完成上述操作,需要用到一个函数:

87463384d40f4f6f891b1d509ded359e.png
87463384d40f4f6f891b1d509ded359e.png

detach,熟悉吧?在进程间通信的时候我们夜也介绍过,在这里,是线程脱离的意思。

打个比方,对于一个家庭来说,默认家庭成员是绑定在一起的,但是有一天你和你家人吵架了,你一生气,出去自己找了个房子居住,此时你和家庭脱离了,但是并不是真正的脱离,为什么不是真正的脱离呢?因为要是真出了什么事,你们还是的共同解决。

线程这里一样,即便一个线程被分离出去了,不被join,它出问题了的话,其他的所有线程都要崩,这里还有一个小小的前提,线程被分离的时候,该线程一定要是存在的。

第二,它的参数是线程的id,那么我们可以通过一个函数直接得到:

383cad2e9c304643b2fd040ac9320ca7.png
383cad2e9c304643b2fd040ac9320ca7.png

所以:

代码语言:javascript
复制
void *threadrun(void *args)
{
    pthread_detach(pthread_self());
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        std::cout << name << " is running" << std::endl;
        sleep(3);
        int *p = nullptr;
        *p = 100;
        break;
    }
    pthread_exit(args); // 专门终止一个线程的!
}

至于测试的话,大家可以自行试试哦。


模拟thread

对于线程的N个子问题我们已经介绍完了,在模拟实现thread之前,我们不妨思考一下,在Linux这个系统里面我们可以使用上面的方式创建,在Windows呢?Mac呢?

这时候,其实是call back文件系统了,对于系统调用,不具有可移植性,所以有了高级语言自己封装的线程等,比如C++的:

6796343539c44bad929a31011a93107a.png
6796343539c44bad929a31011a93107a.png

直接就是将thread封装为了一个类。

它的本质也是封装了底层的thread,现在我们就来简单模拟实现一下,模拟实现线程我们的大体思路得有吧?

那么对于线程来说,我们应该开始线程,终止线程,join线程,线程执行对应函数。

代码语言:javascript
复制
    class Thread
    {
    public:
        void Excute()
        {
            std::cout << _name << " is running" << std::endl;
            _isrunning = true;
            _func(_name);
            _isrunning = false;
        }
    public:
        Thread(const std::string &name, func_t func):_name(name), _func(func)
        {
            std::cout << "create " << name << " done" << std::endl;
        }
        static void *ThreadRoutine(void *args)
        {
            Thread *self = static_cast<Thread*>(args); 
            self->Excute();
            return nullptr;
        }
        bool Start()
        {
            int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
            if(n != 0) return false;
            return true;
        }
        std::string Status()
        {
            if(_isrunning) return "running";
            else return "sleep";
        }
        void Stop()
        {
            if(_isrunning)
            {
                ::pthread_cancel(_tid);
                _isrunning = false;
                std::cout << _name << " Stop" << std::endl;
            }
        }
        void Join()
        {
            ::pthread_join(_tid, nullptr);
            std::cout << _name << " Joined" << std::endl;
        }
        std::string Name()
        {
            return _name;
        }
        ~Thread()
        {
        }

    private:
        std::string _name;
        pthread_t _tid;
        bool _isrunning;
        func_t _func;
    };

模拟实现线程的时候,唯一一个需要注意的点是,实现回调函数的时候,因为类的特性,类成员函数有一个this指针,而函数应该只有一个void类型的指针,所以我们需要将函数设置为静态的,这样就没有对应的this指针了。

那么执行函数的时候,我们将this指针传递过去调用即可。

以上是线程控制的全部内容。


感谢阅读!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • N个子问题
    • 问题六:如何创建多线程呢?
    • 问题七:新线程如何终止?
    • 问题八:可不可以让线程不用join,让它执行完自己就退出了?
  • 模拟thread
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档