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)

posted @   thammer  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示