C++ windowns多线程

转自:http://blog.chinaunix.net/uid-27177626-id-3963099.html


 进程是系统中的重要概念,简单来说字面的意思就是一个运行中的程序,但是程序代表的是静态的指令代码。进程由系统管理的内核对象和存放程序运行资源的地址空间组成。内核对象由系统管理,因此应用程序是无法直接访问的;地址空间中则包含着程序运行所需的所有资源,如可执行模块、DLL、代码和数据,以及动态分配的栈与堆。可以说,其实进程就是程序运行的资源的容器。但是进程只是为程序的执行提供了一个场所,真正实现执行流程的是线程。每个进程启动时都会自动建立一个线程作为主线程,由它去创建其他的线程。线程与进程相比更为轻量级,二者的主要联系在于:
1. 线程创建在进程的地址空间中,对于进程资源有着完全访问权限,多线程间共享资源,可自由通信;
2. 线程建立时也有自己的内核对象和线程栈,而非地址空间;
3. 一般需要实现多任务时我们更推荐使用线程实现,因为创建一个进程需要分配地址空间,而且进程间的切换也很不方便,即使用进程开销很高;


一、创建多线程
     简单了解了进程与线程的概念之后,我们来看看如何在程序中创建线程。Windows SDK为我们提供了创建线程的专用函数:CreateThread()

点击(此处)折叠或打开

  1. HANDLE CreateThread(
  2.   LPSECURITY_ATTRIBUTES lpsa, 
  3.   DWORD cbStack, 
  4.   LPTHREAD_START_ROUTINE lpStartAddr, 
  5.   LPVOID lpvThreadParam, 
  6.   DWORD fdwCreate, 
  7.   LPDWORD lpIDThread
  8. );
-1-第一个参数是安全属性结构,主要控制该线程句柄是否可为进程的子进程继承使用,默认使用NULL时表示不能继承;若想继承线程句柄,则需要设置该结构体,将结构体的bInheritHandle成员初始化为TRUE;
-2-cbStack表示的线程初始栈的大小,若使用0则表示采用默认大小初始化;
-3-lpStartAddr表示线程开始的位置,即线程要执行的函数代码,这点有点类似于回调函数的使用;
-4-lpvThreadParam用来接收线程过程函数的参数,不需要时可以设置为NULL;
-5-fdwCreate表示创建线程时的标志,CREATE_SUSPENDED表示线程创建后挂起暂不执行,必须调用ResumeThread才可以执行,0表示线程创建之后立即执行
-6-lpIDThread用来保存线程的ID;
     了解了CreateThread函数的用法,我们来看一个例子实际体验一下。下面的例子会开启一个线程循环输出信息,我们可以查看结果:

点击(此处)折叠或打开

  1. //MultiThread

  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <windows.h>
  5. using namespace std;

  6. DWORD WINAPI Fun1Proc(LPVOID lpParameter); 


  7. int main()
  8. {

  9.     int j = 0;
  10.     
  11.     HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
  12.     CloseHandle(hThread_1);
  13.     
  14.     while (j++ < 1000)
  15.      cout << "MainThread is running for" << " the "<< j <<" times "<<endl;
  16.         
  17.     system("pause");
  18.     return 0;
  19.         
  20.     
  21. }


  22. DWORD WINAPI Fun1Proc(LPVOID lpParameter)
  23. {
  24.       int i = 0;
  25.       while (i++ < 1000)
  26.           cout << "Thread 1 is running for" <<" the "<< i <<" times "<<endl;
  27.        
  28.       return 0;
  29.       }
      由于我们使用system("pause")命令,因此我们不需要Sleep()命令同样可以看到主线程和分线程的交替执行:



二、多线程的问题
     上面的程序我们只是建立一个新线程,如果建立两个呢?下面我们模拟一个火车售票的模型,线程1和线程2同时负责售票,主线程负责平台搭建。线程1和线程2分别访问全局变量tickets,输出其值作为票号然后将其值减一,直至“售完”所有票:

