(P102)多线程三:_beginthreadex,CreateThread与_beginthreadex区别,线程类封装

时间:2021-02-14 00:50:22


文章目录

  • ​​1._beginthreadex​​
  • ​​2.CreateThread与_beginthreadex区别​​
  • ​​3.线程类封装​​

1._beginthreadex

包含头文件process.h
函数原型
unsigned long _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( __stdcall *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);

2.CreateThread与_beginthreadex区别

  • 下面是一些一般性的规则。如果主线程以外的任何线程进行以下操作,你就应该使用多线程版的 C runtime library ,并使用_beginthreadex() 和 _endthreadex():
    (1)在C 程序中使用 malloc() 和 free() ,或是在 C++ 程序中使用 new 和 delete 。
    (2)调用 stdio.h 或 io.h 中声明的任何函数,包括像 fopen() 、open() 、getchar() 、write() 、printf() 等等。所有这些函数都用到共享的数据结构以及errno。你可以使用 wsprintf() 将字符串格式化,如此就不需要 stdio.h 了。如果链接器抱怨说它找不到 wsprintf() ,你得链接 USER32.LIB 。
    (3)使用浮点变量或浮点运算函数。
    (4)调用任何一个使用了静态缓冲区的 runtim e 函数,如asctime() 、strtok() 或 rand() 。
  • 一个程序如果使用多个线程,而不在任何 worker 线程中调用runtim e library ,能够与单线程版的 runtime library 链接并以 CreateThread() 取代 _beginthreadex() 。
  • F1查看帮助
  • (P102)多线程三:_beginthreadex,CreateThread与_beginthreadex区别,线程类封装

  • 为啥_beginthreadex()比CreateThread()好呢?
    (1)_beginthreadex是多线程版的C runtime library中的一个函数;
    _beginthreadex包裹了CreateThread;
    C runtime library是分为单线程版本的runtime library和多线程版本的runtime library
    (2)malloc free fopen strtok等等这些C runtime library函数都不是线程安全的。因为这些函数内部都使用全局变量或者全局的静态变量。
    (3)所以_beginthreadex是线程安全的。CreateThread()是单线程版本的,依赖于单线程runtime library,但是多线程runtime library也可以用它,_beginthreadex()是多线程版本的,依赖于多线程runtime library。
  • windows是如何处理的呢?
    (1)win提供了多线程版的C runtime library,win也提供了单线程版本的C runtime library,分开提供是为了不影响效率
    (2)win提供了多线程如何解决malloc等不是线程安全的函数?
    Linux是TSD.多个线程中的每个线程都提供了一份相同的但又彼此独立的数据。
    win是TLS 线程局部存储,要求在创建线程时,需要分配TLS。
  • vs2008的debug默认使用多线程版C runtime library的多线程调试库DLL。
  • (P102)多线程三:_beginthreadex,CreateThread与_beginthreadex区别,线程类封装

  • 所以在vs2008中尽量使用_beginthreadex()而不是CreateThread()。

3.线程类封装

  • eg:
    02.cpp
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;

//__stdcall unsigned ThreadProc(void* lpParameter)也行
unsigned __stdcall ThreadProc(void* lpParameter)
{
cout<<"GetCurrentThreadId()="<<GetCurrentThreadId()<<endl;
int n = (int)lpParameter;
while (n--)
{
cout<<"this is a test"<<endl;
Sleep(1000);
}
//ExitThread();不要使用ExitThread(),这里要么使用return,要么使用_endthreadex(),要配套使用
return 100;
}

int main()
{

unsigned threadId;
//_beginthreadex(安全属性,堆栈大小,线程函数的入口地址,传递给线程函数的参数,线程选项,返回的线程ID)
//0表示线程没有挂起
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, (void*)5, 0, &threadId);
if (hThread == NULL)
{
cout<<"error with code"<<GetLastError()<<endl;
exit(1);
}

cout<<"threadId="<<threadId<<endl;

DWORD ret;
ret = WaitForSingleObject(hThread, INFINITE);
if (ret == WAIT_FAILED)
{
cout<<"error with code="<<GetLastError()<<endl;
exit(1);
}
else if (ret == WAIT_OBJECT_0)
{
cout<<"wait succ"<<endl;
}

//Sleep(10*1000);

CloseHandle(hThread);
return 0;
}
  • eg:
    03.cpp
#include "JThread.h"
#include <iostream>
using namespace std;

//用类的方式实现
class TestThread : public JThread
{
public:
TestThread(int n) : n_(n)
{

}
~TestThread()
{

}

//在Run中实现线程的业务逻辑
void Run()
{
while (n_--)
{
cout<<"this is a test"<<endl;
Sleep(1000);
}
}

private:
int n_;
};

int main()
{
TestThread t(5);
t.Start();
t.Wait();//防止主线程比子线程先结束,因为主线程结束了,那么子线程也就结束了,可能子线程还没运行呢
return 0;
}

JThread.h

#ifndef _JTHREAD_H_
#define _JTHREAD_H_

