美文网首页C++多线程
2.2 向线程函数传递参数 ~ 2.3转移线程所有权

2.2 向线程函数传递参数 ~ 2.3转移线程所有权

作者: 庄阿莫 | 来源:发表于2018-11-25 01:51 被阅读111次

一、依赖隐式转换的危险

void f(int i,  std::string const& s);
void oops( int some_param )
{
  char buffer[1024];
  sprintf(buffer, "%i", some_param);
  std::thread t(f, 3, buffer);
  t.detach();
}

std::thread t 的构造函数中,想要依赖隐式转换,将字面值buffer转换为期待的 string 对象。但是 std::thread t 的构造函数只会复制buffer

怎么解决?
buffer传递到 std::thread t的构造函数之前,就将它显示转换为 std::string 对象

void f(int i,  std::string const& s);
void not_oops(int some_param)
{
  char buffer[1024];
  sprintf(buffer,  "%i",  some_param);
  std::thread t(f,  3,  std::string(buffer));//显示转换
  t.detach();
}

注意:

  • 向线程传递一个引用的变量时,在一些情况下会出现错误
class func{
public:
    string &a;
    explicit func(string &a_) : a(a_) {}
};

void fu(string str, func &b)
{
    b.a = str;
}

int main()
{
    string st("hhh");
    string str("嘿嘿");

    func my_func(st);
    std::thread my_thread(fu, str, my_func);//报错
    my_thread.join();

    cout << st << endl;


    return 0;
}

my_thread构造函数无视函数期待的参数类型,并盲目的拷贝my_func。当线程调用fu函数时,传递给函数的参数是my_func内部拷贝的引用,而非它本身的引用。

怎么解决?
使用 std::ref 将参数my_func转换成引用的形式:

std::thread my_thread(fu, str, std::ref(my_func));

当然,也可以使用bind的方式来解决:

class func1{
public:
    int &a;
    explicit func1(int &a_) : a(a_) {}


    int operator()(int c)
    {
        for(;a < c; ++a)
        {
            cout << a << endl;
        }
        cout << a << endl;
        return a;
    }
};

int main()
{
    int st = 6;
    int c = 10;

    func1 f(st);
    /*
     *  f::operator ()(int)实际上是一个operator()(func1 * this, int)
     *  因为类的成员函数,并没有在类内占空间,而是利用签名在类外进行查找
     *  传一个对象引用进去,让函数是针对的是哪个实例
     * */
    std::thread my_thread (&func1::operator(), &f, c);
    my_thread.join();

    return 0;
}

二、一个有趣的情况:提供的参数可以移动,但不能拷贝

"移动"是指:原始对象中的数据转移给另一对象,而转移的这些数据就不再在原始对象中保存了。
比如:std::unique_ptr
移动构造函数(move constructor)和移动赋值操作符(move assignment operator)允许一个对象在多个 std::unique_ptr 实现中传递。在使用"移动"转移原对象后,就会留下一个空指针(NULL)。

当对象是一个临时变量时,自动进行移动操作,例如:string str = string("hhh");。但当对象是一个命名变量,那么转移的时候就需要使用 std::move() 进行显示移动:

void process_big_object( std::unique_ptr<big_object> );

void main()
{
  std::unique_ptr<big_object> p_ob(new big_object);
  p_ob->prepare_data(42);
  std::thread t( process_big_object, std::move(p) ); //①
  
  t.join();
}

①运行过程:
对象p_ob 的所有权就被首先转移到新创建线程的的内部存储中,之后传递给process_big_object函数。

thread 的特性:
std::thread 实例不像 std::unique_ptr 那样能占有一个动态对象的所有权,但是它能占有其他资源:每个实例都负责管理一个执行线程。执行线程的所有权可以在多个 std::thread 实例中互相转移,这是依赖于 std::thread 实例的可移动且不可复制性
不可复制性保证了在同一时间点,一个 std::thread 实例只能关联一个执行线程。
可移动性使得程序员可以自己决定,哪个实例拥有实际执行线程的所有权。

三、转移线程所有权的基础用法

void some_function();
void some_other_function();

std::thread t01(some_function);
std::thread t02 = std::move(t01);//t01的所有权就转移给t02

/*
*  与一个临时 std::thread 对象启动了相关联的线程
*  由于std::thread(some_other_function)是一个临时对象,所以会隐式的调用移动操作
* */
t01 = std::thread(some_other_function);

std::thread t03 = std::move(t02);
t01 = std::move(t03); // ①赋值操作将使程序崩溃

