傻白探索Chiplet,Gem5实例程序分析(九)

记录一下在运行Chiplet-Gem5-SharedMemory-main项目时,遇到的关于C++线程的一些知识:

目录

1.sysconf

2.线程pthread_t

2.1 创建线程

2.2 结束线程

2.3 线程阻塞

3.线程屏障pthread_barrier_t

3.1 屏障的初始化

3.2 屏障的释放

3.3 屏障下的等待

3.4 实例

4.互斥变量和条件变量

4.1 互斥变量 pthread_mutex_t

4.2 条件变量 pthread_cond_t

5.CPU亲和力掩码和CPU亲和力

5.1 CPU亲和力掩码

5.2 CPU亲和力


1.sysconf

coreNum = sysconf(_SC_NPROCESSORS_ONLN);

使用系统调用sysconf来获取当前计算机上处于在线状态的处理器(即 CPU)的数量。 _SC_NPROCESSORS_ONLN 是一个常量,表示当前计算机上的在线处理器数量。 sysconf 函数的作用是根据参数提供的信息,返回相应的系统配置信息。常见的参数包括:

  • _SC_ARG_MAX:获取命令行参数的最大字节数
  • _SC_CHILD_MAX:获取单个进程可以创建的子进程数量
  • _SC_CLK_TCK:获取每秒时钟滴答数(即每秒的时钟次数)
  • _SC_OPEN_MAX:获取单个进程可以打开的最大文件数
  • _SC_JOB_CONTROL:判断系统是否支持作业控制
  • _SC_NGROUPS_MAX:获取单个进程可以加入的最大组数
  • _SC_NPROCESSORS_CONF:获取系统可以使用的 CPU 核数,系统中被配置使用的 CPU 核数。这个值会受到系统 BIOS 中的设置影响,即使实际上系统不能使用这些 CPU 核。
  • _SC_NPROCESSORS_ONLN:获取当前在线的 CPU 核,实际上可以使用的 CPU 核数。这个值不会受到系统 BIOS 中的设置影响,只受到系统的实际配置和运行状态影响。

 

2.线程pthread_t

2.1 创建线程

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 
参数描述
thread指向线程标识符指针。
attr一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine线程运行函数起始地址,一旦线程被创建就会执行。
arg运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。 

2.2 结束线程

#include <pthread.h>
pthread_exit (status) 

在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

2.3 线程阻塞

pthread_join (threadid, status)
描述:调用pthread_join即等待参数中的线程结束后在执行其他线程
参数 :threadid即线程ID,标识唯一线程。
返回值 : 0代表成功。 失败,返回的则是错误号。

3.线程屏障pthread_barrier_t

线程屏障是一种用于线程同步的机制,在多个线程之间共享数据时可以使用。

  • 屏障是用户协调多个线程并行工作的同步机制
  • 工作原理:屏障允许每个线程等待,直到所有的合作线程都到达某一点(屏障),然后从该点继续执行工作

3.1 屏障的初始化

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,const pthread_barrierattr_t *restrict attr,unsigned int count);

// 返回值:成功返回0,否则返回错误编号

功能:对屏障变量进行初始化
参数1:初始化的屏障变量
参数2:屏障初始化时的属性。如果用默认属性,此处填NULL,屏障属性见文章:

(22条消息) APUE编程:65---线程处理(屏障属性:pthread_barrierattr_t)_董哥的黑板报的博客-CSDN博客
参数3:用此参数指定,在允许所有线程继续执行之前,必须到达屏障的线程数目。当到达了这个数目之后就可以继续执行

3.2 屏障的释放

int pthread_barrier_destroy(pthread_barrier_t *barrier);
 // 返回值:成功返回0,否则返回错误编号

功能:屏障变量的反初始化,释放销毁
参数:屏障变量
如果在pthread_barrier_destroy之前就释放了读写锁占用的内存空间,那么分配给这个锁的资源就会丢失
备注(重点):此函数只是反初始化屏障变量,并没有释放内存空间,如果屏障变量是通过malloc等函数申请的,那么需要在free掉读屏障变量之前调用pthread_barrier_destroy函数

3.3 屏障下的等待

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
 
// 返回值:成功返回0或者PTHREAD_BARRIER_SERIAL_THREAD;否则返回错误编号

功能:线程调用该函数用来表示自己已经到达了屏障
工作原理:如果线程调用这个函数发现屏障的线程计数还未满足要求,那么线程就会进入休眠状态。如果线程调用此函数之后,发现刚好满足屏障计数,那么所有的线程都被唤醒
返回值的注意事项:如果一个线程调用该函数返回PTHREAD_BARRIER_SERIAL_THREAD,那么其他在屏障中的线程就返回0.这使得一个线程可以作为主线程,它可以工作在其他所有线程已经完成的工作结果上

3.4 实例

    // Create array of thread objects we will launch
    pthread_t threads[num_threads];

    // Create a barrier and initialize it
    pthread_barrier_t barrier;
    pthread_barrier_init(&barrier, NULL, num_threads);

 

4.互斥变量和条件变量

