• C/C++基础,ROS中boost::bind的使用


    Boost::thread构建线程的方法:

    (1)thread():构造一个表示当前执行线程的线程对象; 
    (2)explicit thread(const boost::function0& threadfunc): 
         boost::function0可以简单看为:一个无返回(返回void),无参数的函数。这里的函数也可以是类重载operator()构成的函数;该构造函数传入的是函数对象而并非是函数指针,这样一个具有一般函数特性的类也能作为参数传入。

    方式一:全局函数创建线程

    1. #include
    2. #include
    3. #include
    4. //Linux make: g++ -o main main8.c -lboost_system -lboost_thread
    5. using namespace boost;
    6. void thread1(int x){
    7. std::cout << "x= " << x << std::endl;
    8. for (int i = 0; i < 5; i++){
    9. std::cout << "thread1: " << i << std::endl;
    10. }
    11. }
    12. void thread2(int x){
    13. std::cout << "x= " << x << std::endl;
    14. for (int i = 0; i < 5; i++){
    15. std::cout << "thread2: " << i << std::endl;
    16. }
    17. }
    18. int main(){
    19. //boost::thread thrd1(&thread1, 2);//线程中传入函数thrd1 地址,并传入参数2
    20. //boost::thread thrd2(&thread2, 3);
    21. boost::thread thrd1(boost::bind(&thread1, 1));//使用boost::bind()函数,实现函数绑定
    22. boost::thread thrd2(boost::bind(&thread2, 2));
    23. thrd1.join();
    24. thrd2.join();
    25. return 0;
    26. }

            如果线程需要绑定的函数有参数则需要使用boost::bind。比如想使用 boost::thread创建一个线程来执行函数:void f(int i),如果这样写:boost::thread thrd(f)是不对的,因为thread构造函数声明接受的是一个没有参数且返回类型为 void 的类别,而且不提供参数 i 的值 f 也无法运行,这时就可以写:boost::thread thrd(boost::bind(f,1))。涉及到有参函数的绑定问题基本上都是boost::thread、boost::function、boost::bind结合起来使用。 


    方式二:用类内函数创建线程

    1. #include
    2. #include
    3. #include
    4. //Linux make: g++ -o main main8.c -lboost_system -lboost_thread
    5. class HelloWorld{
    6. public:
    7. void hello(){
    8. std::cout << "Hello world, I'm a thread!" << std::endl;
    9. }
    10. void start(){
    11. boost::thread thrd(boost::bind(&HelloWorld::hello,this));
    12. thrd.join();
    13. }
    14. };
    15. int main(int argc, char* argv[]){
    16. HelloWorld hello;
    17. hello.start();
    18. return 0;
    19. }

    方式三:用类内函数创建线程

    1. #include
    2. #include
    3. //Linux make: g++ -o main main8.c -lboost_system -lboost_thread
    4. class HelloWorld{
    5. public:
    6. static void hello(){
    7. std::cout << "Hello world, I'm a thread!" << std::endl;
    8. }
    9. static void start(){
    10. boost::thread thrd(hello);
    11. thrd.join();
    12. }
    13. };
    14. int main(int argc, char* argv[]){
    15. HelloWorld::start();
    16. return 0;
    17. }

    方式四:类外用类内函数创建线程

    1. #include
    2. #include
    3. #include
    4. #include
    5. class HelloWorld{
    6. public:
    7. void hello(const std::string& str){
    8. std::cout << str << std::endl;
    9. }
    10. };
    11. int main(int argc, char* argv[]){
    12. HelloWorld obj;
    13. boost::thread thrd(boost::bind(&HelloWorld::hello, &obj, "Hello world, I'm a thread!"));
    14. thrd.join();
    15. return 0;
    16. }

    更多方式参考 

    boost::bind的用法:

            得益于c++的模板以及操作符重载,去看boost::bind的实现就会发现它是一个有n多重载的函数,这些重载主要是为了适应函数的参数个数。

            其实boost::bind的原理是函数对象,而函数对象就是一个重载了()操作符的对象,这样我们就可以像调用一个方法一样来调用一个类上的这个操作符,比如a(),其实你是在调用a这个对象的()方法,而不是调用一个叫a的方法。

            一般来说boost::bind有两种方式的调用,一种是对自由方法,也取非类方法, 一种是对类方法。

    • 自由方法:boost::bind(函数名, 参数1,参数2,...)
    • 类  方  法:boost::bind(&类名::方法名,类实例指针,参数1,参数2)

            这里需要额外注意的问题是,函数对象也可以像自由方法一样被bind,而boost::function也是一种函数对象。 接下来我们需要注意什么情况下需要用_1, _2这样的参数。

            举个例子:void test(int a, int b, int c)

    • boost::bind(test, 1, _1, _2)得到一个函数对象b,当调用b(3,4)时,相当于调用test(1,3,4)
    • boost::bind(test, _2, 3, _1)得到一个函数对象b,当调用b(3,4)时,相当于调用test(4,3,3)   _1表示占位符,中间的3已经传递进去了,b(3,4),3对应_2,4对应_1,再把占位符的位置按顺序排列就是实际的传递位置
    • boost::bind(test,  1,  2,  3),那么在调用b()时就相当于调用test(1,  2,  3)

            注意,boost::bind里的参数个数一定要与被bind的函数相同,否则这个函数对象就无法生成了。


    Boost::thread线程同步:

    一、互斥量

    互斥变量mutex的对象类大致如下:

        (1).mutex,独占式互斥量,最简单的,而且是最常用的一种互斥变量。

        (2).timed_mutex ,独占式互斥量,并提供超时锁定功能。

        (3).recursive_mutex: 递归式互斥量,可以多次锁定,相应地也要多次解锁。

        (4).recursive_timed_mutex: 它也是递归式互斥量,提供超时锁定功能。

        (5).shared_mutex: multiple-reader/single-writer 型的共享互斥量(又称读写锁)。

    直接操作 mutex,即直接调用 mutex 的 lock / unlock 函数。

    1. #include
    2. #include
    3. #include
    4. //Linux make: g++ -o main main8.c -lboost_system -lboost_thread
    5. using namespace std;
    6. boost::mutex mutex;
    7. void thread(){
    8. for(int i = 0; i < 5; ++i){
    9. usleep(100000);
    10. mutex.lock();
    11. std::cout << "Thread " << boost::this_thread::get_id() << ": " << i << std::endl;
    12. mutex.unlock();
    13. }
    14. }
    15. int main(){
    16. boost::thread t1(&thread);
    17. boost::thread t2(thread);
    18. t1.join();
    19. t2.join();
    20. }

    g++编译方法: 

    g++ -o main main8.c -lboost_system -lboost_thread

    二、lock模板类使用

            lock模板类也分为独占式的 和共享式的。lock模板类与mutex对象结合使用,在lock构造函数中调用互斥体mutex的lock方法,mutex对象 被锁住,一旦离开作用域,会自动调用lock的析构函数,释放锁。

    (1).boost::unique_lock,其中T可以mutex中的任意一种。(独占式),如:

         boost::unique_lock,构造与析构时则分别自动调用lock和unlock方法,不需要手动的释放锁。

    (2).boost::shared_lock,其中的T只能是shared_mutex类。(共享锁)

    使用 lock_guard 自动加锁、解锁。原理是 RAII,和智能指针类似

    1. #include
    2. #include
    3. #include
    4. #include
    5. boost::mutex mutex;
    6. int count = 0;
    7. void Counter() {
    8. // lock_guard 在构造函数里加锁,在析构函数里解锁。
    9. boost::lock_guard lock(mutex);
    10. int i = ++count;
    11. std::cout << "count == " << i << std::endl;
    12. }
    13. int main() {
    14. boost::thread_group threads;
    15. for (int i = 0; i < 4; ++i) {
    16. threads.create_thread(&Counter);
    17. }
    18. threads.join_all();
    19. return 0;
    20. }

    使用 unique_lock 自动加锁、解锁

    unique_lock 与 lock_guard 原理相同,但是提供了更多功能(比如可以结合条件变量使用)。 注意:mutex::scoped_lock 其实就是 unique_lock 的 typedef。(引用)

    1. #include
    2. #include
    3. #include
    4. boost::mutex mutex;
    5. int count = 0;
    6. void Counter() {
    7. boost::unique_lock lock(mutex);
    8. int i = ++count;
    9. std::cout << "count == " << i << std::endl;
    10. }
    11. int main() {
    12. boost::thread_group threads;
    13. for (int i = 0; i < 4; ++i) {
    14. threads.create_thread(&Counter);
    15. }
    16. threads.join_all();
    17. return 0;
    18. }

    使用 shared_lock

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. boost::shared_mutex mutex_;//共享锁
    10. boost::mutex g_io_mutex;
    11. void wait(int seconds){
    12. boost::this_thread::sleep(boost::posix_time::seconds(seconds));
    13. }
    14. class Counter{
    15. public:
    16. Counter():value_(0) {}
    17. size_t Get() const{
    18. boost::shared_lock lock(mutex_); //shared_lock 和share_mutex 配合使用
    19. return value_;
    20. }
    21. void Put(){ //同一时间 只能有一个线程来访问
    22. boost::unique_lock lock(mutex_); //使用lock模板类
    23. value_++;
    24. }
    25. private:
    26. size_t value_;
    27. };
    28. void threadFunc(Counter& counter){
    29. for (int i = 0; i < 3; ++i){
    30. counter.Put(); //写入数据,锁住mutex
    31. size_t value = counter.Get(); //多个线程可以同时读取数据
    32. boost::lock_guard lock(g_io_mutex); //锁定 cout 打印输出,cout 是非线程安全的
    33. std::cout << boost::this_thread::get_id() << ' ' << value << std::endl;
    34. }
    35. }
    36. int main(){
    37. Counter counter;
    38. boost::thread_group threads; //boost threads的线程组
    39. for(int i =0;i< 3;i++){
    40. threads.create_thread(boost::bind(threadFunc, boost::ref(counter)));//ref传入引用
    41. }
    42. threads.join_all();
    43. return 0;
    44. }

    Boost::thread条件变量: 

            condition是一个简单的同步对象,用于使一个线程等待一个特定的条件成立(比如资源可用)。一个condition对象总是和一个mutex对象配合使用。mutex在交给condition对象的wait系列函数时,必须已经通过lock对象加上了锁。当线程陷入等待时,condtion对象将释放mutex上的锁,当wait返回时,mutex上的锁会重新加上,这一unlock/lock动作由conditon对象自动完成。

    先看一个示例:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int number;
    7. boost::mutex m;
    8. boost::condition full;
    9. boost::condition empty;
    10. void writer(){
    11. while(1){
    12. boost::mutex::scoped_lock sl(m);
    13. if (number == 5) {
    14. full.wait(m);
    15. }
    16. ++number;
    17. std::cout << "after w: " << number << std::endl;
    18. empty.notify_one();
    19. }
    20. }
    21. void reader(){
    22. while(1){
    23. boost::mutex::scoped_lock sl(m);
    24. if (number == 0) {
    25. std::cout << std::endl;
    26. empty.wait(m);
    27. }
    28. --number;
    29. std::cout << "after r: " << number <<" ";
    30. full.notify_one();
    31. }
    32. }
    33. int main(){
    34. boost::thread trd1(&writer);
    35. boost::thread trd2(&reader);
    36. trd1.join();
    37. trd2.join();
    38. return 0;
    39. }

     加延时:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int number;
    8. boost::mutex m;
    9. boost::condition full;
    10. boost::condition empty;
    11. void writer(){
    12. while(1){
    13. usleep(500000); //500ms 这里不加延时,会正确打印0-5-4-1,加了延时之后就变成一直打印1010101010...这样的状态
    14. boost::mutex::scoped_lock sl(m);
    15. if (number == 5) {
    16. full.wait(m);
    17. }
    18. ++number;
    19. std::cout << "after w: " << number << std::endl;
    20. empty.notify_one(); //说明这里每次加一个数之后,就立即唤醒reader线程,然后再reader中减减,然后reader又进入了休眠,writer这边又加加,然后循环往复,而writer一直就没有休眠,所以就出现了1010101010...这样的打印
    21. }
    22. }
    23. void reader(){
    24. while(1){
    25. boost::mutex::scoped_lock sl(m);
    26. if (number == 0) {
    27. std::cout << std::endl;
    28. empty.wait(m);
    29. }
    30. --number;
    31. std::cout << "after r: " << number <<" ";
    32. full.notify_one();
    33. }
    34. }
    35. int main(){
    36. boost::thread trd1(&writer);
    37. boost::thread trd2(&reader);
    38. trd1.join();
    39. trd2.join();
    40. return 0;
    41. }

    为了解决这个问题,使其正常输出0-5-4-1这样的序列,就在每次只有满足条件之后才进行通知,也就是notify_one()函数放在if判断条件里面:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int number;
    8. boost::mutex m;
    9. boost::condition full;
    10. boost::condition empty;
    11. void writer(){
    12. while(1){
    13. usleep(500000); //500ms
    14. boost::mutex::scoped_lock sl(m);
    15. if (number == 5) {
    16. empty.notify_one();
    17. full.wait(m);
    18. }
    19. ++number;
    20. std::cout << "after w: " << number << std::endl;
    21. }
    22. }
    23. void reader(){
    24. while(1){
    25. boost::mutex::scoped_lock sl(m);
    26. if (number == 0) {
    27. full.notify_one();
    28. std::cout << std::endl;
    29. empty.wait(m);
    30. }
    31. --number;
    32. std::cout << "after r: " << number <<" ";
    33. }
    34. }
    35. int main(){
    36. boost::thread trd1(&writer);
    37. boost::thread trd2(&reader);
    38. trd1.join();
    39. trd2.join();
    40. return 0;
    41. }

    可以得到正常的输出

    如果再加多个writer和reader线程,结果仍然是不会变的。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int number;
    8. boost::mutex m;
    9. boost::condition full;
    10. boost::condition empty;
    11. void writer(){
    12. while(1){
    13. usleep(500000); //500ms
    14. boost::mutex::scoped_lock sl(m);
    15. if (number == 5) {
    16. empty.notify_one();
    17. full.wait(m);
    18. }
    19. ++number;
    20. std::cout << "after w: " << number << std::endl;
    21. }
    22. }
    23. void reader(){
    24. while(1){
    25. boost::mutex::scoped_lock sl(m);
    26. if (number == 0) {
    27. full.notify_one();
    28. std::cout << std::endl;
    29. empty.wait(m);
    30. }
    31. --number;
    32. std::cout << "after r: " << number <<" ";
    33. }
    34. }
    35. int main(){
    36. boost::thread trd1(&writer);
    37. boost::thread trd2(&reader);
    38. boost::thread trd3(&writer);
    39. boost::thread trd4(&writer);
    40. boost::thread trd5(&reader);
    41. trd1.join();
    42. trd2.join();
    43. trd3.join();
    44. trd4.join();
    45. trd5.join();
    46. return 0;
    47. }

    参考

    帮助理解的简单使用:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. //Linux make:g++ -o main main8.c -lboost_system -lboost_thread
    8. boost::mutex m;
    9. boost::condition empty;
    10. bool wakethread = false;
    11. void writer(){
    12. std::cout << "2秒后阻塞reader" << std::endl;
    13. usleep(2000000);
    14. wakethread = true;
    15. usleep(200000);//等待reader被阻塞
    16. std::cout << "2秒后恢复reader" << std::endl;
    17. usleep(2000000);
    18. empty.notify_one();
    19. }
    20. /*
    21. 线程阻塞时的特点:
    22. 该线程放弃CPU的使用权,暂停运行,只有当阻塞的原因消除后才回到就绪状态进行运行
    23. 被其他的线程中断,该线程也会推出阻塞状态,同时抛出InterruptedException的异常
    24. */
    25. void reader(){
    26. for(int i=0; i<20; i++){
    27. while(wakethread){
    28. wakethread = false;
    29. //std::cout << wakethread << std::endl;
    30. std::cout << "reader suspended" << std::endl;
    31. empty.wait(m); //在这里就开始阻塞,下面的都不执行了
    32. std::cout << "begin reader work" << std::endl; //解除阻塞之后开始从这里执行
    33. }
    34. usleep(200000);
    35. std::cout << "in reader..." << std::endl;
    36. }
    37. }
    38. int main(){
    39. boost::thread trd1(&writer);
    40. boost::thread trd2(&reader);
    41. trd1.join();
    42. trd2.join();
    43. return 0;
    44. }

     

    有关多线程状态机的思考: 

    1.Switch case中,只有当前case完全退出之后才进行状态的切换

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. //Linux make: g++ -o main main8.c -lboost_system -lboost_thread
    8. int curTask;
    9. enum{ TASK_NO, TASK_ON, TASK_NEW };
    10. bool isontask = true;
    11. void writer(){
    12. curTask = TASK_NO;
    13. std::cout << "curTask = TASK_NO, 1.5秒后切换状态" << std::endl;
    14. usleep(1500000);
    15. curTask = TASK_NEW;
    16. std::cout << "curTask = TASK_NEW" << std::endl;
    17. }
    18. void reader(){
    19. for(int i=0; i<7; i++){
    20. switch(curTask){
    21. case TASK_NO:
    22. std::cout << "TASK_NO" << std::endl;
    23. usleep(1000000);
    24. std::cout << "TASK_NO sleep 1s end......" << std::endl;
    25. break;
    26. case TASK_NEW:
    27. std::cout << "TASK_NEW" << std::endl;
    28. break;
    29. case TASK_ON:
    30. std::cout << "TASK_ON" << std::endl;
    31. break;
    32. }
    33. usleep(100000);
    34. }
    35. }
    36. int main(){
    37. boost::thread trd1(&writer);
    38. boost::thread trd2(&reader);
    39. trd1.join();
    40. trd2.join();
    41. return 0;
    42. }

            从结果可以看出,在1.5s的时候状态机发生了切换,而此时reader线程还处于sleep状态,当sleep结束需要再打印一下 TASK_NO sleep 1s end...... 直到 break 才进行下一次的条件判断,也就是说必须把当前的case走完,当前线程才会真正的切换状态,而非在 writer 中标志位切换的时候,reader线程就立即进入这个状态了,需要当前状态运行完毕,才会发生状态切换。

    2.while循环的退出只跟当前判断的条件有关,而跟case的状态切换无关,除非case中耦合了当前状态

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. //Linux make: g++ -o main main8.c -lboost_system -lboost_thread
    8. int curTask;
    9. enum{ TASK_NO, TASK_ON, TASK_NEW };
    10. bool isontask = true;
    11. void writer(){
    12. curTask = TASK_NO;
    13. std::cout << "curTask = TASK_NO, 1.5秒后切换状态" << std::endl;
    14. usleep(1500000);
    15. curTask = TASK_NEW;
    16. std::cout << "curTask = TASK_NEW" << std::endl;
    17. }
    18. void reader(){
    19. for(int i=0; i<7; i++){
    20. switch(curTask){
    21. case TASK_NO:
    22. while(isontask){
    23. std::cout << "TASK_NO in while......" << std::endl;
    24. usleep(300000);
    25. }
    26. break;
    27. case TASK_NEW:
    28. std::cout << "TASK_NEW" << std::endl;
    29. break;
    30. case TASK_ON:
    31. std::cout << "TASK_ON" << std::endl;
    32. break;
    33. }
    34. usleep(100000);
    35. }
    36. }
    37. int main(){
    38. boost::thread trd1(&writer);
    39. boost::thread trd2(&reader);
    40. trd1.join();
    41. trd2.join();
    42. return 0;
    43. }

            可以看到,即使在writer中1.5s后切换了状态, reader线程依然还是处于TASK_NO这个case,没有退出。而while的判断条件耦合了当前状态,即可安全退出while循环,上面的while判断条件换成下面这样的:

    while(isontask && curTask==TASK_NO)

    输出:

    发现可以正常的进行条件的选择和退出了。

    哎编码水平有点低,都是坑啊...

  • 相关阅读:
    idea创建springboot项+集成阿里连接池druid
    如何挂载镜像文件(两种方法) 以及利用镜像文件配置本地yum源
    结合实战,浅析GB/T28181(四)——录像回放及控制
    WPF绑定单变量Binding和绑定多变量MultiBinding 字符串格式化 UI绑定数据,数据变化自动更新UI,UI变化自动更新数据
    有没有人声和背景音乐分离的神器?
    代理技术的崭新纪元:Socks5代理和代理IP的多重应用
    【最新鸿蒙应开发】——HarmonyOS沙箱目录
    9月14日计算机视觉基础学习笔记——基本图像处理
    JavaScript-DOM实战案例
    剑指 Offer 04. 二维数组中的查找
  • 原文地址:https://blog.csdn.net/qq_34761779/article/details/127681991