Linux 系统编程 学习:10-线程:线程的属性
Linux 系统编程 学习:10-线程:线程的属性
背景
上一讲我们介绍了线程的创建,回收与销毁;简单地提到了线程属性。这一讲我们就来具体看看,线程的属性。
概述
#include <pthread.h>
typedef struct __pthread_attr_s
{
int __detachstate; // 线程的分离状态
int __schedpolicy; // 线程调度策略
structsched_param __schedparam; // 线程的调度参数
int __inheritsched; // 线程的继承性
int __scope; // 线程的作用域
size_t __guardsize; // 线程栈末尾的警戒缓冲区大小
int __stackaddr_set; // 线程的栈设置
void* __stackaddr; // 线程栈的位置
size_t __stacksize; // 线程栈的大小
} pthread_attr_t;
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
线程具有属性,用pthread_attr_t表示,在对该结构进行处理之前必须进行初始化(pthread_attr_init),在使用后需要对其去除初始化(pthread_attr_destroy)。
初始化为默认属性
int pthread_attr_init(pthread_attr_t *attr);
描述:初始化一个线程属性对象,重置为当前系统支持线程的所有属性的默认值。
| 属性 | 值 |
|---|---|
| __scope | PTHREAD_SCOPE_PROCESS |
| __tetachstate | PTHREAD_CREATE_JOINABL |
| __stackaddr | NULL |
| __stacksize | 1M |
| __sched_param.priority | 0 |
| __inheritsched | PTHREAD_INHERIT_SCHED |
| __schedpolicy | SCHED_OTHER |
反初始化
int pthread_attr_destroy(pthread_attr_t *attr);
描述:销毁一个线程属性对象,使它在重新初始化之前不能重新使用。
原理:用无效的值设置了属性对象。
因此:如果经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。
detachstate 分离状态
我们来分析结构体中的有关成员。
int pthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate);
// 有set就有get 。
int pthread_attr_getdetachstate(const pthread_attr_t* attr, int *detachstate)
描述:设置线程是否和其他线程分离(能否调用pthread_join()回收), 运行时可以调用pthread_detach() 完成。
int pthread_detach(pthread_t thread);
参数解析:
attr:设置的属性对象
detachstate :分离状态
- PTHREAD_CREATE_JOINABLE(默认):线程的资源在退出后自行释放。
- PTHREAD_CREATE_DETACHED:
设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置) 则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
返回值:成功返回0,失败返回错误号。
schedpolicy 调度策略与优先级
调度策略
如果主线程是唯一的线程,那么基本上不会被调度出去。另一方面,如果可运行的线程数大于CPU的数量,那么操作系统最终会将某个正在运行的线程调度出去,从而使其他线程能够使用CPU。这将导致一次上下文切换。在这个过程中将保存当前运行线程的执行上下文,并将新调度进来的线程的执行上下文设置为当前上下文。
Linux内核的三种调度策略:
- SCHED_OTHER 分时调度策略,默认的调度策略
- SCHED_FIFO 实时调度策略,先到先服务。一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃
- SCHED_RR 实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平
继承创建者的调度策略
只有在不继承时,下面的操作才是有效的。
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);
描述:设置线程是否继承创建者优先级属性
参数解析:
inheritsched : 是否继承
- PTHREAD_INHERIT_SCHED 继承
- PTHREAD_EXPLICIT_SCHED 不继承
获取可设置的优先级
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
描述:获取本线程的最大/小优先级。
返回值:成功时返回最大/小值,失败返回-1。
设置线程的调度策略与优先级
int pthread_setschedparam(pthread_t thread, int policy,
const struct sched_param *param);
int pthread_getschedparam(pthread_t thread, int *policy,
struct sched_param *param);
/* 用到的结构体 */
struct sched_param {
int sched_priority; /* Scheduling priority */
};
描述:设置线程的调度策略, 运行时可以调用pthread_setschedparam()来改变。
参数解析:
policy:
- (默认)SCHED_OTHER(正常、非实时)
- SCHED_RR(实时、轮转法)
- SCHED_FIFO(实时、先入先出)
param:优先级(越大越高)
优先级与调度的例程
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *task0(void *arg)
{
while(1)
{
//sleep(1);
printf("task0.\n");
}
}
void *task1(void *arg)
{
while(1)
{
sleep(1);
printf("task1.\n");
}
}
int main(void)
{
pthread_attr_t attr;
struct sched_param parm;
pthread_t tid0, tid1;
void* retval;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); //不继承创建者的调度策略,而是设置以下的调度
pthread_attr_setschedpolicy(&attr, SCHED_FIFO); //为线程属性设置调度策略
parm.sched_priority = 1; // 设置线程优先级
pthread_attr_setschedparam(&attr,&parm); // 设置线程优先级
pthread_create(&tid0, &attr, task0, NULL); // 为线程task0设置优先级
pthread_create(&tid1, NULL , task1, NULL); // 让线程task1使用默认优先级
while(1);
// 等待线程的结束(实际上由于线程一直在循环中,所以main函数不会结束)
pthread_join(tid0, &retval); // 等待线程的结束,并取返回值
pthread_attr_destroy(&attr);
pthread_join(tid1, &retval); // 等待线程的结束,并取返回值
}
设置线程的竞争作用域
线程的竞争作用域:表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。
int pthread_attr_setscope(pthread_attr_t*attr,int scope);
int pthread_attr_getscope(const pthread_attr_t*attr,int*scope);
描述:设置线程的竞争范围。
使用前,需要将
pthread_attr_setinheritsched设置为PTHREAD_EXPLICIT_SCHED
参数解析:
scope:
- PTHREAD_SCOPE_SYSTEM :与系统中所有线程一起竞争CPU时间
- PTHREAD_SCOPE_PROCESS:仅与同进程中的线程竞争CPU。
根据
man pthread_attr_setscope的结果来看目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。Linux supports
PTHREAD_SCOPE_SYSTEM, but notPTHREAD_SCOPE_PROCESS
设置堆栈
线程可以设置堆栈地址(stackaddr)与大小(stacksize)。对于大部分程序是应该避免使用的。
如果使用attr创建多个线程,则调用方必须在对pthread_create()的调用之间更改堆栈地址属性;否则,线程将尝试为其堆栈使用相同的内存区域,随后将出现混乱。
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
// 或者由这2个函数分别设置
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr,
void **stackaddr, size_t *stacksize);
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);
当应用程序使用
pthread_attr_setstack()时,它将接管分配堆栈的责任;此时,pthread_attr_setguardsize()设置的任何保护大小值都将被忽略。如果认为有必要,应用程序有责任分配一个保护区(防止读写的一个或多个页面)来处理堆栈溢出的可能性。stackaddr中指定的地址应该适当对齐:为了完全可移植,请在页面边界(
sysconf(_SC_PAGESIZE))上对齐它。posix_memalign()可用于分配。stacksize也应该是系统页面大小的倍数。
关于大小
默认情况下线程保留1M的,而且会在堆栈的顶增加一个空闲的内存页,当访问该内存页的时候就会触发SIGSEGV信号,如果开发者设置了stack size那么就需要用户制定这个多余的内存页并且通过mprotect函数设置保护标志,而且它必须设置调用pthread_attr_setdetachstate 且设置PTHREAD_CREATE_JOINABLE模式,因为只有其他线程调用pthread_join后分配的资源才会被释放, 线程的堆栈的分配必须大于一个最小值PTHREAD_STACK_MIN() 。当分配内的时候会设置MAP_NORESERVE标志(mmap),这个标志表示不预留交换空间,当对该内存进行写的时候,如果系统不能分配到交换空间,那么就会触发SIGSEGV信号,如果可以分配到交换空间,那么就会把private page复制到交换空间。如果mmap没有指定MAP_NORESERVE,在分配空间的时候就会保留和映射区域相同大小的交换空间(这个其实就是资源的滞后分配原则)
关于地址
如果线程地址为NULL,那么pthread分配指定的内存(1M)或者是指定的堆栈大小,如果设定了堆栈的地址那么内存的分配必须由开发者设定,例如:
stackbase = (void *) malloc(size);
ret = pthread_attr_setstacksize(&tattr, size);
ret = pthread_attr_setstackaddr(&tattr, stackbase);
ret = pthread_create(&tid, &tattr, func, arg);
affinity 设置线程亲和性
线程的亲和性
CPU 亲和性(affinity) 就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
亲和性分为软亲和性与硬亲和性2种:
- 软亲和性 :进程并不会在处理器之间频繁迁移
- 硬亲和性:进程需要在您指定的处理器上运行
CPU的数量与表示
在有n个CPU的Linux上,CPU是用0...n-1来进行一一标识的。
CPU的数量可以通过proc文件系统下的CPU相关文件得到,如cpuinfo和stat:
cat /proc/stat | grep "^cpu[0-9]\+" | wc -l
在系统编程中,可以直接调用库调用sysconf获得:sysconf(_SC_NPROCESSORS_ONLN);
#define _GNU_SOURCE
int pthread_attr_setaffinity_np(pthread_attr_t *attr,
size_t cpusetsize, const cpu_set_t *cpuset);
int pthread_attr_getaffinity_np(const pthread_attr_t *attr,
size_t cpusetsize, cpu_set_t *cpuset);
/* 运行时设置 */
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);
/*
cpu_set_t:可以理解为一个CPU的集合,通过约定好的宏进行清除,设置以及判断
*/
void CPU_ZERO(cpu_set_t *set); //(初始化操作)
void CPU_SET(int cpu,cpu_set_t *set);//(将某个cpu加进cpu集里)
void CPU_CLR(int cpu,cpu_set_t *set);//(将某个cpu清除出cpu集里)
void CPU_ISSET(int cpu,const cpu_set_t *set);// (判断某个cpu是不是在cpu集里)
在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与 亲和性(affinity)相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。
如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。
例程
#define _GNU_SOURCE
#include <stdio.h>
#include <math.h>
#include <pthread.h>
cpu_set_t cpuset,cpuget;
double waste_time(long n)
{
double res = 0;
long i = 0;
while (i <n * 200000000) {
i++;
res += sqrt(i);
}
return res;
}
void *thread_func(void *param)
{
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset); /* cpu 0 is in cpuset now */
/* bind process to processor 0 */
if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) !=0) {
perror("pthread_setaffinity_np");
}
printf("Core 0 is running!\n");
/* waste some time so the work is visible with "top" */
printf("result: %f\n", waste_time(5));
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t my_thread;
time_t startwtime, endwtime;
startwtime = time (NULL);
if (pthread_create(&my_thread, NULL, thread_func,NULL) != 0) {
perror("pthread_create");
}
pthread_join(my_thread,NULL);
endwtime = time (NULL);
printf ("wall clock time = %d\n", (endwtime - startwtime));
return 0;
}
Linux 系统编程 学习:10-线程:线程的属性的更多相关文章
- Linux 系统编程 学习:11-线程:线程同步
Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...
- Linux 系统编程 学习:09-线程:线程的创建、回收与取消
Linux 系统编程 学习:09-线程:线程的创建.回收与取消 背景 我们在此之前完成了 有关进程的学习.从这一讲开始我们学习线程. 完全的开发可以参考:<多线程编程指南> 在Linux ...
- Linux 系统编程 学习:01-进程的有关概念 与 创建、回收
Linux 系统编程 学习:01-进程的有关概念 与 创建.回收 背景 上一讲介绍了有关系统编程的概念.这一讲,我们针对 进程 开展学习. 概念 进程的身份证(PID) 每一个进程都有一个唯一的身份证 ...
- Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号
Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...
- Linux 系统编程 学习:04-进程间通信2:System V IPC(1)
Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...
- Linux 系统编程 学习:05-进程间通信2:System V IPC(2)
Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...
- Linux 系统编程 学习 总结
背景 整理了Liunx 关于 进程间通信的 很常见的知识. 目录 与 说明 Linux 系统编程 学习:000-有关概念 介绍了有关的基础概念,为以后的学习打下基础. Linux 系统编程 学习:00 ...
- Linux 系统编程 学习:00-有关概念
Linux 系统编程 学习:00-有关概念 背景 系统编程其实就是利用系统中被支持的调度API进行开发的一个过程. 从这一讲开始,我们来介绍有关Linux 系统编程的学习. 知识 在进行Linux系统 ...
- Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道
Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...
随机推荐
- idea查询类的继承关系图
方式一:在一个类中,鼠标右键: 结果如下图所示: 方式2:在一个类中 结果如图:
- MySQL 5.7二进制日志
简介 二进制日志是MySQL服务器用来记录数据修改事件的,比如INSERT.UPDATE.DELETE等会导致数据发生变化的语句,SELECT语句不会被记录在内.MySQL必须先执行完一条语句才能知道 ...
- 细嚼JS闭包知识点及案例分析
闭包是js开发惯用的技巧,什么是闭包? 闭包指的是:能够访问另一个函数作用域的变量的函数.清晰的讲:闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量.默认闭包的this指向windows. ...
- Python练习题 017:三支乒乓球队出赛名单
[Python练习题 017] 两个乒乓球队进行比赛,各出三人.甲队为a,b,c三人,乙队为x,y,z三人.已抽签决定比赛名单.有人向队员打听比赛的名单.a说他不和x比,c说他不和x,z比.请编程序找 ...
- Word rings
Word rings 题目描述 这道题就是想求出所有的环,然后在所有环中比较出环串的平均长度最长的那一个,然后就输出平均长度最长的,如果在一个点当中的样例中没有环的话我们就应该输出"No S ...
- shiro入门学习--授权(Authorization)|筑基初期
写在前面 经过前面的学习,我们了解了shiro中的认证流程,并且学会了如何通过自定义Realm实现应用程序的用户认证.在这篇文章当中,我们将学习shiro中的授权流程. 授权概述 这里的授权指的是授予 ...
- DM8数据库备份还原的原理及应用
(本文部分内容摘自DM产品技术支持培训文档,如需要更详细的文档,请查询官方操作手册,谢谢) 一.原理 1.DM8备份还原简介 1.1.基本概念 (1)表空间与数据文件 ▷ DM8表空间类型: ▷ SY ...
- Python 中 pip 工具的安装与使用
pip 是 Python 包管理工具,该工具提供了对Python 包的查找.下载.安装.卸载的功能. 目前如果你在 python.org 下载最新版本的安装包,则是已经自带了该工具. Python 2 ...
- RHSA-2018:1700-重要: procps-ng 安全更新(存在EXP、本地提权)
[root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...
- 使用react Context+useReducer替代redux
首先明确一点,Redux 是一个有用的架构,但不是非用不可.事实上,大多数情况,你可以不用它,只用 React 就够了. 曾经有人说过这样一句话. "如果你不知道是否需要 Redux,那就是 ...