4.1 互斥变量 pthread_mutex_t

概念:可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据
互斥量的使用:

  • 互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量
  • 对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞,直到当前线程释放该互斥锁
  • 如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,只能再次等待它重新变为可用(在这种方式下,每次只有一个线程可以向前执行)

互斥量的初始化与释放:

①静态初始化
直接把pthread_mutex_t互斥变量设置为常量PTHREAD_MUTEX_INITIALIZER
静态初始化互斥变量只能拥有默认的互斥量属性,不能设置其他互斥量属性,互斥量属性见文章:(22条消息) APUE编程:59---线程处理(互斥量属性:pthread_mutexattr_t)_董哥的黑板报的博客-CSDN博客_pthread_mutexattr_t
例如:

pthread_mutex_t  mutex;
mutex=PTHREAD_MUTEX_INITIALIZER;
 
//或者
pthread_mutex_t *mutex=(pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
*mutex=PTHREAD_MUTEX_INITIALIZER;

②动态初始化

  • 静态初始化互斥变量只能拥有默认互斥量属性,我们可以通过pthread_mutex_init函数来动态初始化互斥量,并且可以在初始化时选择设置互斥量的属性
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
 
// 返回值:成功返回0,否则返回错误编号

pthread_mutex_init:

  • 功能:对互斥量进行初始化
  • 参数:
    • 参数1: 需要初始化互斥量
    • 参数2:初始化时互斥量的属性。如果使用默认属性,此处填NULL


pthread_mutex_destroy:

  • 功能:互斥量的反初始化
  • 参数:互斥量
  • 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数

实例:

pthread_mutex_t* mutex=(pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex,NULL);
 
/*do something*/
 
pthread_mutex_destroy(mutex);
free(mutex);
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);
 
/*do something*/
 
pthread_mutex_destroy(&mutex);

互斥量的加锁与解锁:

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
 
// 返回值:成功返回0,否则返回错误编号
  • pthread_mutex_lock:对互斥量进行加锁。如果互斥量已经上锁,调用线程将阻塞到互斥量被解锁
  • pthread_mutex_unlock:对互斥量进行解锁
  • pthead_mutex_trylock:对互斥量进行尝试加锁(非阻塞)。如果互斥量处于未加锁状态,那么pthead_mutex_trylock就会锁住这个互斥量;如果此锁处于加锁状态,那么pthead_mutex_trylock就出错返回EBUSY,并且不会阻塞

例如去食堂吃饭:如果食堂人太多(加锁),pthread_mutex_lock就会排队等待(阻塞);pthead_mutex_trylock是去食堂尝试(try)一下,如果人多就不排队(不阻塞)直接退出

4.2 条件变量 pthread_cond_t

概念:

  • 条件变量是线程可用的另一种同步机制
  • 条件变量给多个线程提供了一个会合的场所
  • 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生
  • 条件变量是线程中的东西,就是等待某一条件的发生,和信号一样

条件变量的使用:

  • 条件变量要与互斥量一起使用,条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量
  • 其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件

条件变量的初始化与释放:

 ①静态初始化
直接把pthread_cond_t定义的条件变量设置为常量PTHREAD_COND_INITIALIZER
静态初始化条件变量只能拥有默认的条件变量属性,不能设置其他条件变量属性,条件变量属性见文章:(22条消息) APUE编程:63---线程处理(条件变量属性:pthread_condattr_t)_董哥的黑板报的博客-CSDN博客
例如:

pthread_cond_t cond;
cond=PTHREAD_COND_INITIALIZER;
 
//或者
pthread_cond_t *cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t));
*cond=PTHREAD_COND_INITIALIZER;

②动态初始化

  • 静态初始化条件变量只能拥有默认条件变量属性,我们可以通过pthread_mutex_init函数来动态初始化条件变量,并且可以在初始化时选择设置条件变量的属性
#include <pthread.h>
int pthread_cond_init(pthread_cond_t* restrict cond,const pthread_condattr_t* restrict attr);
int pthread_cond_destroy(pthread_cond_t* cond);
 
//返回值:成功返回0;失败返回错误编号

pthread_cond_init:

  • 功能:对条件变量初始化
  • 参数:
    • 参数1: 需要初始化的条件变量
    • 参数2:初始化时条件变量的属性。如果使用默认属性,此处填NULL

pthread_cond_destroy:

  • 功能:对条件变量反初始化(在条件变量释放内存之前)
  • 参数:条件变量
  • 备注(重点):此函数只是反初始化互斥量,并没有释放内存空间,如果互斥量是通过malloc等函数申请的,那么需要在free掉互斥量之前调用pthread_mutex_destroy函数

实例:

pthread_cond_t cond;
pthread_cond_init(&cond,NULL);
 
/*do something*/
 
pthread_cond_destroy(&cond);
pthread_cond_t * cond=(pthread_cond_t *)malloc(sizeof(pthread_cond_t));
pthread_cond_init(cond,NULL);
 
/*do something*/
 
pthread_cond_destroy(cond);
free(cond);

