Windows _beginthreadex 线程类与线程池

一.线程类

》》函数

1.创建线程

void startThread(bool bSuspend = false) 

2.停止线程

virtual void endThread() = 0 

3.挂起线程

void suspendThread() 

4.恢复线程

void resumeThread() 

5.获取线程句柄

inline HANDLE GetHandle(){return m_hThread;} 

6.线程的入口函数

static unsigned int __stdcall m_fnThread(void *ptr) 

6.执行某些操作

virtual void run() = 0

》》数据

HANDLE m_hThread;

bool m_bSuspend;

// MyThread.h
#pragma once

#include "CommonHead.h"

class MyThread
{
public:
    MyThread(void);
    virtual ~MyThread(void); // 作为基类,析构函数必须定义为虚函数

    void startThread(bool bSuspend = false); // 启动线程时是否为挂起状态
    virtual void endThread() = 0; // 纯虚函数
    
    void suspendThread(); // 挂起线程
    void resumeThread(); // 恢复线程

    inline HANDLE GetHandle() // 获取线程句柄
    {
        return m_hThread;
    }
protected:
    static unsigned int __stdcall m_fnThread(void *ptr); // 线程函数入口
    virtual DWORD run() = 0;
private:
    HANDLE m_hThread; // 线程句柄    
};
// MyThread.cpp
#include <assert.h>
#include <process.h> // _endthread() 需要用到
MyThread::MyThread(
void) :m_hThread(INVALID_HANDLE_VALUE) { } MyThread::~MyThread(void) { if( INVALID_HANDLE_VALUE != m_hThread ) { CloseHandle(m_hThread); m_hThread = INVALID_HANDLE_VALUE; } } // 线程函数入口 unsigned int __stdcall MyThread::m_fnThread(void *ptr) { MyThread * pThread = (MyThread *)ptr; DWORD ret = 0; if(pThread) { ret = pThread->run(); _endthread(); } return ret; } // 启动线程时是否为挂起状态 void MyThread::startThread(bool bSuspend /*= false*/) { assert( INVALID_HANDLE_VALUE == m_hThread); if( INVALID_HANDLE_VALUE == m_hThread ) { unsigned threadID = 0; m_hThread = (HANDLE)_beginthreadex(NULL, 0, m_fnThread, (void *)this, (bSuspend ? CREATE_SUSPENDED : 0), &threadID); } } // 挂起线程 void MyThread::suspendThread() { ::SuspendThread(m_hThread); } // 恢复线程 void MyThread::resumeThread() { ::ResumeThread(m_hThread); }

其中:

区分 CreateThrad 与 _beginthreadex 函数
HANDLE  WINAPI  CreateThread(
LPSECURITY_ATTRIBUTES  lpThreadAttributes, // 线程内核对象的安全属性,一般传入NULL表示使用默认设置。
SIZE_T  dwStackSize, // 线程栈空间大小。传入0表示使用默认大小(1MB)。
LPTHREAD_START_ROUTINE  lpStartAddress, // 新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
LPVOID  lpParameter, // 传给线程函数的参数。
DWORD  dwCreationFlags, // 线程启动时是挂起还是运行状态.0 表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行
LPDWORD  lpThreadId // 返回线程的ID号,传入NULL表示不需要返回该线程ID号。
);
配套线程等待函数
DWORD  WINAPI  WaitForSingleObject(
HANDLE  hHandle, // 要等待的内核对象
DWORD  dwMilliseconds // 最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入 INFINITE 表示无限等待。
);
返回值
在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED

 

unsigned long _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist, /* 这个就是传给线程函数的参数的指针 */
unsigned initflag,
unsigned *thrdaddr );

尽量使用_beginthreadex()来代替使用CreateThread()
_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。
事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。
因此,如果在代码中有使用标准C运行库中的函数时,尽量使用_beginthreadex()来代替CreateThread()。

 

二、线程池

需要上面的类

class MyThread;

