当前位置:   article > 正文

C++进行代码加速、多线程等知识点。pthread、openomp进行多线程加速、SSE进行加速

openomp

1、关于多线性进行全局变量的使用的时候为什么需要加锁?

a、在多线程中对全局变量进行处理的时候,如果涉及到在线程中对全局变量进行赋值的话,则需要进行加锁。

如果不加锁的话,容易导致全局变量在某个线程中被修改后,影响另一线程的数据处理。

b、全局变量不加锁主要是为了数据处理的正正确性,而不是为了防止导致多个线程同时操作同一个数据时出现程序崩溃。因为这种情况是不会产生的。

下面是一个多线程对多个全局变量进行赋值操作和读取操作。其都不会出现程序崩溃现象下面c、的代码也可以看出其不会导致崩溃。

c、下面是关于线程的join和detach的区别:

 

    int pthread_detach(pthread_t tid); 若成功则返回0,若出错则为非零。

    pthread_detach用于分离可结合线程tid。线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。

    如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。

    由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码

    pthread_detach(pthread_self())

或者父线程调用

    pthread_detach(thread_id)(非阻塞,可立即返回)

这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。

  注意:第一:如果使用detach要小心点,就是如果线程的执行时间太久,会导致主线程会在上次子线程还没执行完就带着新数据或者在新一轮里改变了子线程里的一些全局变量。这是就需要记得加锁,来防止数据不一致。第二:如果detach后面的代码是需要子线程执行完后的处理结果的则不能使用detach方式,因为子线程还没有处理后结果,后面代码就进行结果读取的话,要么导致内存泄漏,要么是结果数据不对。

下面是使用pthread_join创建线程。

  1. #include"3rdparty\include\pthread.h"
  2. #include"opencv2\opencv.hpp"
  3. using namespace cv;
  4. int* hhnum; Mat img;
  5. int threadnums = 6;
  6. void* jia(void*params)
  7. {
  8. int args = *(int*)params;
  9. free(params);
  10. while (true)
  11. {
  12. img = imread("3.jpg");
  13. *hhnum =args;
  14. std::cout << *hhnum << std::endl;
  15. imwrite("4.jpg", img);
  16. }
  17. }
  18. void main()
  19. {
  20. hhnum = new int[1];
  21. pthread_t *threads = (pthread_t*)calloc(threadnums, sizeof(pthread_t));
  22. for (size_t i = 0; i < threadnums; i++)
  23. {
  24. int*ptr = (int*)calloc(1, sizeof(int));
  25. *ptr = i;
  26. pthread_t thread;
  27. pthread_create(&thread, 0, jia, ptr);
  28. threads[i] = thread;
  29. }
  30. for (size_t j = 0; j < threadnums; j++)
  31. {
  32. try
  33. {
  34. pthread_join(threads[j], 0); //这个创建的线程是阻塞式的,只有当所有的线程结束后,程序才会往下执行。
  35. }
  36. catch (Exception e)
  37. {
  38. int i = 0;
  39. }
  40. }
  41. }

  注意:这种这线程是不可分离线程,其需要等待所有的线程执行完成才可以继续执行。上面的代码会一直阻塞在主线程上。其一般跟joinable配合使用。其中joinable:代表该线程是否是可执行线程,其true代表可执行,false代表不可执行,不可执行意味着改线程要么没被创建,要么已经带有join是否相关资源了。其配合使用代码如下:

  1. if (t_cap.joinable()) {
  2. t_cap.join();
  3. ++fps_cap_counter;
  4. cur_frame = cap_frame.clone();
  5. }

  注意:在调用碗join()后,其中joinable()就返回的就是false了、

下面使用pthread_detach来创建可分离线程。

  1. #include"3rdparty\include\pthread.h"
  2. #include"opencv2\opencv.hpp"
  3. using namespace cv;
  4. int* hhnum; Mat img;
  5. int threadnums = 10;
  6. void* jia(void*params)
  7. {
  8. int args = *(int*)params;
  9. free(params);
  10. cvNamedWindow("krk");
  11. for (size_t i = 0; i < args; i++)
  12. {
  13. img = imread("3.jpg");
  14. *hhnum = args;
  15. //std::cout << *hhnum << std::endl;
  16. imwrite("4.jpg", img);
  17. cvWaitKey(100 - i);
  18. if (args == 3)
  19. {
  20. while (true)
  21. {
  22. }
  23. }
  24. }
  25. return (void*)8;
  26. }
  27. void main()
  28. {
  29. hhnum = new int[1];
  30. cvNamedWindow("kk");
  31. pthread_t *threads = (pthread_t*)calloc(threadnums, sizeof(pthread_t));
  32. for (size_t i = 0; i < threadnums; i++)
  33. {
  34. int*ptr = (int*)calloc(1, sizeof(int));
  35. *ptr = i;
  36. pthread_t thread;
  37. pthread_create(&thread, 0, jia, ptr);
  38. threads[i] = thread;
  39. }
  40. for (size_t j = 0; j < threadnums; j++)
  41. {
  42. try
  43. {
  44. pthread_detach(threads[j]);
  45. std::cout << j << std::endl;
  46. }
  47. catch (Exception e)
  48. {
  49. int i = 0;
  50. }
  51. }
  52. waitKey(0);
  53. }