点击(此处)折叠或打开

  1. //MultiThread

  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <windows.h>
  5. using namespace std;

  6. DWORD WINAPI Fun1Proc(LPVOID lpParameter); 
  7. DWORD WINAPI Fun2Proc(LPVOID lpParameter);

  8. int tickets = 100;

  9. int main()
  10. {
  11.            
  12.     HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
  13.     HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
  14.     CloseHandle(hThread_1);
  15.     CloseHandle(hThread_2);
  16.            
  17.     system("pause");
  18.     return 0;
  19.         
  20.     
  21. }


  22. DWORD WINAPI Fun1Proc(LPVOID lpParameter)
  23. {
  24.      
  25.       while (true)
  26.       {        
  27.             if (tickets > 0)
  28.             {
  29.                  Sleep(10);
  30.                  cout << "Thread 1 sell ticket : "<<tickets--<<endl;
  31.                  }
  32.             else
  33.                  break;       
  34.             }
  35.       return 0;
  36.       }
  37.       
  38. DWORD WINAPI Fun2Proc(LPVOID lpParameter)
  39. {

  40.        while (true)
  41.       {
  42.         
  43.             if (tickets > 0)
  44.             {
  45.                  Sleep(10);
  46.                  cout << "Thread 2 sell ticket : "<<tickets--<<endl;
  47.                  }
  48.             else
  49.                  break;
  50.          
  51.             }
  52.       return 0;
  53.       }
      再次运行之后结果:

     分析下上面的结果,首先线程1和线程2确实交替运行“购票”了,其次看到不规则的输出,1314连到一起。关于这点我们要知道由于CPU是执行分片处理的,即不同的线程会得到不同的时间片来执行程序,尤其是对于单CPU来说更是如此,因此当线程1执行到"Thread 1 sell ticket"时结束分片,由线程2继续执行;线程2执行到同样位置再次交还给线程1继续执行显示“13”,然后线程2执行显示“14”。这样看好像也没有问题,但是问题在于最后出现了“0”,我们的代码中是不允许出现0的,之所以这样,同上面的元婴一样,在线程1执行到ticket = 1时交接给了线程2执行,并且输出了其值1,然后线程1继续输出了当前值0.此时的if条件就失去作用了。虽然这种情况不一定总是发生,但是在实际的操作中是很有可能出现的,而且排查起来也很困难。那么我们该如何解决呢?
     问题的关键在于线程1和线程2同时访问了全局变量tickets,导致了错误;那么我们的解决方案就是将全局变量的访问控制起来就可以了,不允许同时有多个线程同时访问该变量。我们使用互斥量来实现。


三、互斥量的应用
     使用互斥量并不困难,核心步骤如下:
-1-CreateMutex创建一个互斥量:

点击(此处)折叠或打开

  1. HANDLE CreateMutex( 
  2.   LPSECURITY_ATTRIBUTES lpMutexAttributes, 
  3.   BOOL bInitialOwner, 
  4.   LPCTSTR lpName 
  5. );
      第一个参数同样是安全结构,默认是NULL不能继承句柄;第二个参数为FALSE时创建Mutex时不指定所有权,若为TRUE则指定为当前的创建线程ID为所有者,其他线程访问需要先ReleaseMutex;第三个参数用于设置Mutex名,后续我们会说明,为NULL时表示是匿名互斥量。
-2-WaitForSingleObject():请求一个互斥量的访问权;
-3-ReleaseMutex():释放一个互斥量的访问权;
     好了,我们再来看看应用了互斥量的改进程序:

点击(此处)折叠或打开

  1. //MultiThread

  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <windows.h>
  5. using namespace std;

  6. DWORD WINAPI Fun1Proc(LPVOID lpParameter); 
  7. DWORD WINAPI Fun2Proc(LPVOID lpParameter);

  8. int tickets = 100;
  9. HANDLE hMutex;

  10. int main()
  11. {
  12.    

  13.     hMutex = CreateMutex(NULL, FALSE, NULL);
  14.     
  15.     HANDLE hThread_1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
  16.     HANDLE hThread_2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
  17.     CloseHandle(hThread_1);
  18.     CloseHandle(hThread_2);

  19.         
  20.     system("pause");
  21.     return 0;
  22.         
  23.     
  24. }


  25. DWORD WINAPI Fun1Proc(LPVOID lpParameter)
  26. {

  27.       while (true)
  28.       {
  29.             WaitForSingleObject(hMutex, INFINITE);
  30.             if (tickets > 0)
  31.             {
  32.                  Sleep(10);
  33.                  cout << "Thread 1 sell ticket : "<<tickets--<<endl;
  34.                  }
  35.             else
  36.                  break;
  37.             ReleaseMutex(hMutex);
  38.             }
  39.       return 0;
  40.       }
  41.       
  42. DWORD WINAPI Fun2Proc(LPVOID lpParameter)
  43. {

  44.        while (true)
  45.       {
  46.             WaitForSingleObject(hMutex, INFINITE);
  47.             if (tickets > 0)
  48.             {
  49.                 Sleep(10);
  50.                  cout << "Thread 2 sell ticket : "<<tickets--<<endl;
  51.                  }
  52.             else
  53.                  break;
  54.             ReleaseMutex(hMutex);
  55.             }
  56.       return 0;
  57.       }
      这次运行之后的结果不会出现上面的错误了:

      互斥量还有一个小应用,利用命名互斥量来保证只有一个程序实例运行。我们可以创建一个命名互斥量,当程序要重复运行时,检查互斥量的返回值,若为ERROR_ALREADY_EXISTS则表示已经有一个实例运行了,直接return即可。在源程序中添加以下代码:

点击(此处)折叠或打开

  1. //确保只有一个实例运行
  2.     HANDLE hMutex_1 = CreateMutex(NULL, TRUE, "tickets");
  3.     if (hMutex_1)
  4.     {
  5.                if (ERROR_ALREADY_EXISTS == GetLastError())
  6.                {
  7.                        cout << "Only one instance can run !" << endl;
  8.                        system("pause");
  9.                        return 0;
  10.                                         }
  11.                }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
