目录
前言:
N个子问题
问题六:如何创建多线程呢?
问题七:新线程如何终止?
问题八:可不可以让线程不用join,让它执行完自己就退出了?
模拟thread
前言:
在前面Linux的线程控制1中,我们介绍了如下的几个问题:主线程和子线程的执行顺序,主线程先退出还是子线程先退出,什么是tid,全面看待线程函数传参,全面看待线程函数的返回值。
以上是N个子问题的部分问题,本文,我们要学习N个子问题的剩余问题以及我们要简单的模拟实现一下线程。
那么话不多说,我们直接进入主题吧!
N个子问题
问题六:如何创建多线程呢?
我们可以使用函数pthread_create创建线程,同样可以利用它创建多个线程,并且我们是不需要担心函数pthread_create创建的子线程会再次创建子线程的,因为子线程执行的方法是我们给定了的,所以在主线程里面我们就尽情的创建即可。
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查看即可:
这个问题还是比较简单的,无非借助了个容器,其实如果我们不想要收集线程的id的话,不用数组也可以,只是后面操作没有那么顺畅罢了。
问题七:新线程如何终止?
我们先不管是否终止,我们就利用上面的代码,简单看一下现象:
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,直接看现象:
第一点,我们明明创建了10个线程,可是为什么打印出来的只有这么几个呢?
第二点,在ps -aL打印出来的,我们甚至看不到有对应的线程出现了。
那么这里不废话,直接给结论,对于exit来说是用来终止进程的,而对于线程,也就是Linux中的轻量级进程来说,使用该函数是会直接报错的,所以如何终止一个特定的线程呢?
我们使用到的函数是pthread_exit:
其中的参数表示的是向其他进程传递线程的退出结果或状态。
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;
}
如果我不想传递任何信息的话,直接设置为空就可以了:
此时就很beautiful了。
当然了,还可以使用函数pthread_cancel,这里就不演示了,同学们自己尝试。
问题八:可不可以让线程不用join,让它执行完自己就退出了?
对于这个问题来说,当然是可以的,但是我们需要注意的点是一个线程被创建的时候,默认是joinable的,那么如果我们想要完成上述操作,需要用到一个函数:
detach,熟悉吧?在进程间通信的时候我们夜也介绍过,在这里,是线程脱离的意思。
打个比方,对于一个家庭来说,默认家庭成员是绑定在一起的,但是有一天你和你家人吵架了,你一生气,出去自己找了个房子居住,此时你和家庭脱离了,但是并不是真正的脱离,为什么不是真正的脱离呢?因为要是真出了什么事,你们还是的共同解决。
线程这里一样,即便一个线程被分离出去了,不被join,它出问题了的话,其他的所有线程都要崩,这里还有一个小小的前提,线程被分离的时候,该线程一定要是存在的。
第二,它的参数是线程的id,那么我们可以通过一个函数直接得到:
所以:
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++的:
直接就是将thread封装为了一个类。
它的本质也是封装了底层的thread,现在我们就来简单模拟实现一下,模拟实现线程我们的大体思路得有吧?
那么对于线程来说,我们应该开始线程,终止线程,join线程,线程执行对应函数。
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指针传递过去调用即可。
以上是线程控制的全部内容。
感谢阅读!