目录
前言:
N个子问题
问题一:主线程和新线程之间谁先运行?
问题二:我们期望谁是最后退出的呢?为什么?
问题三:tid是什么?
问题四:全面看待线程函数的传参,我们到底能传些什么?
问题五:全面看待线程的返回
前言:
前文我们已经介绍了线程的基本概念,知道查看线程是使用ps -aL查看,并且因为线程的特殊性,我们必须在用户层和操作系统层面接一层库,也就是线程库,编译程序的时候需要将程序link到线程库里面。
我们也见识了最基本的线程创建,使用函数pthread_createa,但是仅仅从创建来学习线程,或者说仅仅从概念中学习线程的所有内容实在是有一点不可靠,所以本文我们介绍线程的各种操作。
大体的介绍思路是我们从各种角度的问题出发,问题介绍的差不多了,欸我们不妨自己试试模拟实现一个所谓的线程。
N个子问题
问题一:主线程和新线程之间谁先运行?
我们先来看看这一段代码:
void* threadRun(void* args)
{std::string name = static_cast<const char*>(args);std::cout << "This is new thread, name is " << name << std::endl;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread -1");std::cout << "This is main thread ..." << std::endl;return 0;
}
我们原本的预期结果是屏幕打印出两条语句,然后我们好讨论是谁先运行这个子问题,可是现在的问题是为什么我们运行了那么多次都没有打印出子线程的结果?
因为我们没有调用join函数,所以主线程创建之后不会关心子线程如何了,主线程的东西做完了就直接退出了,那么如果我们运气好,子线程比主线程先将事儿做完,那么就有可能看到子线程打印东西。
而其实到了这里,第一个子问题我们已经介绍的比较清楚了,运气好点,就能看到子线程,这实际上就说明了主线程和子线程之间的调用顺序不是固定的,是完全随机的。
这里其实和进程完全一样,父子进程,调度次序完全随机。
问题二:我们期望谁是最后退出的呢?为什么?
int main()
{//问题一:主线程和子线程谁先运行?pthread_t tid;void* result;pthread_create(&tid, nullptr, threadRun, (void*)"thread -1");std::cout << "This is main thread ..." << std::endl;// sleep(10);//pthread_join(tid,&result);//问题二:主线程和子线程之间,我们期望谁先退出呢?int n = pthread_join(tid, &result);if(n == 0)std::cout << "main thread wait success, new thread begin quit ..." << std::endl;return 0;
}
对于该问题,其实我们有了前面进程的基础,我们甚至可以直接猜测说是是期望主线程最后退出,就像进程退出那样,父进程退出的时候要收集子进程的退出信息,同理,这里子线程退出的时候,退出信息也是要给主线程的,可是我们从哪里去看子线程的相关信息呢?
在上一篇文章,我们是已经介绍过了,对于函数pthread_create来说,第一个参数实际上是输出参数,第二个参数是线程的属性,我们暂时都不管,直接改为nullptr,第三个参数是函数指针,返回值是void*,参数是void*,可是你有没有发现,我们似乎可以直接使用参数?参数的值是哪里来的呢?
参数的值其实是从pthread_create的第四个参数来的,也就是该函数的第四个参数给了第三个参数的参数。
当我们使用16进制打印出tid的时候,会发现它是一串地址,它既然是地址,那么它就开辟了空间了,开辟了空间,那么如果主线程不管子线程,是不是会出现类似于僵尸进程的问题呢?
所以,我们期望最后退出的线程的是主线程。
问题三:tid是什么?
std::string ToHex(pthread_t& tid)
{char buffer[128];snprintf(buffer,sizeof(buffer),"0x%lx",tid);return buffer;
}
我们其实透过源码看pthread_t的时候,可以看到pthread_t的定义:
它实际上不过是一个无符号的长整型而已,那么我们通过该函数把它用16进制的方式打印出来:
一眼地址,但是呢,我们现在对它的理解到地址这里就可以了,它的更深层内容我们防止后面在看。
问题四:全面看待线程函数的传参,我们到底能传些什么?
class ThreadData
{
public:std::string _name;int _id;
};void* threadRunClass(void* args)
{ThreadData t = *(ThreadData*)args;std::cout << "The new thread name is " << t._name << " ,id is " << t._id << std::endl;return nullptr;
}//问题四:全面看待线程函数传参
pthread_t tid1;
ThreadData t1;
t1._name = "thread -1";
t1._id = 1;
pthread_create(&tid1, nullptr, threadRunClass, (void*)&t1);
pthread_join(tid1,nullptr);
对于最后一个参数,我们可以传所谓的字符串,那么我们可以传我们自己定义的类成员吗!!
当然可以,如果你要问为什么,我只能给你说:那咋啦那咋啦。
所以我们可以实现这种效果:
可是,如果我们主线程的栈区里面修改对应的t1不就炸了吗?
是的,我们在上一盘文章说到,线程独有的资源里面有栈,所以如果我们创建了一个局部变量,还传过去了,传的还是地址,那所有的线程都看得到,有一个修改都炸了。
那么最安全的方式是在堆上开空间,传递过去即可。
pthread_t tid1;ThreadData t1;t1._name = "thread -1";t1._id = 1;ThreadData *td = new ThreadData();td->_name = "thread-2";pthread_create(&tid1, nullptr, threadRunClass, (void*)&t1);pthread_create(&tid1, nullptr, threadRunClass, td);pthread_join(tid1,nullptr);
对于这个子问题来说,我们一定不能死板了,如果固定的传某些内置类型,那我们就直接玩儿完了。
问题五:全面看待线程的返回
欸您猜怎么着,对于全面看待线程返回问题,几乎是和线程函数的传参一样的,我们可以返回不只是有nullptr,我们可以返回任意类型,不过强制转换即可。
同学,到现在,你理解了C语言或者C++的泛型指针了吗?
class ThreadResult
{
public:bool _result;
};
class ThreadData
{
public:std::string _name;
};
void* threadRun(void* args)
{ThreadData* t = static_cast<ThreadData*>(args);std::cout << "The new thread is running..., name is " << t->_name << std::endl; ThreadResult* s = new ThreadResult();s->_result = true;return (void*)s;
}
int main()
{pthread_t tid;ThreadResult* result = nullptr;ThreadData* t = new ThreadData();t->_name = "thread -1";pthread_create(&tid, nullptr, threadRun, (void*)t);pthread_join(tid,(void**)&result);std::cout << "The new thread's result is " << result->_result << std::endl;return 0;
}
那么这个代码,还需多言吗?
以上是多个子问题中的部分,线程控制2介绍剩下的子问题以及模拟实现一个线程。
感谢阅读!