上一个章节讲了进程,把Windows 系统就像是一座庞大而繁忙的城市,进程则是城市里形形色色的居民。有的居民安静本分,按部就班地完成自己的工作;有的居民则活力充沛,时不时就想 “大闹一番”。在这座城市里,如果没有合理的管理,很容易陷入混乱。而 Windows 作业,就像是城市里的 “社区管理员”,它的首要任务就是对进程这个 “居民” 施加限制,维护城市的秩序。
1、对作业中的进程施加限制
在 Windows 系统中,作业就像一个 “虚拟社区”,被纳入作业的进程就是这个社区的 “住户”。为了让社区能够和谐稳定地运行,“管理员”(作业)需要对住户(进程)的行为做出各种限制。比如,限制进程能够使用的 CPU 时间,就好比规定一个家庭每天只能使用一定量的水电,避免过度消耗资源,影响其他 “住户”。再比如,限制进程能够访问的内存大小,这就如同限制每个家庭的居住面积,防止某个进程因为过度占用内存,导致系统内存紧张,运行缓慢。
我记得有一次,我们在开发一个大型的数据处理程序时,程序中包含了多个子进程。由于没有合理限制这些进程的资源使用,在数据量较大时,整个系统变得异常卡顿。后来,我们引入了 Windows 作业,为每个进程设定了 CPU 和内存的使用上限。就像给狂奔的野马套上了缰绳,程序运行一下子变得稳定有序,系统也恢复了往日的流畅。以下是使用 C++ 代码对作业中的进程施加限制的示例:
#include <windows.h>
#include <stdio.h>
int main() {
// 创建作业对象
HANDLE hJob = CreateJobObject(NULL, NULL);
if (hJob == NULL) {
printf("CreateJobObject failed. Error: %d\n", GetLastError());
return 1;
}
// 设置作业的基本限制信息
JOBOBJECT_BASIC_LIMIT_INFORMATION jbli = {0};
jbli.PerProcessUserTimeLimit.QuadPart = 100000000; // 限制每个进程的用户CPU时间为1秒(100000000 100ns单位)
jbli.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_TIME;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
jeli.BasicLimitInformation = jbli;
jeli.ProcessMemoryLimit = 1024 * 1024 * 100; // 限制进程内存为100MB
if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) {
printf("SetInformationJobObject failed. Error: %d\n", GetLastError());
CloseHandle(hJob);
return 1;
}
// 这里可以创建新进程并将其加入作业中,此处省略创建进程部分代码
// 关闭作业句柄
CloseHandle(hJob);
return 0;
}
这种对进程施加限制的能力,让 Windows 作业成为了程序稳定运行的坚实保障。
2、 将进程放入作业中
把进程放入作业,就像是将散落的居民组织进社区。在 Windows 系统的编程世界里,这一过程需要通过特定的 API 函数来实现。当我们创建一个作业对象时,就相当于建立了一个新的 “社区”。然后,通过一系列操作,将进程 “邀请” 进这个社区。
这让我想起刚做项目经理的时候,负责组建一个新的开发团队。团队成员就像是一个个进程,来自不同的地方,有着不同的工作习惯和能力。我需要将他们整合到一起,明确各自的职责和工作范围,就如同把进程放入作业中,让他们在一个统一的框架下协作。每一次成功地将进程纳入作业,都像是成功地说服一个成员加入团队,看着他们开始在新的环境中发挥作用,那种成就感油然而生。下面是将进程放入作业的 C++ 代码示例:
#include <windows.h>
#include <stdio.h>
int main() {
// 创建作业对象
HANDLE hJob = CreateJobObject(NULL, NULL);
if (hJob == NULL) {
printf("CreateJobObject failed. Error: %d\n", GetLastError());
return 1;
}
// 创建进程的启动信息和进程信息结构体
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
// 要创建的进程命令行,这里以记事本为例
TCHAR szCommandLine[] = TEXT("notepad.exe");
if (!CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
printf("CreateProcess failed. Error: %d\n", GetLastError());
CloseHandle(hJob);
return 1;
}
// 将进程加入作业中
if (!AssignProcessToJobObject(hJob, pi.hProcess)) {
printf("AssignProcessToJobObject failed. Error: %d\n", GetLastError());
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hJob);
return 1;
}
// 关闭进程和线程句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
// 关闭作业句柄
CloseHandle(hJob);
return 0;
}
3、 终止作业中的所有线程查询作业统计信息
当我们需要对作业进行管理时,终止作业中的所有线程和查询作业统计信息是两项重要的操作。终止作业中的所有线程,就好比社区管理员发现某个 “住户” 出现了严重问题,为了避免影响整个社区,果断采取措施让其停止活动。在程序运行过程中,如果某个进程出现了异常,可能会影响整个系统的稳定性,这时通过作业终止该进程的所有线程,能够迅速解决问题,保障系统安全。
而查询作业统计信息,则像是社区管理员定期对社区进行盘点。了解每个 “住户” 的资源使用情况,比如消耗了多少 CPU 时间、占用了多少内存等。这些统计信息对于优化程序性能、合理分配系统资源至关重要。就像我做部门经理时,需要定期了解每个项目组的工作进度和资源消耗情况,以便做出更合理的决策。通过查询作业统计信息,我们能够清晰地掌握程序运行的 “健康状况”,为后续的优化和改进提供依据。以下是终止作业中所有线程和查询作业统计信息的 C++ 代码示例:
#include <windows.h>
#include <stdio.h>
int main() {
// 创建作业对象
HANDLE hJob = CreateJobObject(NULL, NULL);
if (hJob == NULL) {
printf("CreateJobObject failed. Error: %d\n", GetLastError());
return 1;
}
// 创建进程并加入作业(此处省略创建进程代码,可参考5.2节)
// 终止作业中的所有进程
if (!TerminateJobObject(hJob, 0)) {
printf("TerminateJobObject failed. Error: %d\n", GetLastError());
}
// 查询作业统计信息
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jbai;
DWORD cb = sizeof(jbai);
if (QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation, &jbai, cb, &cb)) {
printf("Total CPU time used by processes in the job: %I64d 100ns units\n", jbai.TotalUserTime.QuadPart);
} else {
printf("QueryInformationJobObject failed. Error: %d\n", GetLastError());
}
// 关闭作业句柄
CloseHandle(hJob);
return 0;
}
4、作业通知
作业通知功能是 Windows 作业的一项贴心设计,它就像是社区里的 “智能报警器”。当作业中的进程出现特定事件时,比如进程结束、资源使用达到阈值等,作业会及时发出通知。这种通知机制让程序开发者能够实时掌握作业运行的动态,及时做出响应。
我在创业初期,开发了一款实时监控软件。其中就运用了作业通知功能,当被监控的进程出现异常情况时,软件能够第一时间收到作业发出的通知,并及时采取措施,如自动重启进程或向管理员发送警报。这就好比社区里安装了智能监控系统,一旦有异常情况发生,管理员能够立即知晓并处理。作业通知功能让程序变得更加智能和灵活,能够更好地适应复杂多变的运行环境。下面是使用作业通知的 C++ 代码示例,通过等待作业对象的信号来获取通知:
#include <windows.h>
#include <stdio.h>
int main() {
// 创建作业对象
HANDLE hJob = CreateJobObject(NULL, NULL);
if (hJob == NULL) {
printf("CreateJobObject failed. Error: %d\n", GetLastError());
return 1;
}
// 创建进程并加入作业(此处省略创建进程代码,可参考5.2节)
// 设置作业通知,当作业中的任何进程结束时发出通知
JOBOBJECT_NOTIFICATION_INFORMATION jni = {0};
jni.EventMask = JOB_OBJECT_NOTIFY_PROCESS_EXIT;
HANDLE hJobNotification = CreateIoCompletionPort((HANDLE)hJob, NULL, 0, 1);
if (hJobNotification == NULL) {
printf("CreateIoCompletionPort failed. Error: %d\n", GetLastError());
CloseHandle(hJob);
return 1;
}
// 等待作业通知
OVERLAPPED ol = {0};
if (!SetInformationJobObject(hJob, JobObjectNotificationInformation, &jni, sizeof(jni))) {
printf("SetInformationJobObject for notification failed. Error: %d\n", GetLastError());
CloseHandle(hJobNotification);
CloseHandle(hJob);
return 1;
}
printf("Waiting for job notification...\n");
DWORD dwBytes;
LPOVERLAPPED_COMPLETION_PORT lpCompletionPort;
if (GetQueuedCompletionStatus(hJobNotification, &dwBytes, (PULONG_PTR)&lpCompletionPort, &ol, INFINITE)) {
printf("Job notification received!\n");
} else {
printf("GetQueuedCompletionStatus failed. Error: %d\n", GetLastError());
}
// 关闭相关句柄
CloseHandle(hJobNotification);
CloseHandle(hJob);
return 0;
}
5、 job lab 示例程序
为了更好地理解 Windows 作业的实际应用,我们来看一个 job lab 示例程序。这个示例程序就像是一个 “社区生活样板间”,展示了 Windows 作业在实际场景中的各种功能和用法。
在这个 “样板间” 里,我们可以看到如何创建作业、将进程放入作业、对进程施加限制、查询作业统计信息以及利用作业通知功能。每一行代码都像是 “样板间” 里的一件家具,各司其职,共同构建起一个完整的作业管理体系。下面是一个整合了上述多种功能的 job lab 示例 C++ 代码:
#include <windows.h>
#include <stdio.h>
int main() {
// 创建作业对象
HANDLE hJob = CreateJobObject(NULL, NULL);
if (hJob == NULL) {
printf("CreateJobObject failed. Error: %d\n", GetLastError());
return 1;
}
// 设置作业的基本限制信息
JOBOBJECT_BASIC_LIMIT_INFORMATION jbli = {0};
jbli.PerProcessUserTimeLimit.QuadPart = 100000000; // 限制每个进程的用户CPU时间为1秒
jbli.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_TIME;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
jeli.BasicLimitInformation = jbli;
jeli.ProcessMemoryLimit = 1024 * 1024 * 100; // 限制进程内存为100MB
if (!SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) {
printf("SetInformationJobObject failed. Error: %d\n", GetLastError());
CloseHandle(hJob);
return 1;
}
// 创建进程并加入作业
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
TCHAR szCommandLine[] = TEXT("notepad.exe");
if (!CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
printf("CreateProcess failed. Error: %d\n", GetLastError());
CloseHandle(hJob);
return 1;
}
if (!AssignProcessToJobObject(hJob, pi.hProcess)) {
printf("AssignProcessToJobObject failed. Error: %d\n", GetLastError());
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hJob);
return 1;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
// 设置作业通知,当作业中的任何进程结束时发出通知
JOBOBJECT_NOTIFICATION_INFORMATION jni = {0};
jni.EventMask = JOB_OBJECT_NOTIFY_PROCESS_EXIT;
HANDLE hJobNotification = CreateIoCompletionPort((HANDLE)hJob, NULL, 0, 1);
if (hJobNotification == NULL) {
printf("CreateIoCompletionPort failed. Error: %d\n", GetLastError());
CloseHandle(hJob);
return 1;
}
if (!SetInformationJobObject(hJob, JobObjectNotificationInformation, &jni, sizeof(jni))) {
printf("SetInformationJobObject for notification failed. Error: %d\n", GetLastError());
CloseHandle(hJobNotification);
CloseHandle(hJob);
return 1;
}
// 等待作业通知
OVERLAPPED ol = {0};
printf("Waiting for job notification...\n");
DWORD dwBytes;
LPOVERLAPPED_COMPLETION_PORT lpCompletionPort;
if (GetQueuedCompletionStatus(hJobNotification, &dwBytes, (PULONG_PTR)&lpCompletionPort, &ol, INFINITE)) {
printf("Job notification received!\n");
} else {
printf("GetQueuedCompletionStatus failed. Error: %d\n", GetLastError());
}
// 查询作业统计信息
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jbai;
DWORD cb = sizeof(jbai);
if (QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation, &jbai, cb, &cb)) {
printf("Total CPU time used by processes in the job: %I64d 100ns units\n", jbai.TotalUserTime.QuadPart);
} else {
printf("QueryInformationJobObject failed. Error: %d\n", GetLastError());
}
// 终止作业中的所有进程
if (!TerminateJobObject(hJob, 0)) {
printf("TerminateJobObject failed. Error: %d\n", GetLastError());
}
// 关闭相关句柄
CloseHandle(hJobNotification);
CloseHandle(hJob);
return 0;
}
通过学习和分析这个示例程序,我们能够更直观地感受到 Windows 作业的强大功能,也能为自己的程序开发提供宝贵的经验和借鉴。
回顾我的 IT 职业生涯,从程序员到创业者,见证了无数技术的兴衰更替。而 Windows 作业,就像是一位默默守护程序世界秩序的老友,在每一个关键的时刻,都发挥着不可或缺的作用。它让我明白,在看似复杂的代码世界里,只要有合理的管理和规划,就能创造出稳定、高效的程序。未来,无论技术如何发展,这种对秩序和规则的追求,都将是我们程序员不断前行的动力源泉。