
在前面Linux的线程控制1中,我们介绍了如下的几个问题:主线程和子线程的执行顺序,主线程先退出还是子线程先退出,什么是tid,全面看待线程函数传参,全面看待线程函数的返回值。
以上是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,这里就不演示了,同学们自己尝试。
对于这个问题来说,当然是可以的,但是我们需要注意的点是一个线程被创建的时候,默认是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); // 专门终止一个线程的!
}至于测试的话,大家可以自行试试哦。
对于线程的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指针传递过去调用即可。
以上是线程控制的全部内容。
感谢阅读!