C/C++如何判断程序是否已经在运行?

7

如果应用程序作为服务运行,并且您选择CreateEvent或CreateMutex,请确保在Global对象命名空间http://msdn.microsoft.com/en-us/library/windows/desktop/aa382954(v=vs.85).aspx中创建一个对象。 - buzz3791
10个回答

17

如果这是你的程序,那么在 Windows 系统下最短的可能版本是:

int main(int argc, char** argv)
{
    CreateMutexA(0, FALSE, "Local\\$myprogram$"); // try to create a named mutex
    if(GetLastError() == ERROR_ALREADY_EXISTS) // did the mutex already exist?
        return -1; // quit; mutex is released automatically

    // ... program code ...
}

不需要变得过于复杂,如果您只需要一个简单的检查...


不确定关于"互斥锁会自动释放"的注释。MSDN文档似乎在警告放弃互斥锁的情况,"如果一个互斥锁被放弃,拥有该互斥锁的线程在终止之前没有正确释放它。在这种情况下,共享资源的状态是不确定的,并且继续使用互斥锁可能会掩盖潜在的严重错误。一些应用程序可能会尝试将资源恢复到一致的状态;这个例子只是返回一个错误并停止使用互斥锁。"--http://msdn.microsoft.com/en-us/library/ms686927%28VS.85%29.aspx - buzz3791
2
@buzz3791 再次阅读手册:“使用CloseHandle函数关闭句柄。当进程终止时,系统会自动关闭句柄。当最后一个句柄关闭时,互斥对象将被销毁。” - Jorma Rebane

12

在继续之前,我认为你需要考虑一下你的情景。对于“多次运行相同程序”,有许多不同的解释。例如,您是希望:

  1. 每台计算机只运行一次
  2. 每次登录只运行一次
  3. 每个用户只运行一次

这些都有不同但类似的解决方案。

最容易描述的是每台计算机仅运行一次的情况。在这种情况下,您需要创建一个命名的Mutex。每次启动程序时,它必须获取此mutex。如果成功获取,程序将运行并在进程生命周期内保持该mutex。否则,其他程序正在运行,它们会立即退出。

不幸的是,这种方法也有其缺点。如果我想破坏您的程序,我可以创建一个具有相同名称的mutex。这将防止您的程序运行任何实例,因为它们无法确定谁拥有该mutex,只知道有某个程序正在持有该mutex。


2
关于“敌对互斥锁”,如果您不是在编写类似Word这样的软件,那么您真的需要担心它吗? - Rex M
3
@ Rex M,这确实是作者必须回答的问题。我敢说答案是否定的。但当你编写程序时,你应该考虑到这一点。即使只是为了防止意外的自我影响。 - JaredPar

9
当您的应用程序的第一个实例启动时,您可以创建一个互斥量。为了防止第二个实例,您只需要检查互斥量是否正在使用即可。
实际上,有一个问题提出了关于使用互斥量进行此目的here,请查看JaredPar的答案。
注意:如果您希望“一个实例”仅适用于用户会话(而不是所有用户),则可以使用本地互斥量。

3

另一种简单的解决方案是创建一个适当唯一的全局命名事件(可能是 GUID 字符串),然后在启动时检查其是否存在。如果存在,则已经启动了该应用程序的一个实例。如果不存在,则自动创建该事件并可以继续运行,例如:

// for brevity, a complete set of error handling has been omitted

m_hEvent = CreateEvent(NULL, TRUE, FALSE, szMyUniqueNamedEvent);

switch (GetLastError)
{
    // app is already running
    case ERROR_ALREADY_EXISTS:
    {
        CloseHandle(m_hEvent);

        // now exit
        break;
    }

    // this is the first instance of the app
    case ERROR_SUCCESS:
    {
        // global event created and new instance of app is running,
        // continue on, don't forget to clean up m_hEvent on exit
        break;
    }
}

任何原子操作,如果全局对象不存在则创建它,并返回一个值指示是否发生了此创建,都可以完成该任务。 - j_random_hacker

3

2
这是一个我使用boost.interrprocess脚本编写的类,我用它来在GUI和CLI版本之间进行同步。
你可能会发现它很有用:
#pragma once

#include <boost/interprocess/windows_shared_memory.hpp>
#include <boost/interprocess/mapped_region.hpp>

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

using boost::interprocess::windows_shared_memory;
using boost::interprocess::mapped_region;
using boost::interprocess::open_or_create;
using boost::interprocess::read_write;
using boost::interprocess::interprocess_exception;

class CProcessMutex
{

public:
    CProcessMutex()
        : m_region()
        , m_status(false)
    {
        initSharedMemory();
        Increment();
    }

    ~CProcessMutex()
    {
        Decrease();
    }

public:
    int GetCount()
    {
        return m_status ? *(static_cast<unsigned char*>(m_region.get_address())) : 0;
    }

private:
    void initSharedMemory()
    {
        try
        {
            //Create a native windows shared memory object.
            windows_shared_memory shm (open_or_create, "shared_memory", read_write, 1);
            //Map the whole shared memory in this process
            m_region.swap(mapped_region(shm, read_write));
            m_status = true;
        }
        catch(interprocess_exception &ex)
        {
            ex.what();
            m_status = false;
        }
    }

    void Increment()
    {
        if(m_status) (*(static_cast<unsigned char*>(m_region.get_address())))++;
    }
    void Decrease()
    {
        if(m_status) (*(static_cast<unsigned char*>(m_region.get_address())))--;
    }
private:
    mapped_region m_region;
    bool m_status;
};

使用方法很简单:
CProcessMutex pm;
size_t current_process_count = pm.GetCount();
if(current_process_count > 1)
{
 ...
}

因此,您可以轻松限制并行处理的进程数量。

2
当您使用Qt时,您可以下载QtSingleApplication组件。 点击此处进行下载。

1

使用互斥锁,就像其他人建议的那样。

来自微软的CreateMutex()文档提供了许多有用的信息,并特别解决了使用互斥锁防止程序运行多个实例的情况。尤其是:

  • 调用CreateMutex()并设置 bInitialOwner = FALSE,然后调用等待函数(例如WaitForSingleObject()),以确保只有一个实例获取互斥锁。
  • 如果您担心拒绝服务攻击,请考虑使用被锁定的文件。

0

如果同时启动前两个实例,它能正常工作吗? - Liran Orevi
2
这里存在竞态条件,因为进程枚举无法保证原子性。这就是为什么互斥锁是个好主意的原因。 - RBerteig
很抱歉,但这是个糟糕的想法。除了RBerteig提到的竞态条件外,你还有正确识别进程的问题 -- 如果你通过比较窗口标题来实现这一点,你很容易得到假阳性(其他程序与你的窗口标题相同)和假阴性(如果目标程序的窗口标题发生变化,例如许多程序在窗口标题中显示打开文档的名称)。更好的方法存在,应该使用它。 - j_random_hacker

-1
你可以检查窗口类是否已经注册。请查看this的MSDN条目。

从查看链接和相关链接,我不认为RegisterClassEx()返回的句柄是跨进程实体——即使对于“应用程序全局类”(设置CS_GLOBALCLASS),您必须在每个进程中加载注册类的DLL,根据http://msdn.microsoft.com/en-us/library/ms633574(VS.85).aspx。 - j_random_hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接