Windows提高_2.1第一部分:线程

第一部分:线程

什么是线程?

  • 线程其实可以理解为一段正在执行中的代码,它最少由一个线程内核对象和一个栈组成。

  • 线程之间是没有从属关系的,同一进程下的所有线程都可以访问进程内的所有内容。

  • 主线程其实是创建进程时创建的线程,主线程一旦退出,所有子线程也会退出。

创建一个线程

#include <stdio.h>
#include <process.h>
#include <windows.h>// 线程函数
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
    while (true)
    {
        printf("WorkerThread()
");
    }
}
​
int main()
{
    DWORD ThreadId = 0;
    
    // 如何创建一个线程
    HANDLE Thread = CreateThread(
        NULL,               // 安全属性
        0,                  // 设置栈的大小,使用默认
        WorkerThread,       // 表示的是线程的开始位置
        NULL,               // 线程函数的参数
        NULL,               // 创建标志
        &ThreadId);         // 创建出的线程的 Id
// 可以使用 process.h 提供的函数更加安全的创建和结束线程
    // _beginthreadex() + _endthreadex()
    
    while (true)
    {
        printf("main()
");
    }
​
    return 0;
}

代码 - 等待线程

#include <stdio.h>
#include <windows.h>// 线程函数
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
    // 获取传入的参数
    int count = (int)lpThreadParameter;
​
    // 循环次数 100
    for (int i = 0; i < count; ++i)
        printf("i = %d
", i);
​
    return 0;
}
​
int main()
{
    // 如何创建一个线程
    HANDLE Thread = CreateThread(
        NULL,               // 安全属性
        0,                  // 设置栈的大小,使用默认
        WorkerThread,       // 表示的是线程的开始位置
        (LPVOID)500,        // 线程函数的参数
        NULL,               // 创建标志
        NULL);              // 创建出的线程的 Id
// 线程内核对象的信号:
    // - 有信号: 当线程运行结束的时候,处于有信号状态
    // - 无信号: 当线程正在执行的时候,处于无信号状态
// 等待线程知道线程退出为止
    WaitForSingleObject(Thread, INFINITE);
​
    // 主线程一旦退出,子线程也会退出
return 0;
}

代码 - 遍历线程

#include <stdio.h>
#include <windows.h>
// 1. 包含头文件
#include <TlHelp32.h>int main()
{
    int Pid = 0;
    scanf_s("%d", &Pid);
​
    // 2. 拍摄当前所有线程的快照,参数 2 是无效的,传入任何值遍历的都是所有的线程
    HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
​
    // 3. 检查快照是否创建成功
    if (Snapshot == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, L"快照创建失败", L"标题", MB_OK);
        ExitThread(-1);
    }
​
    // 4. 创建结构体用于保存遍历到的信息
    THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) };
​
    // 5. 尝试遍历到第一个线程信息
    if (Thread32First(Snapshot, &ThreadInfo))
    {
        do {
            // [ 判断遍历到的线程所属进程 id 是否为想要遍历的进程 ]
            if (Pid == ThreadInfo.th32OwnerProcessID)
            {
                printf("tid: %d
", ThreadInfo.th32ThreadID);
            }
        } while (Thread32Next(Snapshot, &ThreadInfo));
    }
​
    return 0;
}

代码 - 挂起和恢复

#include <stdio.h>
#include <windows.h>
// 1. 包含头文件
#include <TlHelp32.h>int main()
{
    int Pid = 0;
    scanf_s("%d", &Pid);
​
    // 2. 拍摄当前所有线程的快照,参数 2 是无效的,传入任何值遍历的都是所有的线程
    HANDLE Snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
​
    // 3. 检查快照是否创建成功
    if (Snapshot == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL, L"快照创建失败", L"标题", MB_OK);
        ExitThread(-1);
    }
​
    // 4. 创建结构体用于保存遍历到的信息
    THREADENTRY32 ThreadInfo = { sizeof(THREADENTRY32) };
