首页 > 系统相关 >C++信号量实现线程间同步,windows使用SetEvent,linux使用sem_t,QT测试

C++信号量实现线程间同步,windows使用SetEvent,linux使用sem_t,QT测试

时间:2023-03-16 14:23:20浏览次数:36  
标签:信号量 QT 状态 对象 SetEvent 线程 信号 linux sem

 

目录

 

windows使用CreateEvent、SetEvent、ResetEvent、WaitForSingleObject

CreateEvent
功能:创建或打开一个命名的或无名的事件对象。

/*
参数1,lpEventAttributes,确定返回的句柄是否可被子进程继承,是NULL,此句柄不能被继承。 
参数2,bManualReset,复位方式,true(手动,WaitForSingleObject后必须手动调用ResetEvent清除信号)
                             false(自动,WaitForSingleObject后,系统自动清除事件信号);
参数3,bInitialState,初始状态,false为无信号,true为有信号;
参数4,lpName,信号名称,可以为Null;
返回值,返回事件句柄HANDLE。
*/
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpName);

1、所有线程都可以在一个等待函数中指定事件对象句柄。当指定的对象的状态被置为有信号状态时,单对象等待函数将返回。
2、对于多对象等待函数,可以指定为任意或所有指定的对象被置为有信号状态。当等待函数返回时,等待线程将被释放去继续运行。
3、使用SetEvent函数将事件对象的状态置为有信号状态。使用ResetEvent函数将事件对象的状态置为无信号状态。
4、当一个手动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至明确调用ResetEvent函数将其置为无符号状态。
5、当事件的对象被置为有信号状态时,任意数量的等待中线程,以及随后开始等待的线程均会被释放。
6、当一个自动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至一个等待线程被释放;系统将自动将此函数置为无符号状态。如果没有等待线程正在等待,事件对象的状态将保持有信号状态。
7、多个进程可持有同一个事件对象的多个句柄,可以通过使用此对象来实现进程间的同步。

SetEvent

//参数1,hEvent,一个内核事件对象的句柄。
//返回值,成功返回非零值,失败返回为0,调用GetLastError得到错误的详细信息。
BOOL SetEvent(HANDLE hEvent);

设置事件的状态为有状态,释放任意等待线程。
1、如果事件是手动的,此事件将保持有状态直到调用ResetEvent,这种情况下将释放多个线程;
2、如果事件是自动的,此事件设置为有状态,直到一个线程调用WaitForSingleObject,系统将设置事件的状态为无状态;
3、如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。

ResetEvent
把指定的事件对象设置为无信号状态。

//参数1,hEvent,一个内核事件对象的句柄。
//返回值,成功返回非0值,失败返回0值,调用GetLastError得到错误的详细信息。
BOOL ResetEvent(HANDLE hEvent);

1、一个事件对象一直都保持在无信号状态,直到显式调用 SetEvent or PulseEvent 函数把它设置到有信号状态。 这些无信号的事件对象会阻塞任何在内部调用wait函数的线程。
2、ResetEvent用于手动重置的事件对象。手动重置的对象在线程释放后必须手动置为无信号状态。 自动重置的事件对象在一个等待它成功的线程释放后会自动变为无信号状态。
3、重置一个无信号的事件对象没有任何效果。

WaitForSingleObject
用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。

/*
参数1,一个内核对象的句柄,可以是Event,Mutex,Semaphore(信号量),Process,Thread。
参数2,阻塞超时时长,单位毫秒
      1)传0:表示不阻塞,立即返回,返回值为WAIT_OBJECT_0。
      2)传>0:阻塞时长,超时时返回WAIT_TIMEOUT。
      3)传INFINITE:表示一直阻塞,直到等待句柄的状态发生改变。
返回值:
WAIT_ABANDONED :当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。
WAIT_OBJECT_0 :指定的对象处于有信号状态,
WAIT_TIMEOUT :等待超时。
WAIT_FAILED :出现错误,可通过GetLastError得到错误代码。
*/
DWORD WaitForSingleObject(_In_ HANDLE hHandle, _In_ DWORD dwMilliseconds);

WaitForMultipleObjects