其输出结果是:

注意:其是立即返回的。并且但线程执行完后,系统会自动回收线程资源。其中第三个线程还一直在执行着。

2、pthread、openomp进行多线程加速:

其中pthread实现多线程比openomp麻烦,但是其加速的速度比openomp的快;而且pthread实现的多线程的限制没有openomp那么多。

关于两个使用的个人见解:

a、pthread适合实现代码比较多的地方。而openomp适合代码比较少的地方特别适合多重for循环的地方。

b、pthread适合一些openomp无法实现多线程的地方。例如一些具有bread代码的地方,使用openomp会报错。

下面是根据seataface实现openomp的例子:

  1. clock_t start, finish;
  2. float duration;
  3. Mat imgg = imread("13.jpg");
  4. for (size_t i = 0; i < 90; i++)
  5. {
  6. auto starts = chrono::system_clock::now();
  7. //openomp实现格式#pragma omp 指令...; parallel表示这段代码将被多个线程并行执行。
  8. //num_threads 指定并行域内的线程的数目。
  9. #pragma omp parallel num_threads(4)
  10. {
  11. //用来取消栅障。其栅障是用于线程同步的一种方法,线程遇到栅障时必须等待,知道并行的所有的线程都到达同一个点。
  12. #pragma omp for nowait
  13. for (int32_t i = 0; i < 1; i++)
  14. {
  15. for (int32_t j = 0; j < imgg.cols; j++)
  16. {
  17. imgg.at<Vec3b>(i, j)[0] = imgg.at<Vec3b>(i, j)[0] * 0.01;
  18. imgg.at<Vec3b>(i, j)[1] = imgg.at<Vec3b>(i, j)[1] * 0.01;
  19. imgg.at<Vec3b>(i, j)[2] = imgg.at<Vec3b>(i, j)[2] * 0.01;
  20. }
  21. }
  22. }
  23. auto finishs = chrono::steady_clock::now();
  24. cout << "处理时间为:" << (short)chrono::duration_cast<chrono::milliseconds> (finishs - starts).count() << endl;
  25. }

下面介绍openomp的知识:

其中一些库函数:

      函数原型                                         功能

      int omp_get_num_procs(void)      返回当前可用的处理器个数

      int omp_get_num_threads(void)  返回当前并行区域中活动线程的个数,如果在并行区域外部调用,返回1

      int omp_get_thread_num(void)    返回当前的线程号(omp_get_thread_ID更好一些)

      int omp_set_num_threads(void)   设置进入并行区域时,将要创建的线程个数

  函数声明                                                                   功能

  void omp_init_lock(omp_lock*)                               初始化互斥器

  void omp_destroy_lock(omp_lock*)                        销毁互斥器

  void omp_set_lock(omp_lock*)                               获得互斥器

  void omp_unset_lock(omp_lock*)                           释放互斥器

  void omp_test_lock(omp_lock*)                              试图获得互斥器,如果获得成功则返回true,否则返回false

隐式栅障(Barrier)是OpenMP用于线程同步的一种方法。线程遇到栅障时必须等待,知道并行的所有线程都到达同一点。

nowait:是取消栅障的指令。

OpenMP基本概念:
OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C、C++和Fortran。OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序。程序中已有的OpenMP指令不会影响程序的正常编译运行。在VS中启用OpenMP很简单,很多主流的编译环境都内置了OpenMP在项目上右键->属性->配置属性->C/C++->语言->OpenMP支持,选择“是”即可。

OpenMP执行模式:
OpenMP采用fork-join的执行模式。开始的时候只存在一个主线程,当需要进行并行计算的时候,派生出若干个分支线程来执行并行任务。当并行代码执行完成之后,分支线程会合,并把控制流程交给单独的主线程。

一个典型的fork-join执行模型的示意图如下:

OpenMP编程模型以线程为基础,通过编译制导指令制导并行化,有三种编程要素可以实现并行化控制,他们分别是编译制导、API函数集和环境变量。
3、使用SSE计算单元进行算法加速:

  1. //计算宇炫距离适合很合适
  2. float simd_dot(const float* x, const float* y, const long& len) {
  3. float inner_prod = 0.0f;
  4. __m128 X, Y; // 128-bit values
  5. __m128 acc = _mm_setzero_ps(); // 初始化为 (0, 0, 0, 0)
  6. float temp[4];
  7. long i;
  8. //这里是要计算x,y之间的对应位相乘后相加的总结果。
  9. for (i = 0; i + 4 < len; i += 4) {
  10. X = _mm_loadu_ps(x + i); // 这里使用的是SSE指令集加速,128位可以加载四个float值。
  11. Y = _mm_loadu_ps(y + i);
  12. acc = _mm_add_ps(acc, _mm_mul_ps(X, Y));
  13. }
  14. _mm_storeu_ps(&temp[0], acc); // 存储到一个数组里
  15. inner_prod = temp[0] + temp[1] + temp[2] + temp[3];
  16. // 累加剩下的值
  17. for (; i < len; ++i) {
  18. inner_prod += x[i] * y[i];
  19. }
  20. return inner_prod;
  21. }

注意:这里的SSE的指令集操作也是有开销的,如果len长度不大,就可以不使用sse加速。

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/267887
推荐阅读
相关标签
  

闽ICP备14008679号