​
    // 5. 尝试遍历到第一个线程信息
    if (Thread32First(Snapshot, &ThreadInfo))
    {
        do {
            // [ 判断遍历到的线程所属进程 id 是否为想要遍历的进程 ]
            if (Pid == ThreadInfo.th32OwnerProcessID)
            {
                printf("tid: %d
", ThreadInfo.th32ThreadID);
​
                // 打开目标线程的句柄
                HANDLE Thread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadInfo.th32ThreadID);
​
                // 尝试进行挂起, 每调用一次就挂起一次
                // SuspendThread(Thread);
// 尝试进行恢复,每调用一次就恢复一次
                // ResumeThread(Thread);
// 当挂起计数为 0 的时候,线程就会被调度
// 用于结束标目线程
                // TerminateThread(Thread, 0);
            }
        } while (Thread32Next(Snapshot, &ThreadInfo));
    }
​
    return 0;
}

代码 - 伪句柄产生的问题

#include <stdio.h>
#include <windows.h>// 使用伪句柄作为参数传递可能会带来的问题
// 功能函数,通过传入的线程句柄,获取到线程的创建时间
VOID GetThreadCreateTime(HANDLE Thread)
{
    // 0. 创建用于保存线程相关时间的结构
    FILETIME CreateTime = { 0 }, ExitTime = { 0 };
    FILETIME KernelTime = { 0 }, UserTime = { 0 };
​
    // 1. 使用 GetThreadTimes 获取到传入的线程的相关时间
    GetThreadTimes(Thread, &CreateTime,
        &ExitTime, &KernelTime, &UserTime);
​
    // 2. 将时间转换为本地时间
    FILETIME LocalCreateTime = { 0 };
    FileTimeToLocalFileTime(&CreateTime, &LocalCreateTime);
​
    // 3. 将时间戳转换为系统时间
    SYSTEMTIME SystemTime = { 0 };
    FileTimeToSystemTime(&LocalCreateTime, &SystemTime);
​
    // 4. 输出时间
    printf("CreateTime: %d 时 %d 分 %d 秒
", SystemTime.wHour, 
        SystemTime.wMinute, SystemTime.wSecond);
}
​
// 线程函数
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
    // 接收传入到线程内的伪句柄
    HANDLE Thread = (HANDLE)lpThreadParameter;
​
    // 根据[伪句柄]输出线程的创建时间
    // [输出的实际上是自己的创建时间]
    GetThreadCreateTime(Thread);
​
    return 0;
}
​
int main()
{
    // 获取当前线程的[伪]句柄
    HANDLE Thread = GetCurrentThread();
​
    // 1. 查看当前[主]线程的创建时间
    GetThreadCreateTime(Thread);
​
    // 2. 等待 2 秒钟,不能保证
    Sleep(2000);
​
    // 3. 创建一个新的线程,将伪句柄传入
    HANDLE Handle = CreateThread(NULL, 0, WorkerThread, 
        (LPVOID)Thread, NULL, NULL);
​
    // 4. 等待线程执行完毕
    WaitForSingleObject(Handle, INFINITE);
​
    return 0;
}

总结:由于传入的句柄是一个伪句柄,始终指向当前的线程内核对象,所以导致在工作线程内计算出的时间不是主线程的运行时间。线程伪句柄的值始终为【-2】,进程伪句柄的值始终为【-1】

代码 - 真实句柄的获取

// 将伪句柄转换成真实的句柄
    DuplicateHandle(
        GetCurrentProcess(),    // 从哪里拷贝
        GetCurrentThread(),     // 要拷贝什么
        GetCurrentProcess(),    // 拷贝到哪里去
        &Thread,                // 保存拷贝到的句柄
        0,                      // 安全访问级别
        false,                  // 是否可以被子进程继承
        DUPLICATE_SAME_ACCESS); // 转换选项

线程的退出方式

  1. 主线程函数(mainWinMain)返回,最为友好,会调用析构函数、会清理栈

  2. 使用ExitThread:不会调用析构函数

  3. 使用TerminateThread:不会调用析构函数,不会清理栈

  4. 结束进程:可能来不及保存工作结果

线程的优先级

  • 线程只有相对于进程的优先级,随着进程优先级的改变,线程的相对优先级并不会改变

  • 通常情况下手动修改优先级并不会对程序的执行产生变化

  •  

原文地址:https://www.cnblogs.com/ltyandy/p/10938171.html