简体   繁体   English

如何将现有功能放入其自己的连续(循环)线程并将更新的参数传递给它?

[英]How to put an existing funtion into its own continuous (looping) thread and pass updated parameters to it?

I have an OpenCV console application that is taking in camera frames from a port and displaying them to the screen, with optional image processing routines done on them first.我有一个 OpenCV 控制台应用程序,它从端口接收相机帧并将它们显示到屏幕上,首先在它们上完成可选的图像处理例程。 The main() loop is continuous, ie using while(true), and on each pass it gets a new image that's waiting for it to place into a Mat. main() 循环是连续的,即使用 while(true),每次通过它都会得到一个新图像,等待它放入 Mat 中。 I need to maintain at least a 30fps rate in main() so incoming frames are not dropped.我需要在 main() 中保持至少 30fps 的速率,以便不会丢失传入的帧。

Usually this is not a problem unless I have intense processing, but when I do I'd like to at least offload some of the simpler routines to their own threads so they don't hog linear CPU time.通常这不是问题,除非我有大量处理,但是当我这样做时,我想至少将一些更简单的例程卸载到它们自己的线程中,这样它们就不会占用线性 CPU 时间。 They can run independently by "grabbing" any frame and acting on it, and display their results asynchronously to main().它们可以通过“抓取”任何帧并对其进行操作来独立运行,并将其结果异步显示到 main()。 For example a histogram routine, and a routine that computes global contrast/offset adjustment values.例如直方图例程和计算全局对比度/偏移调整值的例程。

I've seen a simple example using < thread > syntax where 3 routines are launched in their own threads within main(), they run independently, then are all rejoined at the end of main(), then execution stops.我见过一个使用 <thread> 语法的简单示例,其中 3 个例程在 main() 内的它们自己的线程中启动,它们独立运行,然后在 main() 结束时重新加入,然后执行停止。 That example is below, and I have incorporated method 1 into my app since it looks simplest.该示例如下,我已将方法 1 合并到我的应用程序中,因为它看起来最简单。 (I have no idea what lambda is). (我不知道 lambda 是什么)。

// CPP program to demonstrate multithreading using three different callables.
#include <iostream>
#include <thread>
using namespace std;

// A dummy function
void foo(int Z)
{
  for (int i = 0; i < Z; i++) {
    cout << "Thread using function pointer as callable\n";
  }
}

// A callable object
class thread_obj {
public:
  void operator()(int x)
  {
    for (int i = 0; i < x; i++)
      cout << "Thread using function object as callable\n";
  }
};

int main()
{
  cout << "Threads 1 and 2 and 3 operating independently" << endl;

  // This thread is launched by using function pointer as callable
  thread th1(foo, 3);

  // This thread is launched by using function object as callable
  thread th2(thread_obj(), 3);

  // Define a Lambda Expression
  auto f = [](int x) {
    for (int i = 0; i < x; i++)
      cout << "Thread using lambda expression as callable\n";
  };

  // This thread is launched by using lamda expression as callable
  thread th3(f, 3);

  // Wait for the threads to finish
  // Wait for thread t1 to finish
  th1.join();

  // Wait for thread t2 to finish
  th2.join();

  // Wait for thread t3 to finish
  th3.join();

  return 0;
}

For a single pass thread, I can do the above and it makes sense.对于单通线程,我可以执行上述操作,这是有道理的。 But I want a thread that gets invoked and given several dynamic input parameters (including a Mat and some int/double/bool values) that normally change with every loop around main().但是我想要一个线程被调用并给出几个动态输入参数(包括一个 Mat 和一些 int/double/bool 值),这些参数通常随着围绕 main() 的每个循环而改变。 I want the thread stay open and be "re-triggered" every loop and produce new results.我希望线程保持打开状态并在每个循环中“重新触发”并产生新的结果。 These results can remain within the thread (as with the histogram which only displays to an independent window) or they can consist of a few computed values that get passed back to main() for use elsewhere (as with a contrast/offset routine).这些结果可以保留在线程内(与仅显示到独立窗口的直方图一样),或者它们可以由一些计算值组成,这些值传递回 main() 以在其他地方使用(如对比度/偏移例程)。 But in no case do the results need to be synchronized to what main() is doing.但在任何情况下,结果都不需要与 main() 正在做的事情同步。

