当前位置:   article > 正文

线程属性详解

线程属性

一、线程的意义:

  1. 实现真正的并发;
  2. 有一些情况下会产生阻塞,我们可以将阻塞放到某个线程中,保证我们程序整体可以继续向下执行;
  3. 对于一些计算密集的任务,我们可以专门开启一个线程去执行。

二、再分析创建线程函数

 #include <pthread.h>
  int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                      void *(*start_routine) (void *), void *arg);
  • 1
  • 2
  • 3
  1. thread:线程的ID;
  2. attr: 线程的属性,这些属性包括很多东西,比如线程优先级,调度策略,线程栈的大小;
  3. void *(*start_routine) (void *):线程执行的函数;
  4. arg:传递给线程的函数的参数。

三、设置线程的优先级
Linux内核的三种调度策略:

  1. SCHED_OTHER 分时调度策略: 它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。
  2. SCHED_FIFO实时调度策略,先到先服务。 它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部((POSIX 1003.1)。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。
  3. SCHED_RR实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。

系统创建线程时,默认的线程是SCHED_OTHER。所以如果我们要改变线程的调度策略的话,可以通过下面的这个函数实现。

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
  • 1
  • 2
  • 3

四、将线程与CPU”捆绑“: CPU的亲和性

  1. 捆绑的意义:不论对于进程还是线程来说,CPU缓存是及其宝贵的,如果频繁的将线程或者进程切换,那么必定会造成缓存的失效,所有的上下文都需要更换,实际上这是效率上的极大的损失;
  2. CPU的亲和性 : 就是进程和线程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,也称为CPU关联性;再简单的点的描述就将指定的进程或线程绑定到相应的cpu上;在多核运行的机器上,每个CPU本身自己会有缓存,缓存着进程和线程使用的信息,而进程或线程可能会被OS调度到其他CPU上,如此,CPU cache(L1,L2,L3)命中率就低了,当绑定CPU后,进程或线程就会一直在指定的cpu跑,不会由内核调度到其他CPU上,性能有一定的提高。
  3. 如何在程序中设置呢
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                                  const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
                                  cpu_set_t *cpuset);
  • 1
  • 2
  • 3
  • 4

补充:cache line
https://blog.csdn.net/KingOfMyHeart/article/details/97942190

五、线程的状态: 分离与非分离

  1. 非分离状态:这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源;
  2. 而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源;
  3. 线程的状态说明什么问题:主要体现是资源释放上;
  4. 线程正确结束的方法:return 。
在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。
1.
一个可结合的线程能够被其他线程收回其资源和杀死;
在被其他线程回收之前,它的存储器资源(如栈)是不释放的。
2.
相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。



1.
线程的分离状态决定一个线程以什么样的方式来终止自己。
在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。
只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
2.
而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
程序员应该根据自己的需要,选择适当的分离状态。
所以如果我们在创建线程时就知道不需要了解线程的终止状态,
则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

可以参考这个链接,写的很明白:https://blog.csdn.net/lhf_tiger/article/details/8291984

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                                  const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
                                  cpu_set_t *cpuset);
  • 1
  • 2
  • 3
  • 4

六、线程栈的大小以及私有数据:

  1. 线程栈默认大小为1M,我们知道,一般线程的独有的资源也就是栈和小部分寄存器;
  2. 线程的私有数据:设计中有必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。比如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由 Posix 线程库维护,成为线程私有数据 (Thread-specific Data,或称为 TSD)。

线程级别的全局变量,是不是听起来很奇怪;

int i ; //全局变量,但是对于每个线程它的值可能不一样
  • 1

线程私有数据: https://blog.csdn.net/caigen1988/article/details/7901248

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);

errno :key - value(线程设置errno的key,并且设置errno的值)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

七、栈溢出保护区

出于以下两个原因,为应用程序提供了 guardsize 属性:

原因一: 溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。

原因二:线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。

guardsize 参数提供了对栈指针溢出的保护。

  1. 如果创建线程的栈时使用了保护功能,则实现会在栈的溢出端分配额外内存。此额外内存的作用与缓冲区一样,可以防止栈指针的栈溢出。如果应用程序溢出到此缓冲区中,这个错误可能会导致 SIGSEGV 信号被发送给该线程。
  2. 如果 guardsize 为零,则不会为使用 attr 创建的线程提供溢出保护区
  3. 如果 guardsize 大于零,则会为每个使用 attr 创建的线程提供大小至少为 guardsize 字节的溢出保护区。缺省情况下,线程具有实现定义的非零溢出保护区。

允许合乎惯例的实现,将 guardsize 的值向上舍入为可配置的系统变量 PAGESIZE 的倍数。请参见 sys/mman.h 中的 PAGESIZE。如果实现将 guardsize 的值向上舍入为 PAGESIZE 的倍数,则以 guardsize(先前调用 pthread_attr_setguardsize() 时指定的溢出保护区大小)为单位存储对指定 attr 的 pthread_attr_getguardsize() 的调用。

八、线程对资源的竞争范围

  1. 线程资源的竞争范围(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS);
  2. 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
  • 1
  • 2
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/181948
推荐阅读
相关标签
  

闽ICP备14008679号