#include <Windows.h>
//JThread是抽象类
class JThread
{
public:
JThread();
virtual ~JThread();

virtual void Run() = 0;//每个对象都执行一个任务,
//纯虚函数:任何一个继承JThread类,都应该去实现Run函数

/*
ThreadFun不能做成是虚函数,因为他是全局函数,当调用_beginthreadex,OS会回调ThreadFun来开辟一个线程
而虚函数的第一个参数是this指针(指向调用该函数的对象),而回调函数ThreadFun第一个参数就不是this指针,所以不行。
*/
static unsigned __stdcall ThreadFun(void* p);//static函数无this指针
bool Start();//启动线程

void Wait(DWORD timeout=INFINITE);

private:
HANDLE hThread_;
unsigned threadId_;
};

#endif // _JTHREAD_H_

JThread.cpp

#include "JThread.h"
#include <process.h>

JThread::JThread() : hThread_(NULL), threadId_(0)
{
}

JThread::~JThread()
{
if (hThread_ != NULL)
CloseHandle(hThread_);
}

unsigned __stdcall JThread::ThreadFun(void* p)
{
//获取到传递的this指针,访问到JThread成员中的变量
JThread* jtp = (JThread*)p;//t.Start();后,基类指针jtp指向派生类对象t
jtp->Run();//未来是派生类对象调用Start函数,所以这里是基类指针指向派生类对象
//其调用虚函数,调用的是派生类的虚函数
return 0;
}

bool JThread::Start()
{
//传递this指针
hThread_ = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, this, 0, &threadId_);
return hThread_ != NULL;
}

void JThread::Wait(DWORD timeout)
{
WaitForSingleObject(hThread_, timeout);
}
  • 测试:
  • (P102)多线程三:_beginthreadex,CreateThread与_beginthreadex区别,线程类封装

  • eg:
    TcpSrv\main.cpp
#pragma comment(lib, "ws2_32.lib")
#include <WinSock2.h>
#include <process.h>
#include "JThread.h"
#include <iostream>
using namespace std;

class ServiceThread : public JThread
{
public:
ServiceThread(SOCKET conn) : conn_(conn)
{
cout<<"ServiceThread ..."<<endl;
}
~ServiceThread()
{
cout<<"~ServiceThread ..."<<endl;
}
void Run()
{
char buf[1024] = {0};
while (1)
{
int ret = recv(conn_, buf, sizeof(buf), 0);
if (ret == SOCKET_ERROR)
{
cout<<"error with code = "<<WSAGetLastError()<<endl;
exit(1);
}
if (ret == 0)
{
cout<<"client close"<<endl;
break;
}
if (ret > 0)
{
cout<<buf<<endl;
send(conn_, buf, strlen(buf), 0);
}
memset(buf, 0, sizeof buf);
}
closesocket(conn_);
}

private:
SOCKET conn_;
};
//
//unsigned __stdcall do_service(void* p)
//{
// int conn = (SOCKET)p;
// char buf[1024] = {0};
// while (1)
// {
// int ret = recv(conn, buf, sizeof(buf), 0);
// if (ret == SOCKET_ERROR)
// {
// cout<<"error with code = "<<WSAGetLastError()<<endl;
// exit(1);
// }
// if (ret == 0)//说明对等方关闭了
// {
// cout<<"client close"<<endl;
// break;
// }
// if (ret > 0)
// {
// cout<<buf<<endl;
// send(conn, buf, strlen(buf), 0);
// }
// memset(buf, 0, sizeof buf);
// }
// closesocket(conn);
//
// return 0;
//}

int main(void)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 2, 2 );//表示要请求的版本

//来自F1的eg start
err = WSAStartup( wVersionRequested, &wsaData );//启动它
if ( err != 0 ) {
return 1;
}

//判断请求的是不是2.2版本
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return 1;
}

SOCKET listenfd;
listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenfd == INVALID_SOCKET)
{
cout<<"error with code = "<<WSAGetLastError()<<endl;
exit(1);
}
//来自F1的eg end

//准备一个服务器地址
sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
//servaddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//Windows写法
//Linux写法如下
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//S_un.S_addr等于s_addr,用宏做的
servaddr.sin_port = htons(8888);

int ret;
int opt = 1;
ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
if (ret == SOCKET_ERROR)
{
cout<<"error with code = "<<WSAGetLastError()<<endl;
exit(1);
}


//绑定他
//C语言不能省略struct,C++可以
//C语言写法:ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
ret = bind(listenfd, (sockaddr*)&servaddr, sizeof(servaddr));
//SOCKET_ERROR定义是-1
if (ret == SOCKET_ERROR)
{
cout<<"error with code = "<<WSAGetLastError()<<endl;
exit(1);
}

ret = listen(listenfd, SOMAXCONN);//SOMAXCONN监听队列最大值
if (ret == SOCKET_ERROR)
{
cout<<"error with code = "<<WSAGetLastError()<<endl;
exit(1);
}


