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 @ 2025-01-15 14:01  thammer  阅读(254)  评论(0)    收藏  举报