网关程序:主要目的是作了一个中间程序转发网络消息,其实在网上有很多这样的程序,比如跨平台的ACE,目前版本为5.6,如果从ACE开始学习网关,个人觉得挺费劲的,我也曾经想用ACE编写网关程序,后来由于ACE的复杂性,还是胆怯了,还是自己下定决心写了一个网关程序。该网关程序目前只支持Windows,下一步的目标准备将程序移植到GCC环境下。程序中用到STL的std::map和std::list,也大量的运行了模板类,如:关于线程的参数ARGS即为模板类:template ARGS{}、还有一个就是SOCKET结构体:HOSTSTRUCTSTRCT的定义也是用到了模板类。程序的主要部份为:class CFramework 文件:framework.h framework.cpp,如果想编写一个网关程序,首先需要从该类继承,如目前例程中的:class CMyGateway;大家都知道网关程序即SOCKET通讯多线程程序,其中当然用到SOCKET;网关中有SOCKET服务端,也有SOCKET客户端;作为SOCKET服务端时,需要接收远程主机的连接,当远程主机请求连接,根据业务需要首先要验证该客户端是否是合法的客户,此时,需要从系统的允许访问队列表查询是否有该主机的信息,如果有该主机的信息,则允许该主机连接,此时触发OnConnected事件,在该事件中,可以接收客户端的登录信息,验证客户端的登录信息,如果验证成功,则将该主机信息添加到系统路由表中,当有消息需要转发到该主机时,从系统路由表取到目标主机的信息,通过host.fd发送消息;同理,网关作为一个客户端时,需要连接其它远程服务器,一旦连接上后,触发OnConnected事件,在该事件中,我们可以发送登录信息,并接收应答信息,解析应答信息,判断我们的登录是否成功,如果成功的话,将连接主机的信息添加到系统路由表中,当有其它信息需要转发到该主机时,从系统路由表中取到连接信息通过send() host.fd转发信息。在class CFramework中还有一定非常重要的函数:OnExecuteMessagte(const xuwn::MESSAGE& message)方法,这个方法是在从消息队列取到消息后执行的,xuwn::MESSAGE中定义了一个buffer即收到的消息,同时消息的长度为:message.size.nhead+message.size.nbody,您可以处理消息,在模拟程序中,我将消息转发到另外一个服务器即:B_HOST,HOSTSTRUCT的有个字段name即我称之为节点名称,该名称是我作为索引用的,在系统路由中只能存在这样一个KEY值的HOSTSTRUCT;在class CFramework中还有一个重要函数:OnRecvData(const HOSTSTRCT& host__, xuwn::MESSAGE& message),这个方法是由我们执行如何接收消息的,因为大多数时候我们定义消息都为变长,即消息存在消息头+消息体,大多时候,消息头为定长,消息体的长度在消息头中体现,当我们接收完消息头后,设置后继包(消息体)的长度,再调用CFramework::OnRecvData(host__, message)去接收消息体,并把消息写入到消息队列中。
当然!以下是一个使用C++多线程编写的简单Ping代码示例: ```cpp #include <iostream> #include <thread> #include <chrono> #include <atomic> #ifdef _WIN32 #include <winsock2.h> #include <ws2tcpip.h> #pragma comment(lib, "ws2_32.lib") #else #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #endif std::atomic<bool> running(true); void ping(const std::string& ip, int timeout) { while (running) { #ifdef _WIN32 SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { std::cerr << "Failed to create socket" << std::endl; return; } sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(80); inet_pton(AF_INET, ip.c_str(), &(serverAddr.sin_addr)); if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { std::cout << ip << " is down" << std::endl; } else { std::cout << ip << " is up" << std::endl; } closesocket(sock); #else int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "Failed to create socket" << std::endl; return; } sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(80); inet_pton(AF_INET, ip.c_str(), &(serverAddr.sin_addr)); if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { std::cout << ip << " is down" << std::endl; } else { std::cout << ip << " is up" << std::endl; } close(sock); #endif std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); } } int main() { std::string ip; int timeout; std::cout << "Enter IP address to ping: "; std::cin >> ip; std::cout << "Enter timeout (in milliseconds): "; std::cin >> timeout; std::thread pingThread(ping, ip, timeout); // Wait for user input to stop the ping thread std::cin.ignore(); running = false; pingThread.join(); return 0; } ``` 这段代码使用了sockets来实现Ping功能。它创建一个线程来定期尝试连接到指定的IP地址,并根据连接结果输出相应的信息。 请注意,此代码在Windows和Linux上都可以编译运行。在Windows上,它使用了Winsock库;在Linux上,它使用了socket库。因此,根据不同的操作系统,你可能需要进行一些调整和适配。 希望这个示例对你有所帮助!如果你有任何疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值