For example a threaded histogram routine would get the Mat image, some rescaling values, and some Boolean controls that tell it to start processing the image data into a histogram and display it with imshow().例如,线程直方图例程将获取 Mat 图像、一些重新缩放值和一些布尔控件,这些控件告诉它开始将图像数据处理为直方图并使用 imshow() 显示它。

If I put the whole histogram routine into a while(true) loop then the thread never terminates, but then I need to have it see the parameters changing so it executes again and I don't know a good way to do this.如果我将整个直方图例程放入 while(true) 循环中,则线程永远不会终止,但是我需要让它看到参数发生变化,以便再次执行,但我不知道这样做的好方法。 I'm guessing that it's unnecessary nor elegant to use globals, plus I'm unsure about thread-crossing.我猜测使用全局变量既不必要也不优雅,而且我不确定线程​​交叉。 Also I'm confused about whether I should be passing literals into it or use pointers/addresses (* and &) to minimize memory shuffling.我也很困惑是应该将文字传递给它还是使用指针/地址(* 和 &)来最小化内存改组。

Does anyone have a simple clear example of doing such a thing.有没有人有一个简单清晰的例子来做这样的事情。 Preferably using < thread > if at all possible.如果可能,最好使用 <thread>。 I'm not an expert coder so please don't go into esoterics or jargon only developers would understand.我不是专家编码员,所以请不要进入只有开发人员才能理解的深奥或行话。 Thanks much!非常感谢!

I'm not sure if I have well understood what you want to do but I guess you want to launch threads whose the parameters values are updated each "iteration", right ?我不确定我是否完全理解您想要做什么,但我猜您想启动其参数值在每次“迭代”时更新的线程,对吗?

If so, you can declare the parameters in you main() function and give them by reference to your threads.如果是这样,您可以在main()函数中声明参数并通过引用您的线程来提供它们。 The main() function will perform the update and that way, the threads will take it into account. main()函数将执行更新,这样,线程就会将其考虑在内。

Something like that:类似的东西:

//...
void foo (int &a, bool &stop) // pass by reference
{
    while(!stop)
    {
        // Do something with a
    }
}
void bar(char &c, bool &b, bool &stop) // pass by reference too
{
    while(!stop)
    {
        // Do something with c and b
    }
}
//...
int main()
{
    std::atomic <bool> stop_threads(false);

    // declare your parameters (and initialize them the way you want)
    int param1(0);
    char param2('a');
    bool param3(false);

    // launch your threads
    thread th1(foo, std::ref(param1), std::ref(stop_threads));
    thread th2(bar, std::ref(param2), std::ref(param3), std::ref(stop_threads));

    // Update the parameters in your while loop
    while(/* Your stop condition */)
    {
        // Update the value of param1, param2 and param3
        // ...
    }

    // When exiting the while loop, the process is ended (no more data to process)
    stop_threads = true;

    th1.join();
    th2.join();

    return 0;
}

I added an additional boolean in parameters of each function to be launched in differents threads in order to have them to stop properly at the end.我在要在不同线程中启动的每个函数的参数中添加了一个额外的boolean ,以便让它们在最后正确停止。

I hope this is what you was looking for :)我希望这就是你要找的:)
Don't hesitate to tell me if I have misunderstood your problem.如果我误解了您的问题,请随时告诉我。


EDIT:编辑:

I forgot to mention a very important thing.我忘了说一件很重要的事。 Every times I wrote "Do something with that variable..." or "Update the value of that variable", you need to use a mutex mechanism over the shared variables (mutual exclusion) in order to avoid different threads accessing the same variable at the same time.每次我写“用那个变量做点什么……”或“更新那个变量的值”时,你都需要在共享变量上使用互斥机制(互斥),以避免不同的线程在同时。 And I have changed the bool stop_threads by std::atomic <bool> stop_threads to avoid race condition with the stop flag shared by all threads.我已经通过std::atomic <bool> stop_threads更改了bool stop_threads以避免竞争条件与所有线程共享的停止标志。

