C++语法 进程相关函数
C++ 函数waitpid
waitpid的使用
waitpid用于等待特定的进程结束之后,主进程再继续执行,这时主进程会进入阻塞状态。在fork之后,如果子进程不使用waitpid,则可能时主进程先结束,也可能时子进程先结束。
(阻塞和非阻塞
阻塞: 简单来讲就是一直等待. 阻塞式等待, 为了完成某个功能, 选择一直等待
非阻塞: 简单来讲就是立即返回, 非阻塞式等待, 若果某个功能未完成, 选择立即返回)
waitpid的使用
waitpid用于等待特定的进程结束之后,主进程再继续执行,这时主进程会进入阻塞状态。在fork之后,如果子进程不使用waitpid,则可能时主进程先结束,也可能时子进程先结束
什么是进程等待
简单来说, 进程等待是进程的一种状态, 是父进程等待子进程退出时的一个过程
为什么要进程等待
简单来讲, 因为要避免产生僵尸进程.
进程在退出的时候, 会关闭所有的文件描述符, 释放在用户空间中分配的内存, 但是 PCB 仍会暂时保留, 里面还存放着进程的退出状态以及统计信息等.
当一个进程创建一个子进程时, 父进程往往需要读取子进程的运行结果, 如果子进程先于父进程退出, 父进程则无法获取子进程的退出状态, 子进程的一些残留信息将一直存在(也就是变成所谓的僵尸态), 久而久之, 便会造成内存泄漏.
如何通过进程等待解决上述问题
通过两个系统调用接口来实现进程等待:
pid_t waitpid(pid_t pid, int *status, int options);
// pid:
// pid = -1: 等待任意一个子进程
// pid > 0: 等待子进程 ID 为 pid 的子进程
// status: 进程状态, 若不关注可以置为 NULL
//options: 选项
//阻塞式等待: options 为 0
//非阻塞式等待: options 为 WNOHANG
因为当一个进程正常或者异常终止时, 内核会向父进程发送一个 SIGCHLD 信号. 父进程可以选择忽略该信号, 或者提供一个该信号发生时调用处理的函数. 对于这种信号, 系统默认是忽略它的, 所以父进程如果想要处理掉子进程就要主动调用系统提供的处理接口 — wait 和 waitpid
阻塞和非阻塞
阻塞: 简单来讲就是一直等待. 阻塞式等待, 为了完成某个功能, 选择一直等待
非阻塞: 简单来讲就是立即返回, 非阻塞式等待, 若果某个功能未完成, 选择立即返回
通过 waitpid 来实现进程等待
阻塞式等待
C++ 函数 pthread_setaffinity_np
QNX Neutrino通过runmask支持硬处理器亲和性的概念。 在runmask中设置的每个位表示线程可以运行的处理器。 默认情况下,线程的runmask设置为all,允许它在任何处理器上运行。 值0x01将允许线程仅在第一个处理器上执行。
默认情况下,进程或线程的子进程不继承runmask; 有一个单独的继承掩码。
通过仔细使用这些掩模,系统设计者可以进一步优化系统的运行时性能(例如,通过将非实时过程降级到特定处理器)。但是,一般情况下,这不是必需的,因为当优先级较高的线程准备就绪时,我们的实时调度程序将始终抢占优先级较低的线程。 处理器锁定可能只会影响缓存的效率,因为可以防止线程迁移。
您可以通过以下方式为新线程或进程指定runmask:
调用posix_spawnattr_setrunmask(),使用posix_spawnattr_setxflags()来设置
POSIX_SPAWN_EXPLICIT_CPU扩展标志,然后调用posix_spawn()
实现方法
进程与指定cpu绑定:SetProcessAffinityMask(GetCurrentProcess(), dwMask);
线程与指定cpu绑定:SetThreadAffinityMask(GetCurrentThread(),dwMask);
使用sched_setaffinity系统调用
sched_setaffinity可以将某个进程绑定到一个特定的CPU。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
/* 设置进程号为pid的进程运行在mask所设定的CPU上
-
第二个参数cpusetsize是mask所指定的数的长度
-
通常设定为sizeof(cpu_set_t)
-
如果pid的值为0,则表示指定的是当前进程
*/
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t mask);/ 获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中 */
绑定线程到cpu核上运行
绑定线程到cpu核上使用pthread_setaffinity_np函数,其原型定义如下:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <pthread.h>
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);
Compile and link with -pthread.
各参数的意义与sched_setaffinity相似。
C++ 函数锁
linux下为了多线程同步,通常用到锁的概念。
posix下抽象了一个锁类型的结构:ptread_mutex_t。通过对该结构的操作,来判断资源是否可以访问。顾名思义,加锁(lock)后,别人就无法打开,只有当锁没有关闭(unlock)的时候才能访问资源。
即对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。
要更改缺省的互斥锁属性,可以对属性对象进行声明和初始化。通常,互斥锁属性会设置在应用程序开头的某个位置,以便可以快速查找和轻松修改。
l 头文件:
#include <pthread.h>
l 函数原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
l 函数作用:
该函数用于C函数的多线程编程中,互斥锁的初始化。
pthread_mutex_init() 函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。如果参数attr为空(NULL),则使用默认的互斥锁属性,默认属性为快速互斥锁 。互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
pthread_mutexattr_init() 函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
函数成功执行后,互斥锁被初始化为未锁住态。
l 互斥锁pthread_mutex_t的使用:
- 互斥锁创建和销毁
有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
2.互斥锁属性
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
3.其他锁操作
锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
4.死锁
死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。
总体来讲, 有几个不成文的基本原则:
对共享资源操作前一定要获得锁。
完成操作以后一定要释放锁。
尽量短时间地占用锁。
如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
线程错误返回时应该释放它所获得的锁。