最后一个移动操作使程序崩溃的原因是:
在 t01 将some_function线程的所有权就转移给t02后,t01 又启动了some_other_function线程。当 t03( some_other_function线程 ) 赋给了t01时,由于t01已经有了一个关联的线程,因此系统直接调std::terminate() 终止程序继续运行。这样做是为了保证与 std::thread 的析构函数的行为一致。

  • ( std::terminate() 是noexcept函数,不抛出异常。)
  • “不抛出异常,与 std::thread 的析构函数的行为一致”
    解析:
    线程对象被析构前,需要显式的等待线程完成,分离它;
    进行赋值时,赋一个新值给 std::thread 对象来"丢弃"一个线程。用这种方式来触发它的析构函数,这是绝对不行的。

四、转移线程所有权的两种想法

1,使用函数来返回新线程的所有权

std::thread f()
{
  void some_function();
  return std::thread(some_function);
} 

std::thread g(int a)
{
  void some_other_function(int);
  std::thread t(some_other_function,a);
  return t;
}

2,在函数中转移线程所有权

void func(std::thread t);
void gunc()
{
  void some_function();
  func( std::thread(some_function) );

  std::thread t(some_function);
  func( std::move(t) );
}

五、迈出线程自动化管理的第一步

为了确保线程程序在主线程退出前完成:

class Threa
{
  std::thread t;

public:

  explicit Threa(std::thread t_): 
      t( std::move(t_) )
  {
    if(!t.joinable())//检测是否为可执行线程,当线程不可加入时,抛出异常
      throw std::logic_error(“No thread”);
  } 

  ~Threa()
  {
    t.join();
  } 

  Threa( Threa const& )=delete;
  Threa& operator= (Threa const&)=delete;
};

struct func
{
  int& i;
  func(int& i_) : i(i_) {}
  void operator() ()
  {
    std::cout << i << std::endl;
  }
};

void f()
{
  int some_local_state;
  th t(std::thread(func(some_local_state)));
  do_something_in_current_thread();
}

当主线程到达f()函数的末尾时,scoped_thread对象开始析构,并阻塞至线程结束。

使用vector实现初步的线程自动化管理

void do_work(unsigned id);
void f()
{
  std::vector<std::thread> threads;
  for(unsigned i=0; i < 20; ++i)
{
  threads.push_back(std::thread(do_work,i)); // 产生线程
} 

  std::for_each(threads.begin(),  threads.end(),
                    std::mem_fn(&std::thread::join)); 
//mem_fn把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
}

相关文章

  • 2.2 向线程函数传递参数 ~ 2.3转移线程所有权

    一、依赖隐式转换的危险 在 std::thread t 的构造函数中,想要依赖隐式转换,将字面值buffer转换为...

  • 2.2向线程函数传递参数

    如何传递参数? 【注意】 默认参数是拷贝到线程独立内存中的,以便新线程在执行中可以访问,即使参数是引用的形式。 特...

  • ndk开发-6 子线程操作

    创建线程 向子线程传递参数 获取线程的返回值 输出结果 线程同步的问题

  • C++11线程函数的参数传递

    线程函数的参数传递过程 参数从调用线程创建一份拷贝 执行线程的拷贝变量拷贝到函数实参 验证: 可以看出A对象的拷贝...

  • Java 线程相关

    目录1.并行与并发2.进程与线程---- 2.1 进程---- 2.2 线程---- 2.3 进程与线程的区别--...

  • 提纲

    一、数据结构: 二、线程并发: 2.1 线程池; 2.2 AQS系列; 2.3 CAS; 2.4 synchron...

  • C++ 并发编程学习(三)

    向线程函数传递参数 一. 传参const char*   函数f需要一个 std::string 对象作为第二个参...

  • C++向线程函数传递参数

    C++11的多线程,传递参数时,有几个细节需要注意,如果没有处理好,有可能会得不到预期结果,甚至发生崩溃。 值类型...

  • python基础(下)

    一、多线程 向线程的目标函数中传递参数 从python中启动其他的程序 二、类的简单使用 1.基础类 2.父类、子...

  • 单例模式的几种写法

    一、饿汉式--无法给单例对象的构造方法传递参数,且线程不安全 二、懒汉式--可以给构造函数传递参数,但线程不安全 ...

网友评论

    本文标题:2.2 向线程函数传递参数 ~ 2.3转移线程所有权

    本文链接:https://www.haomeiwen.com/subject/vqeuqqtx.html