I thinks there is some confusion to what exactly the threads are doing in your example and what you want to do.我认为对于线程在您的示例中到底在做什么以及您想要做什么存在一些混淆。

Just to clarify, you have a main() method, which is invoked repeatedly, in which you need to do some processing?只是为了澄清,您有一个main()方法,该方法被重复调用,您需要在其中进行一些处理? You would like to put this processing in threads which (for a speed boost?) relies on some data that changes every time main() is called?你想把这个处理放在线程中(为了提高速度?)依赖于每次调用main()都会改变的一些数据?

If this is the case, then using the threads in such a way will still be "hoging linear CPU time" .如果是这种情况,那么以这种方式使用线程仍然会“占用线性 CPU 时间” This is because join() will wait for the threads to finish.这是因为join()将等待线程完成。 So in this example, three threads are dispatched to do some work.所以在这个例子中,分派了三个线程来做一些工作。 The execution in main() will pause until the three threads are complete. main()的执行将暂停,直到三个线程完成。 I`ll try and illustrate with the below picture.我会试着用下图来说明。

--- main thread -----|-----|-----|--wait^--wait^ --wait^------return.
                     **T1**|*****|return^      ^       ^
                           **T2**|*******return^       ^
                                 ***T3***********return^

This pattern is still useful for processing lots of information quickly.这种模式对于快速处理大量信息仍然很有用。 The three threads are spawned (re-launched or whatever) every main() call and are used to process information in parallel.这三个线程在每次main()调用时产生(重新启动或其他),并用于并行处理信息。 The threads must also finish every main call.线程还必须完成每个主调用。 This is a common pattern for graphics/ information processing.这是图形/信息处理的常见模式。

So for what you want to do and using method one for threads, simply create a function for every individual thread you want to spawn (with whatever parameter types you need), then create a thread with that function and pass it the parameters you need to process.因此,对于您想要执行的操作并对线程使用方法一,只需为您想要生成的每个单独线程创建一个函数(使用您需要的任何参数类型),然后使用该函数创建一个线程并将您需要的参数传递给它过程。

If you need to communicate results in between threads or back then this is an entire other can of worms.如果您需要在线程之间或回传结果,那么这完全是另一种蠕虫。 You need to do some research into mutexes or even futures or other means of passing information around with threads to avoid race conditions您需要对互斥体甚至期货或其他通过线程传递信息以避免竞争条件的方法进行一些研究

If you need to spawn separates threads that are not re-joined in main and run independently then this is another much larger can of worms.如果您需要将未重新加入 main 并独立运行的线程分离出来,那么这是另一个更大的蠕虫罐。 I think the provided pattern is more useful than having multiple separate threads.我认为提供的模式比拥有多个单独的线程更有用。

I discovered the .detach() operation and came up with something that seems to work, at least when I run my entire code I get seemingly very stable operation, I don't notice runaway memory or processor utilization, and everything opens/closes/terminates without error.我发现了 .detach() 操作并想出了一些似乎有效的东西,至少当我运行我的整个代码时,我得到了看似非常稳定的操作,我没有注意到失控的内存或处理器利用率,并且一切都打开/关闭/无错误地终止。 I put pseudocode below to demonstrate the gist of it.我在下面放了伪代码来演示它的要点。

Does the detach() operation ensure that each thread destroys itself when done? detach() 操作是否确保每个线程在完成后自行销毁? I'm not proud of using globals but this is a first attempt that so far lets me run full speed (30fps).我对使用全局变量并不感到自豪,但这是迄今为止让我全速运行 (30fps) 的第一次尝试。 I have noticed that my total time around the main() loop is greatly reduced using these threads, such that time is now dominated by waiting for the next webcam image to be ready, as expected.我注意到使用这些线程大大减少了我在 main() 循环周围的总时间,因此现在时间主要是等待下一个网络摄像头图像准备就绪,正如预期的那样。

//  ...

// globals
bool g_histodone = true;
string g_histwindow = "histogram";
Mat g_webcamImage;


// thread routine
void histogram(Mat Image, double offset, double gain, bool agc)
{
  Mat scaledImage;

  if (agc) {
    resizedImage.convertTo(scaledImage, -1, 1, offset);
    resizedImage.convertTo(scaledImage, -1, gain);
  }
  else
    resizedImage.copyTo(scaledImage);

  // compute histogram image
  // ...

  // display it
  imshow(g_histwindow, histImage);

  // notify main()
  g_histodone = true;
}


void get_webcam_image(void)
{
  // put images into g_webcamImage (actually a group of rotating buffers) as they come in from webcam
}


int main(int argc, const char *argv[])
{
  bool agc = true;
  bool histo = false;
  bool histwindow_not_destroyed = false;
  double offset = 0;
  double gain = 1;
  Mat localImage;

  thread th_capture = thread(get_webcam_image); // start thread to get webcam images
  thread th_histo = thread(histogram, localImage, offset, gain, agc); // start thread for histogram display

  while (true) {

    // wait here to grab the next camera image from buffers and put it into a local Mat
    g_webcamImage.copyTo(localImage);


    // do some optional processing to determine gain/offset (would be its own thread too)
    if (agc) {
      offset = result 1 from agc thread
      gain = result 2 from agc thread

      // apply scaling to the Mat
      localImage.convertTo(localImage, -1, 1, offset);
      localImage.convertTo(localImage, -1, gain);
    }

    // compute and show histogram
    if (histo) {
      if (g_histodone) { // previous thread is finished, start another
        g_histodone = false;
        if (th_histo.joinable())
          th_histo.join();
        th_histo = thread(histogram, localImage, offset, gain, agc);
      }
    }
    // clean up
    else if (histwindow_not_destroyed) {
      destroyWindow(g_histwindow);
      histwindow_not_destroyed = false; // handshaking
    }

    // ...

    // get user keyboard input
    if (key-to-toggle-histogram-pressed) {
      histo = !histo;
      if (histo)
        namedWindow(g_histwindow, WINDOW_AUTOSIZE); // only want window to appear when enabled
      else
        histwindow_not_destroyed = true; // handshaking
    }
    else if (key-to-toggle-agc-pressed)
      agc = !agc;
    else if (key-to-terminate-app-pressed) {
      th_capture.detach(); // terminate the webcam capture thread
      if (th_histo.joinable())
        th_histo.join(); // terminate the histo thread
      return 0;
    }

  }

}

Regarding Fareanor's approach, that looks like a better way as it passes the values into a single thread like I meant to do but didn't know exactly how to do it;关于 Fareanor 的方法,这看起来是一种更好的方法,因为它像我打算做的那样将值传递到单个线程中,但不知道如何去做; I didn't know a thread if declared outside of the while() loop could automatically see the variables as they update (is this because the thread is using the address not the literal value?) I was guessing that variables passed in statically only at the time a thread was declared.我不知道如果在 while() 循环之外声明的线程可以在变量更新时自动看到它们(这是因为线程使用的是地址而不是文字值?)我猜变量只在线程被声明的时间。 So that's good to know, I'll give it a try.所以很高兴知道,我会尝试一下。

Any advice on how to get variables passed back from a thread into main(), such as my computed agc gain and offset values?关于如何将变量从线程传递回 main() 的任何建议,例如我计算的 agc 增益和偏移值? If I declare a struct containing two doubles, then should this work?如果我声明一个包含两个双打的结构,那么这应该有效吗?

thread th_agc(agcparams = calc_agc, localImage);线程 th_agc(agcparams = calc_agc, localImage);

where calc_agc is my routine of type struct_agcparams for the thread, localImage is the Mat, and agcparams contains the gain/offset doubles I'm trying to pass back.其中 calc_agc 是我的线程 struct_agcparams 类型的例程,localImage 是 Mat,而 agcparams 包含我试图传回的增益/偏移双打。

or is it like this?或者是这样吗?

agcparams = thread th_agc(calc_agc, localImage); agcparams = 线程 th_agc(calc_agc, localImage);

I'll post a new answer in response to your additions.我会发布一个新的答案来回应你的补充。 You should add you edits to the question rather than an answer so it is less confusing for people who land on this page.您应该添加对问题的编辑而不是答案,这样登陆此页面的人就不会感到困惑。

There is a lot of things you are asking about multi-threaded environments which have no simple answers.有很多关于多线程环境的问题都没有简单的答案。 What you are talking about is a hard problem.你说的是一个棘手的问题。 You want some amount of threads to process information that is updated by the original thread.您需要一定数量的线程来处理由原始线程更新的信息。 Then you want to be able to share this information back to the original thread.然后,您希望能够将此信息共享回原始线程。

Firstly, detach() does not ensure the thread cleans up or is stopped.首先, detach()不能确保线程清理或停止。 What it does is allow a thread object to go out of scope (destroyed) whilst allowing the actual thread to continue executing.它所做的是允许thread对象超出范围(销毁),同时允许实际线程继续执行。 This can be really dangerous with run away threads that never stop amongst other problems.这对于从不停止在其他问题中的失控线程来说真的很危险。

Secondly, for variable visibility and using a thread object, the thread can see the variables since it takes a reference as its parameters.其次,对于变量可见性和使用线程对象,线程可以看到变量,因为它将引用作为其参数。 The thread constructor you are using takes a function and the parameters the function requires.您正在使用的线程构造函数接受一个函数和该函数所需的参数。 In Fareanor's answer the functions takes a reference.在 Fareanor 的回答中,函数需要参考。 The variable is in scope of the original main() thread and the function being executed by the thread has a reference to this variable.该变量在原始main()线程的范围内,并且该线程正在执行的函数引用了该变量。 That is why both threads can see and update that variable.这就是为什么两个线程都可以看到并更新该变量的原因。 There is no need to 'pass it back' as main() and the thread have the same variable.无需将其“传回”,因为main()和线程具有相同的变量。 Although this will lead to race conditions.虽然这导致竞争条件。 What happens when one thread is in the middle of writing to the variable and the other one reads it?当一个线程正在写入变量而另一个线程读取它时会发生什么?

There are heaps of problems that are going to arise when using threads in this manner without synchronization.在没有同步的情况下以这种方式使用线程时会出现大量问题。 Is it really worth the effort?真的值得努力吗? If so you should do some research and play with some good only examples of multi-threaded applications.如果是这样,您应该做一些研究并尝试使用一些好的多线程应用程序示例。

To summarize the main problem you going to face (excluded the aforementioned race conditions) is that you have no control over what each thread is executing and when.总结一下您将面临的主要问题(排除上述竞争条件)是您无法控制每个线程正在执行的内容和时间。 Your OS will decide this using a scheduler and context shifting.您的操作系统将使用调度程序和上下文转换来决定这一点。 There is no guarantee that a spawned thread will execute its code in a way that coincides with you main thread.不能保证生成的线程会以与您的main线程一致的方式执行其代码。 For example your execution may look like this.例如,您的执行可能如下所示。

Legend: - queued for execution, * Executing, R read, W write
MAIN: ****W*****W*****W-------------------***R***R***R***
T1  : -----------------***R**----------------------------
T2  : -----------------------**R***R**W------------------
T3  :----------------------------------------------------

Main could loop several times and write to variables multiple times before the others even do anything. Main 可以循环多次并在其他人做任何事情之前多次写入变量。 A thread can read the value of a variable but then its execution is queued before it gets a chance to write (T1).一个线程可以读取一个变量的值,但它的执行在它有机会写入之前排队(T1)。 A thread may read a variable twice but it was not updated by main in between reads (T3).一个线程可能会读取一个变量两次,但在两次读取之间(T3)它没有被main更新。 We can have thread that is not executed for a period of time (T4).我们可以有一段时间(T4)不执行的线程。 When you have independent threads without synchronization, nothing is guaranteed about the order of execution and how much will be executed.当您拥有没有同步的独立线程时,对于执行顺序和将执行的数量没有任何保证

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM