• 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. 结束进程:可能来不及保存工作结果

    线程的优先级

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

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

    •  

  • 相关阅读:
    使用C#抓取网页内容并分析获取数据
    C#获取网页内容的三种方式
    jquery各种版本判断checkbox checked
    C#接口作为返回值
    Sql语句用string.Format格式化
    c# empty和null区别
    not is null 和 is not null
    每一个班级中每一门课程获得最高分的学生的学号
    hdu 2647
    hdu 3342
  • 原文地址:https://www.cnblogs.com/ltyandy/p/10938171.html
Copyright © 2020-2023  润新知