/*
参数1,nCount对象句柄的最大数量为MAXIMUM_WAIT_OBJECTS。此参数不能为零。
参数2,lpHandles指向的数组中的对象句柄数。一组对象句柄。该数组可以包含不同类型对象的句柄。它可能不包含同一句柄的多个副本。
参数3,bWaitAll,如果此参数为TRUE,则在lpHandles数组中的所有对象的状态发出信号时,该函数返回。
	 如果为FALSE,则当任何一个对象的状态设置为信号时,该函数返回。在后一种情况下,返回值表示其状态导致函数返回的对象。
参数4,dwMilliseconds,超时间隔,以毫秒为单位。
	如果指定了非0值,则该函数将一直等到指定的对象发出信号或经过间隔。
	如果dwMilliseconds为0,则如果未发出指示对象,则该函数不会进入等待状态,它总是立即返回。
	如果dwMilliseconds是INFINITE,则仅在发出指定对象信号时才返回该函数。
返回值:
如果函数成功,返回值表示该事件导致该函数返回。这个值可以是下列之一。
WAIT_OBJECT_0到(WAIT_OBJECT_0 + nCount - 1如果bWaitAll为TRUE),则返回值表明所有指定对象的状态信号。
如果bWaitAll为FALSE,则返回值减去不是WAIT_OBJECT_0表示lpHandles数组的对象的满意指数的等待。如果多个对象在通话过程中信号成为,这是与所有的信号对象的最小索引值的信号对象的数组索引。
WAIT_ABANDONED_0至(WAIT_ABANDONED_0 + nCount - 1)如果bWaitAll为TRUE,则返回值表明所有指定对象的状态是触发的,并且至少对象之一,是一个废弃的互斥对象。

如果bWaitAll为FALSE,则返回值减去WAIT_ABANDONED_0 表示一个废弃的互斥对象在lpHandles数组中的下标,满足等待。
WAIT_TIMEOUTThe超时间隔已过,由bWaitAll参数指定的条件得不到满足。
*/
DWORD WaitForMultipleObjects(DWORD nCount,const HANDLE* lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);

测试demo
事件设置自动,初始无状态,主线程界面点击按钮触发设置事件为有状态,子线程调用WaitForSingleObject检查状态变化,按钮点击一次执行一次循环。

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <windows.h>
void ThreadFunc(LPVOID pParam)
{
    MainWindow *mw = (MainWindow*)pParam;
    static int countflag = 0;
    while(mw->isloop){
        DWORD waitResult = WaitForSingleObject(mw->m_event, INFINITE);//一直不超时,直到事件变为有状态
        switch (waitResult) {
          case WAIT_OBJECT_0:
            printf("WaitForSingleObject,_hShutdownCaptureEvent\n");
           break;
          case WAIT_TIMEOUT:
            printf("WaitForSingleObject,timeout notification\n");
            break;
          default:
            printf("WaitForSingleObject,unexpected error\n");
            break;
        }

        printf("countflag=%d\n",countflag);
        countflag ++;
    }
}

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_event = ::CreateEvent(NULL, FALSE, FALSE, NULL);//自动,初始无状态

    isloop = 1;
    hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, this, 0, &m_ThreadID);
}

MainWindow::~MainWindow()
{
    isloop = 0;
    CloseHandle(hThread);
    CloseHandle(m_event);
    delete ui;
}

//界面按钮,点击设置为有状态
void MainWindow::on_pushButton_SetEvent_clicked()
{
    SetEvent(m_event);
}

点击三次按钮,打印:

WaitForSingleObject,_hShutdownCaptureEvent
countflag=0
WaitForSingleObject,_hShutdownCaptureEvent
countflag=1
WaitForSingleObject,_hShutdownCaptureEvent
countflag=2

linux使用sem_init、sem_wait、sem_trywait、sem_post、sem_destroy

sem_init
是Posix信号量操作中的函数,初始化一个定位在 sem 的匿名信号量。

//参数1,sem为指向信号量结构的一个指针
//参数2,pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;
//参数3,value给出了信号量的初始值。
//返回值,成功返回0,失败返回 -1,设置errno。
int sem_init (sem_t *sem, int pshared, unsigned int value);

sem_wait
用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减1
1、sem_wait函数是一个原子操作,它的作用是从信号量的值减去一个“1”
2、它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果对一个值为2的信号量调用sem_wait(),线程将会继续执行,信号量的值将减到1。
3、如果对一个值为0的信号量调用sem_wait(),这个函数就 会地等待直到有其它线程增加了这个值使它不再是0为止。
4、如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。

//参数,由sem_init调用初始化的信号量对象的指针
//返回值,成功返回0,失败时信号量的值不改动,返回-1.errno标识错误.
int sem_wait(sem_t * sem);

sem_trywait
sem_trywait是sem_wait的非阻塞版本。函数 sem_trywait和sem_wait有一点不同,即如果信号量的当前值为0,则返回错误而不是阻塞调用。错误值errno设置为EAGAIN。

int sem_trywait(sem_t *sem);