//接受客户端的连接
SOCKET conn;
sockaddr_in peeraddr;//对等方的地址
int peerlen;//linux底下是socklen_t
while (1)
{
peerlen = sizeof(peeraddr);//peerlen是输入输出参数
conn = accept(listenfd, (sockaddr*)&peeraddr, &peerlen);
if (conn == INVALID_SOCKET)
{
cout<<"error with code = "<<WSAGetLastError()<<endl;
exit(1);
}

//成功打印对等方的地址
cout<<inet_ntoa(peeraddr.sin_addr)<<" "<<ntohs(peeraddr.sin_port)<<endl;

//使用没有封装的方式实现多线程
//unsigned threadId;
//HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, do_service, (void*)conn, 0, &threadId);
//if (hThread == NULL)
//{
// cout<<"error with code = "<<GetLastError()<<endl;
// exit(1);
//}
//CloseHandle(hThread);

//使用类的方式实现多线程
/*
不要使用局部的栈上变量,创建完线程后,这边的线程对象t马上就结束了
ServiceThread t(conn);
t.Start();
//应该用堆上变量
*/
ServiceThread* t = new ServiceThread(conn);//new的对象如何销毁?线程执行完毕后,就自动销毁该对象。增加bool autoDel_;
t->SetAutoDel(true);//new出来的对象,当其执行完毕,让其自动销毁
t->Start();
}

WSACleanup();


return 0;
}

TcpSrv\JThread.h

#ifndef _JTHREAD_H_
#define _JTHREAD_H_

#include <Windows.h>
class JThread
{
public:
JThread();
virtual ~JThread();

virtual void Run() = 0;

static unsigned __stdcall ThreadFun(void* p);
bool Start();

void Wait(DWORD timeout=INFINITE);
void SetAutoDel(bool autoDel);

private:
HANDLE hThread_;
unsigned threadId_;
bool autoDel_;
};

#endif // _JTHREAD_H_

TcpSrv\JThread.cpp

#include "JThread.h"
#include <process.h>

JThread::JThread() : hThread_(NULL), threadId_(0), autoDel_(false)
{
}

JThread::~JThread()
{
if (hThread_ != NULL)
CloseHandle(hThread_);
}

void JThread::SetAutoDel(bool autoDel)
{
autoDel_ = autoDel;
}


unsigned __stdcall JThread::ThreadFun(void* p)
{
JThread* jtp = (JThread*)p;
jtp->Run();
if (jtp->autoDel_)
delete jtp;//ServiceThread中的Run执行完毕后,这里自动销毁线程,p这个对象指向的就是ServiceThread
//这样就可以正常销毁ServiceThread对象了
return 0;
}

bool JThread::Start()
{
hThread_ = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, this, 0, &threadId_);
return hThread_ != NULL;
}

void JThread::Wait(DWORD timeout)
{
WaitForSingleObject(hThread_, timeout);
}

TcpCli\main.cpp

#pragma comment(lib, "ws2_32.lib")
#include <WinSock2.h>
#include <iostream>
using namespace std;

int main(void)
{
//来自F1的eg start
WORD wVersionRequested;//表示要请求的版本
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD( 2, 2 );

err = WSAStartup( wVersionRequested, &wsaData );//启动它
if ( err != 0 ) {
return 1;
}

//判断请求的是不是2.2版本
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return 1;
}
//来自F1的eg end

//创建socket
SOCKET sock;
//协议族:TCP/IP
//socket类型:流失套接字
//指定TCP协议,或者写0,让内核自动选择
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//~0表示,0取反就是全1,INVALID_SOCKET就是~0
if (sock == INVALID_SOCKET)
{
//WSAGetLastError()类似于Linux的errno
cout<<"1error with code = "<<WSAGetLastError()<<endl;
exit(1);
}

//准备一个服务器地址
sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
//servaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//Windows写法
//Linux写法如下
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//S_un.S_addr等于s_addr,用宏做的
servaddr.sin_port = htons(8888);

int ret;
ret = connect(sock, (sockaddr*)&servaddr, sizeof(servaddr));
if (ret == -1)
{
cout<<"2error with code = "<<WSAGetLastError()<<endl;
exit(1);
}

char buf[1024] = {0};
char recvbuf[1024] = {0};
while (1)
{
cin>>buf;
if (strcmp(buf, "quit") == 0)
break;
ret = send(sock, buf, strlen(buf), 0);
if (ret == -1)
{
cout<<"3error with code = "<<WSAGetLastError()<<endl;
exit(1);
}

ret = recv(sock, recvbuf, sizeof(buf), 0);
if (ret == -1)
{
cout<<"4error with code = "<<WSAGetLastError()<<endl;
exit(1);
}
if (ret == 0)//说明服务器关闭
{
cout<<"server close"<<endl;
break;
}
if (ret > 0)
{
cout<<recvbuf<<endl;
}
memset(buf, 0, sizeof buf);//C++的sizeof没有括号也行
memset(recvbuf, 0, sizeof recvbuf);
}

closesocket(sock);

return 0;
}
  • 测试:使用没有封装的方式实现多线程
    可以运行多个客户端了,2个客户端的端口是不一样的
  • (P102)多线程三:_beginthreadex,CreateThread与_beginthreadex区别,线程类封装

  • 测试:使用类的方式实现多线程
    客户端输入quit可以看到服务端的析构函数被调用
  • (P102)多线程三:_beginthreadex,CreateThread与_beginthreadex区别,线程类封装