头文件支持
#include <process.h>

》》函数

1.增加线程

void AddThread(MyThread *pthread, bool bSuspend)

2.批量开启线程

void BatchStartThread()

3.批量停止线程

void BatchStopThread()

4.释放所有线程

void Release()

5.设置自动释放

void SetAutoRelease(bool bVal)

 

》》变量

vector<MyThread *> m_threadList;

bool m_bAutoRelease;

// MyThreadPool.h
#pragma once

//#include "MyThread.h" // 头文件不要包含 .h,直接定义一个 class MyThread;在.cpp中包含

  #include <vector>
  using namespace std;

  class MyThread; // Add


class MyThreadPool { public: MyThreadPool(void); ~MyThreadPool(void); void AddThread(MyThread *thread, bool bSuspend = false); // 增加线程 void BatchStopThread(); // 批量停止线程 void BatchStartThread(); // 批量启动线程 void Release(); // 释放全部线程 void SetAutoRelease(bool bVal); // 是否自动释放线程 private: bool m_bAutoRelease; // 自动释放 vector<MyThread *> m_threadList; };
// MyThreadPool.cpp
#include "MyThreadPool.h"
#include "MyThread.h" // 在 .cpp 中添加头文件

MyThreadPool::MyThreadPool(void)
    :m_bAutoRelease(true)
{
}


MyThreadPool::~MyThreadPool(void)
{
    Release();
}

// 增加线程
void MyThreadPool::AddThread(MyThread *pthread, bool bSuspend /*= false*/)
{
    m_threadList.push_back(pthread);
    pthread->startThread(bSuspend);
}

// 批量停止线程
void MyThreadPool::BatchStopThread()
{
    MyThread * pthread = NULL;
    int nSize = m_threadList.size();
    for(int i=0; i<nSize; ++i)
    {
        pthread = m_threadList[i];
        pthread->endThread(); // 结束线程

        WaitForSingleObject(pthread->GetHandle(), INFINITE);
        
    }
}

// 批量启动线程
void MyThreadPool::BatchStartThread()
{
    MyThread * pthread = NULL;
    int nSize = m_threadList.size();
    for(int i=0; i<nSize; ++i)
    {
        pthread = m_threadList[i];
        pthread->resumeThread(); // 恢复线程
    }
}

// 释放全部线程
void MyThreadPool::Release()
{
    BatchStopThread();

    MyThread *pthread = NULL;
    int nSize = m_threadList.size();
    for(int i=0; i<nSize; ++i)
    {
        pthread = m_threadList[i];
        if(m_bAutoRelease)
            delete pthread;
    }

    m_threadList.clear();
}

// 是否自动释放线程
void MyThreadPool::SetAutoRelease(bool bVal)
{
    m_bAutoRelease = bVal;
}

 

转载于:https://www.cnblogs.com/sylar-liang/p/4272323.html

这只是最简单的使用,后续如果热度高的话,再录个视频教程详解吧,学到的好评给一个~ 前  言 · 之前刚接触鱼刺的时候发了个 【鱼刺线程池,自动换IP,队列重试框架(https://bbs.125.la/forum.php?mod=viewthreadtid=14178530 )】 发现热度还不错,大家还是挺喜欢多线程的操作。 常言道:授人以鱼不如授人以渔,鱼刺类模块一直感觉确实稳定好用,对于新手来说一些命令还是比较难理解的。但不知道为什么一直没有详细教程。 今天趁这次开源大赛曾个热度 讲一下鱼刺多线程应用中 线程池Ex的使用方法,废话不多说,直接开始吧。 注: 所讲内容都是自己使用中所得出来的经验,如果讲的不好或者不对得地方请大佬指导哈。 已经请示过作者了: @Bints 首先我们需要下载并载入 教程以 鱼刺类_多线程应用5.43为主:鱼刺类_多线程应用v5.4.zip (112.11 KB, 下载次数: 228) 我们先来看看“鱼刺类_线程池Ex”的命令: 几个参数先说一下: 1.初始栈大小 :可以理解为申请内存的空间的初始大小(个人是这么理解的)。必须是4KB的倍数且最小8KB(8192字节)所以最小为8*1024,不用担心太少,任何情况下Windows会根据需要动态延长堆栈的大小 2.是否在UI线程 :如果填写了真,那么在循环里面会加个"处理事件()"的命令来让消息循环 去队列处理窗口组件操作 防止执行的时候窗口卡死,(记得在哪里看到过线程中处理事件()是没有效果的。不太懂~~) 1. 置_初始栈大小()  设置初始栈的大小,也可以在创建()的第五个参数中设置。此命令可以在线程池工作中设置。 2. 置_空闲回收事件()  设置线程空闲回收的时间,也可以在创建()的第三个参数中设置,此命令可以在线程池工作中设置。 3. 置_最大线程数()  设置最大线程数量,也可以在创建()的第二个参数中设置,此命令可以在线程池工作中设置。 4. 创建() :顾名思义 创建线程池。 5. 投递任务() ,向线程池中投递一个可执行的函数子程序指针,和投递任务_int()基本一模一样,在内部自动转换成指针地址到整数(子程序指针) 6. 投递任务_int()  向线程池中投递一个可执行的函数指针地址 7. 等待任务动态()  :就是等待线程,到指定的等待时间才会往下执行,可以用 触发任务动态来取消等待。 8. 触发任务动态() .这个需要和等待任务动态一起用,也可以理解为 放弃等待中的任务 继续向下执行 9. 暂停()  暂停正在工作的线程,配合 事件_暂停() 使用效果最佳,后续会详解。 10. 事件_暂停()   需要配合暂停命令。如果系统发出了暂停命令返回假 如果正常工作返回真,如果正在销毁的话也会返回假。 11. 继续()  取消暂停。 12. 取_队列任务数()  获取队列中的正在执行的线程数量。 13. 取_空闲任务数()  获取队列中的空闲线程数量,严格控制线程数量可以用到此命令,后续会详解。 14. 取_是否空闲()  获取线程池状态是否彻底空闲,也就是说任务是否全部执行完毕,可以作为后续投递任务完任务后的判断。 15. 取_线程池容量()  获取线程池的可执行的最小线程数量 16. 取_最大线程容量()  获取线程池中可执行的最大线程数量 17. 取_执行线程数()  获取正在工作的线程数量 18. 取_状态()  获取线程正在工作的状态,返回值为常量中的: #线程池_未启动 #线程池_正在工作,#线程池_正在销毁,#线程池_正在创建 下面开始实战,将会用到所有线程池Ex中的命令 首先载入模块后在程序集变量中创建一个线程池Ex。 创建一个按钮。在按钮事件中写入:要执行的任务数量为1000 线程数量为50 如果已知 执行数量为1000了 直接计次循环 写下去可能执行不够准确,因为不排除会投递失败的情况。所以我们: 如下图:只有在投递任务成功的时候 计次才会递增。 但是每次循环都会判断 递增的计次是否小于任务数量, 如果小于就继续执行,如果大于就说明投递的任务数量已经达到了目标任务数,就可以跳出循环了 上图中:投递任务()命令 传递了两个参数 一个是局_计次 一个是 0, 投递 局_计次 可以在任务函数中获取到 用处也比较大,比如可以用作超级列表框的索引。(前提是已经插入了) 等待任务动态().为什么要等待呢,又是时候你投递的是内存指针,投递进去后等待 任务函数将它获取到并释放完毕后触发任务动态就好了 比如: 这样看着没什么问题 是吧~~ 内存方面的知识后续再说把 先掠过,只是这样演示这节只讲线程池Ex 但是如果我们模拟一下真是线程场景 加个延时() 如上图所示,如果有延时的话线程池投递完任务直接销毁 会导致任务被中断,或者放弃了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值