zoukankan      html  css  js  c++  java
  • 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. 结束进程:可能来不及保存工作结果

    线程的优先级

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

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

    •  

  • 相关阅读:
    java操作生成jar包 和写入jar包
    jboss配置jndi连接池
    windows 域的LDAP查询相关举例
    LDAP error Code 及解决方法
    HDU 6417
    CF1299D Around the World
    codechef Chef and The Colored Grid
    Educational Codeforces Round 82 (Rated for Div. 2)
    CF1237F Balanced Domino Placements
    CF1254E Send Tree to Charlie
  • 原文地址:https://www.cnblogs.com/ltyandy/p/10938171.html
Copyright © 2011-2022 走看看