等待条件变量函数:

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t* restrict cond,pthread_mutex_t* restrict mutex);
int pthread_cond_timedwait(pthread_cond_t* cond,pthread_mutex_t* restrict mutex,const struct timespec* restrict tsptr);
 
//返回值:成功返回0;失败返回错误编号

这两个函数调用成功返回时,线程需要重新计算条件,因为另一个线程可能已经在运行并改变了条件

pthread_cond_wait:

  • 注意:等待条件变量变为真
  • 如何使用:参数mutex互斥量提前锁定,然后该互斥量对条件进行保护,等待参数1cond条件变量变为真。在等待条件变量变为真的过程中,此函数一直处于阻塞状态。但是处于阻塞状态的时候,mutex互斥量被解锁(因为其他线程需要使用到这个锁来使条件变量变为真)
  • 当pthread_cond_wait函数返回时,互斥量再次被锁住

pthread_cond_timedwait:

  • pthread_cond_timedwait函数与pthread_cond_wait函数功能相同。不过多了一个超时参数。超时值指定了我们愿意等待多长时间,它是通过timespec结构体表示的
  • 如果超时到期之后,条件还是没有出现,此函数将重新获取互斥量,然后返回错误ETIMEOUT
  • 注意:这个时间值是一个绝对数而不是相对数。例如,假设愿意等待3分钟,那么不是把3分钟转换为timespec结构体,而是需要把当前时间加上3分钟再转换成timespec结构,可以查看互斥量的演示案例:APUE编程:57---线程处理(互斥量:pthread_mutex_t)_董哥的黑板报的博客-CSDN博客_pthread_mutex_t 变量

条件变量信号发送函数:

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
 
//返回值:成功返回0;失败返回错误编号
  • 这两个函数用于通知线程条件变量已经满足条件(变为真)。在调用这两个函数时,是在给线程或者条件发信号
  • 必须注意:一定要在改变条件状态以后再给线程发信号
  • pthread_cond_signal函数:至少能唤醒一个等待该条件的线程
  • pthread_cond_broad函数:则唤醒等待该条件的所有线程

5.CPU亲和力掩码和CPU亲和力

5.1 CPU亲和力掩码

cpu_set_t类型是一个定义了一个 CPU 集合的类型,它可以用来指定哪些 CPU 上的进程或线程可以被调度。

//初始化,设为空
void CPU_ZERO (cpu_set_t *set); 
//将某个cpu加入cpu集中 
void CPU_SET (int cpu, cpu_set_t *set); 
//将某个cpu从cpu集中移出 
void CPU_CLR (int cpu, cpu_set_t *set); 
//判断某个cpu是否已在cpu集中设置了 
int CPU_ISSET (int cpu, const cpu_set_t *set); 

5.2 CPU亲和力

CPU亲合力就是指在Linux系统中能够将一个或多个进程绑定到一个或多个处理器上运行。一个进程的CPU亲合力掩码(一个cpu_set_t结构体)决定了该进程将在哪个或哪几个CPU上运行。在一个多处理器系统中,设置CPU亲合力的掩码可能会获得更好的性能。

下面两个函数就是用来设置获取线程CPU亲和力状态: 
·sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask) 

  • 该函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上。
  • 如果pid的值为0,则表示指定的是当前进程,使当前进程运行在mask所设定的那些CPU上。
  • 第二个参数cpusetsize是mask所指定的数的长度,通常设定为sizeof(cpu_set_t)。如果当前pid所指定的进程此时没有运行在mask所指定的任意一个CPU上,则该指定的进程会从其它CPU上迁移到mask的指定的一个CPU上运行。

·sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask) 
        该函数获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中。即获得指定pid当前可以运行在哪些CPU上。同样,如果pid的值为0,也表示的是当前进程。

在多CPU系统中,通过sched_setaffinity()可以设置进程的CPU亲和力,使进程绑定在某一个或几个CPU上运行,避免在CPU之间来回切换,从而提高该进程的实时性能。

实例:

 cpu_set_t mask;
    CPU_ZERO(&mask);
    // Unpack the arguments
    pthread_barrier_t *barrier = local_args->barrier;
// 使用 pthread_mutex_lock 和 pthread_mutex_unlock 函数加锁和解锁互斥量,防止多个线程同时修改 core_counter 变量
    pthread_mutex_lock(mtx);
//    将当前线程绑定到一个特定的 CPU 上
    CPU_SET(core_counter, &mask);
    int a = sched_setaffinity(0, sizeof(mask), &mask);
    std::cout <<"counter is: " << core_counter << " and affinity is: " << a << std::endl;
    core_counter++;

    pthread_mutex_unlock(mtx);

参考链接:

C++ 多线程 | 菜鸟教程 (runoob.com)

(22条消息) APUE编程:64---线程处理(屏障:pthread_barrier_t)_董哥的黑板报的博客-CSDN博客_pthread_barrier_t

(22条消息) APUE编程:57---线程处理(互斥量:pthread_mutex_t)_董哥的黑板报的博客-CSDN博客_pthread_mutex_t 变量 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值