sem_post
给信号量的值加上一个“1”
1、它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;
2、当有线程阻塞在这个信号量上时,调用这个函数会使其中一个线程不在阻塞,选择机制是由线程的调度策略决定的。

//返回值,成功返回 0;错误时,信号量的值没有更改,返回-1,并设置 errno 来指明错误。
int sem_post(sem_t *sem);

测试demo

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <pthread.h>
#include <semaphore.h>
void *thread_function(void *arg)
{
    MainWindow *mw = (MainWindow*)arg;
    static int countflag = 0;
    while (mw->isloop)
    {
        sem_wait(&mw->bin_sem);//信号量为0时阻塞,大于0时往下运行
        printf("run,countflag=%d\n",countflag);
        countflag++;
    }
    return NULL;
}

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    int res = sem_init(&bin_sem, 0, 0);//初始化信号量
    if (res != 0)
    {
        perror("Semaphore initialization failed");
    }
    isloop = 1;
    res = pthread_create(&a_thread, NULL, thread_function, this);//子线程调用sem_wait
    if (res != 0)
    {
        perror("Thread creation failure");
    }
}
MainWindow::~MainWindow()
{
    isloop = 0;
    sem_post(&bin_sem);//再执行一次子线程循环,否则sem_wait阻塞无法退出
    sem_destroy(&bin_sem);
    pthread_join(a_thread,NULL);
    delete ui;
}

//点击按钮,调用sem_post使信号量+1
void MainWindow::on_pushButton_sem_post_clicked()
{
    sem_post(&bin_sem);
}

点击三次按钮,关闭程序,打印

run,countflag=0
run,countflag=1
run,countflag=2
run,countflag=3

标签:信号量,QT,状态,对象,SetEvent,线程,信号,linux,sem
From: https://www.cnblogs.com/lidabo/p/17222368.html

相关文章

  • QT5笔记: 29. 文本文件读写
    例子:主要讲了QFile、QTextStream进行文本文件读写MainWindow.h#ifndefMAINWINDOW_H#defineMAINWINDOW_H#include<QMainWindow>QT_BEGIN_NAMESPACEnamesp......
  • Linux根目录详解
    /  根目录:根目录,文件的最顶端,整个文件系统的根目录/bin  用户二进制文件:存放系统所需的重要命令/sbin  系统二进制文件:存放一些系统管理的命令,一般只能由超级权......
  • QT5笔记: 30. 二进制文件读写
    Qt预定义类型文件*.stm标准二进制文件*.dat例子:MainWindow.h#ifndefMAINWINDOW_H#defineMAINWINDOW_H#include<QItemSelectionModel>#include<QMainWin......
  • QT5笔记:27. MDI应用程序设计
    MDI:MultipleDocumentInterface多窗口文档界面例子:MainWindow.h#ifndefMAINWINDOW_H#defineMAINWINDOW_H#include<QMainWindow>#include<QMdiSubWindow>......
  • Linux之修改软链接地址
    创建软链接ln-s[源文件或目录][目标文件或目录]例如:当前路径创建test引向/var/www/test文件夹ln–s/var/www/testtest创建/var/test引向/var/www/test文件夹......
  • Linux网络编程IP地址的字符串与网络字节序转换:inet_addr()、inet_aton()、inet_ntoa()
    Linux网络编程IP地址的字符串与网络字节序转换向sockaddr_in注入地址时,需要将ip地址的字符串形式转化为网络字节序的形式;而相反地,网络字节序也能转化回字符串形式。用到的......
  • 【Linux】Vim编辑器(未完善)
    一、学习建议如果对Linux操作系统还尚未了解,请先学习Linux或了解VMware虚拟机后再学习此内容更好。二、学习目标能够说出vim的三种工作模式能够说出vim对应复......
  • Qt 代码编程规范(自用)
    因:目前为止,自己都没有一个明确的代码编写规范,导致一个项目一种编写风格,看起来真费劲。。。希望自己能定一个合心意的代码规范,之后按照这个规范写代码。 1、首先是头文件......
  • QT5笔记: 21. QStandardItemModel
    QStandardItemModel存放数据QItemSelectionModel选择项模型例子:本例子中QListView没有做任何处理,只是拖放至ui文件,设置了布局mainwindow.h#ifndefMAINWINDOW_H#......
  • QT5笔记: 22. 自定义代理
    代理作用:在界面发生编辑时可以指定编辑所用的组件,可以沟通Model和View自定义代理需要继承的基类和需要实现的方法使用步骤:继承QStyledItemDelegate,实现上面的四个......