C/C++中线程的创建
linux C中
由于我的第一语言为C,第一环境为linux,所以这里对于C下线程创建不做过多讨论,重点在我接触不太久的C++,并且只讨论线程创建和线程终止以及资源回收。不讨论线程通信和同步。
Linux系统下的多线程遵循POSIX线程接口,通常是通过pthread库实现(libpthread.so)。基本的接口为:
// 线程创建 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // 等待线程结束并回收资源 int pthread_join(pthread_t thread, void **retval); // 设置线程分离属性 int pthread_detach(pthread_t thread); // 请求结束线程(遇到取消点线程就退出) int pthread_cancel(pthread_t thread); // 调用者所属线程退出 void pthread_exit(void *retval);
以上只包含了部分接口,还有更多辅助接口未一一列出,例如设置线程属性,获取线程id,设置线程优先级等等。
C++中
C++标准库里面提供thread线程库来实现线程的创建,它一共提供了4个构造函数,其中最核心的是一个模板函数:
template<typename _Callable, typename... _Args, typename = _Require<__not_same<_Callable>>> explicit thread(_Callable&& __f, _Args&&... __args) { static_assert( __is_invocable<typename decay<_Callable>::type, typename decay<_Args>::type...>::value, "std::thread arguments must be invocable after conversion to rvalues" );
先抛开这些恐怖的模板修饰,它主要需要2个参数,一个是线程所运行的函数体__f
,一个是传递给函数体的参数__args
,其他东西暂时忽略。先来一个简单示例,感受一下:
#include <iostream> #include <thread> void threadBody(int arg) { std::cout << arg << std::endl; } int main(int argc, char *argv[]) { std::thread t1(threadBody, 1); t1.join(); return 0; }
和C的pthread_create
一样,当std::thread实例创建之后,线程就开始运行了,这里我们使用join等待线程结束和资源回收。
看第二个参数_Args&&... __args
,这里有三个点,表示是可变参数列表,也就是说0个参数也是允许的。
再看看thread模板,真他妈复杂,跳到头文件里面都嵌套了N层。其他的暂时不管,先关注它里面这个静态断言说的啥“std::
thread的参数必须在转换为右值后是一个可调用东西”。它又是通过一个复杂的模版实现的,先不深究它。看看哪些东西可以作为
__is_invocable
可调用的。
普通函数
void threadBody(int arg) { std::cout << arg << std::endl; } int main(int argc, char *argv[]) { std::thread t1(threadBody, 1); t1.join(); return 0; }
lambda表达式
int main(int argc, char *argv[]) { //无参数 std::thread t1([] { std::cout << "run thread" << std::endl; }); t1.join(); //一个参数 std::thread t2([](int i) { std::cout << "run thread with parameter:" << i << std::endl; }, 5); t2.join(); //多个参数 std::thread t3([](int i, const std::string &s) { std::cout << "run thread with parameter:" << i << " " << s << std::endl; }, 4, "hello world"); t3.join(); return 0; }
类静态函数
class Test { public: static void foo() { std::cout << "call foo" << std::endl; } }; int main(int argc, char *argv[]) { std::thread t1(&Test::foo); t1.join(); return 0; }
这个其实和普通函数作为第一个参数是一回事。
类非静态函数
class Test { public: void bar() { std::cout << "call bar" << std::endl; } }; int main(int argc, char *argv[]) { Test test; std::thread t1(&Test::bar, &test); t1.join(); return 0; }
我们知道类的非静态函数的参数列表实际是隐含了一个this指针(即类对象的指针),所以我们这里指定了类对象的地址作为线程函数的第一个参数。
仿函数
class Functor { public: void operator()() { std::cout << "call functor" << std::endl; } }; int main(int argc, char *argv[]) { Functor f; //写法1 std::thread t1(&Functor::operator(), &f); t1.join(); //写法2 std::thread t2(f); t2.join(); return 0; }
仿函数这边有两种写法,第一种是把函数调用符的符号重载当做类的非静态函数,传递函数指针,后面跟实例指针作为this。第二种直接传递的实例的引用。两种写法实际是一回事,但是第二种更常用,因为它更简洁。那为什么对于仿函数可以这么写呢?先得了解什么是仿函数。参考仿函数。
因此,对于仿函数的实例作为参数时,通过对象实例可以同时获得重载的operator()
和this 也就是(&f)
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具