MFC-多线程

方法一:CreateThread
一般在WIN32环境下使用
DWORD WINAPI func(LPVOID lpParam)//线程函数 //注意格式 { int n = (int)lpParam; CString str; str.Format(_T("n=%d"),n); AfxMessageBox(str); return 0; //线程退出吗 } void CduoxiancenDlg::OnBnClickedButton1() { DWORD ID = 0; HANDLE thread = CreateThread(NULL, 0, func, (LPVOID)123, 0, &ID); /* 参数1:设为NULL 参数2:设置初始栈的大小,以字节为单位,如果为0,那么使用默认栈空间大小(1M) 参数3: 线程函数的指针 参数4:LPVOID 向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL 参数5:线程标志,可取值如下 CREATE_SUSPENDED(0x00000004):创建一个挂起的线程 0:表示创建后立即激活 参数6:DWORD 保存新线程的id。如果不需要可以NULL 返回值:函数成功,返回线程句柄;函数失败返回false。若不想返回线程ID,设置值为NULL 说明:线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象 */ BOOL b=CloseHandle(thread);//关闭线程 //参数:线程句柄 //返回值:TRUE:执行成功;FALSE:执行失败,可以调用GetLastError()获知失败原因 }
方法二:AfxBeginThread--推荐
一般在MFC环境下使用
功能是创建用户界面线程和工作者线程
工作线程:与界面没有关系的线程
UINT func1(LPVOID pParam) //线程函数 //注意格式 { int n = (int)pParam; CString str; str.Format(_T("n=%d"),n); AfxMessageBox(str); return 0; } void CduoxiancenDlg::OnBnClickedButton2() { //AfxBeginThread线程会自动释放 CWinThread* pp= AfxBeginThread(func1,LPVOID(456),0,0,0,NULL);//创建工作线程 //工作线程与界面没有关系 /* 参数1:线程的入口函数,声明一定要如下: UINT MyThreadFunction(LPVOID pParam),不能设置为NULL 参数2:传递到线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程 参数3:线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级 参数4:新创建的线程的栈的大小.如果为 0,使用默认大小(1M) 参数5:CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread 0 : 创建线程后就开始运行 参数6:指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性. 如果为 NULL,那么新创建的线程就具有和主线程一样的安全性 返回值: 成功时返回一个指向新线程的线程对象的指针,否则NULL 如果要在线程内结束线程,可以在线程内调用 AfxEndThread. */ pp->m_hThread;//线程句柄 pp->m_nThreadID;// DWORD 当前线程的 ID pp->m_bAutoDelete;//值为1时(默认),线程终止时自动销毁对象 }
hThread=pp->m_hThread;//获取句柄 pp->m_bAutoDelete;//用于确定在线程終止時,线程句柄是否自动释放 //FALSE 表示不自动释放;要记得自己在用完后调用delete删除创建线程的对象或CloseHandle函数关闭线程句柄 //TURE 表示自动释放 默认; pp->m_nThreadID;// 用於記錄線程ID; pp->m_pMainWnd; // 用於表示應用程序的主窗口
等待线程执行完毕
CWinThread* hThread;//线程对象的指针 UINT func(LPVOID param) { CString str; for (int i = 0; i < 1000; i++) { str.Format(_T("i=%d\r\n"), i); ::OutputDebugString(str); } return 0; } void Cduoxiancen8Dlg::OnBnClickedButton1() { hThread = AfxBeginThread(func, NULL, 0, 0, 0, NULL); DWORD d =WaitForSingleObject(hThread->m_hThread, INFINITE);//等待线程处于激活状态 //等待线程执行完 //参数1:线程句柄 if (d== WAIT_OBJECT_0) { ::OutputDebugString(_T("线程执行完毕\r\n")); } }
在线程函数中调用类成员变量和类成员函数
UINT func1(LPVOID pParam) { CduoxiancenDlg* p = (CduoxiancenDlg*)pParam; //创建主对话框的指针=类指针 int n = p->tt; CString s = p->str; //获取类成员变量 ::OutputDebugString(s); int nn = p->add(2,30); //调用类成员函数 s.Format(_T("nn=%d"), nn); ::OutputDebugString(s); return 0; } void CduoxiancenDlg::OnBnClickedButton2() { tt = 300; str = _T("祖国万岁"); CWinThread* pp= AfxBeginThread(func1,this,0,0,0,NULL); //参数2传递的是类指针 } int CduoxiancenDlg::add(int a, int b) { return a+b; }
类静态函数作为线程函数
注意:类普通函数是不能作为线程函数的,类静态函数可以
h文件
// duoxiancenDlg.h: 头文件 // #pragma once // CduoxiancenDlg 对话框 class CduoxiancenDlg : public CDialogEx { // 构造 public: CduoxiancenDlg(CWnd* pParent = nullptr); // 标准构造函数 // 对话框数据 #ifdef AFX_DESIGN_TIME enum { IDD = IDD_DUOXIANCEN_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 实现 protected: HICON m_hIcon; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedButton1(); afx_msg void OnBnClickedButton2(); static UINT func1(LPVOID pParam);//定义成静态类成员函数 //注意:格式 };
cpp中的代码
UINT CduoxiancenDlg::func1(LPVOID pParam) { int n = (int)pParam; CString str; str.Format(_T("n=%d "), n); ::OutputDebugString(str); return 0; } void CduoxiancenDlg::OnBnClickedButton2() { CWinThread* pp= AfxBeginThread(CduoxiancenDlg::func1,(LPVOID)10,0,0,0,NULL); //参数1把类静态成员函数作为线程函数 }
线程的挂起(暂停)
//线程挂起 DWORD nn= ::SuspendThread(hThread);//挂起线程 //参数:HANDLE 线程句柄 //返回值:如果返回-1,表示失败;如果返回整数,表示线程已经被挂起过得次数
线程的恢复
DWORD i = ::ResumeThread(hThread); //参数:HANDLE 线程句柄 //返回值:如果函数成功,返回值是线程的上一个挂起计数。如果函数失败,返回值为-1 //说明:ResumeThread之后,线程不一定执行,每次ResumeThread挂起计数-1,挂起计数变为0,才执行线程
线程的优先级
HANDLE handle=NULL; void Cduoxiancen1Dlg::OnBnClickedButton4() { //线程优先级 CWinThread* pp1 = AfxBeginThread(func1, LPVOID(1000), 0, 0, 0, NULL); /* 参数3:线程的相对优先级 相对的意思:相对于本进程中的优先级 THREAD_PRIORITY_IDLE:idle (最低) 优先级值:如果进程优先级为realtime则调整为16,其它情况为1 THREAD_PRIORITY_LOWEST:LOWEST 低 优先级值:-2(在原有基础上-2) THREAD_PRIORITY_BELOW_NORMAL:BELOW 低于标准 优先级值:-1(在原有基础上-1) THREAD_PRIORITY_NORMAL:NORMAL(标准-默认) 优先级值:不变(取进程优先级值) THREAD_PRIORITY_ABOVE_NORMAL:ABOVE 高于标准 优先级值:+1(在原有基础上+1) THREAD_PRIORITY_HIGHEST:HIGHEST (高) 优先级值:+2(在原有基础上+2) THREAD_PRIORITY_TIME_CRITICAL:CRITICAL(最高) 优先级值:如果进程优先级为realtime则调整为31,其它情况为15 如果想了解的更明白:请看 https://www.cnblogs.com/liming19680104/p/17235490.html */ handle = pp1->m_hThread; int b=GetThreadPriority(handle);//获取线程优先级//参数:HANDLE 线程的句柄 //句柄必须具有 THREAD_QUERY_INFORMATION 或 THREAD_QUERY_LIMITED_INFORMATION 访问权限 //返回值:如果函数成功,则返回值为线程的优先级。如果函数失败,则返回值 THREAD_PRIORITY_ERROR_RETURN CString str; str.Format(_T("b=%d\r\n"),b); ::OutputDebugString(str); BOOL bb=SetThreadPriority(handle, THREAD_PRIORITY_ABOVE_NORMAL);//设置线程的优先级值 //参数1:HANDLE 线程的句柄 //句柄必须具有 THREAD_SET_INFORMATION 或 THREAD_SET_LIMITED_INFORMATION 访问权限 //参数2:int 线程的优先级值 // THREAD_MODE_BACKGROUND_BEGIN=0x00010000 开始后台处理模式。 系统降低了线程的资源计划优先级,以便它可以执行后台工作,而不会显著影响前台的活动。 // 仅当 hThread 是当前线程的句柄时,才能指定此值。 如果线程已在后台处理模式下,该函数将失败 //THREAD_MODE_BACKGROUND_END=0x00020000 结束后台处理模式。 系统在线程进入后台处理模式之前还原线程的资源计划优先级。 // 仅当 hThread 是当前线程的句柄时,才能指定此值。 如果线程不在后台处理模式下,该函数将失败 //THREAD_PRIORITY_ABOVE_NORMAL 高于标准 //THREAD_PRIORITY_BELOW_NORMAL 低于标准 // THREAD_PRIORITY_HIGHEST 最高(其实是“次高”) // THREAD_PRIORITY_IDLE 空闲(最低) // THREAD_PRIORITY_LOWEST 最低(其实是“次低”) // THREAD_PRIORITY_NORMAL 标准 //THREAD_PRIORITY_TIME_CRITICAL 关键时间(最高) //返回值:如果该函数成功,则返回值为非零值。 如果函数失败,则返回值为零 b = GetThreadPriority(handle); str.Format(_T("b=%d\r\n"), b); ::OutputDebugString(str); }
退出线程
ExitThread(0);//退出线程 //通过调用ExitThread 函数,线程将自行撤消(尽量不要使用这种方法 ) //参数:DWORD 0 表示成功完成
线程的退出与终结
退出:
线程函数执行完毕,自己退出
1.添加一个C++类
2.在线程函数中创建一个类对象
3.执行程序
通过断点可以看到正常执行了构造函数和析构函数
终结:
线程函数没有执行完,用户终结执行
如果使用ExitThread(MFC中AfxExitThread) 退出线程,析构函数不会被执行,所以在有类对象时C++中不建议使用
在线程外终止一个线程
BOOL b=::TerminateThread(hThread,1);//在线程外终止一个线程 //参数1:HANDLE 被终止的线程的句柄,为CWinThread指针 // 句柄必须具有 THREAD_TERMINATE 访问权限 //参数2:DWORD 线程的退出代码。 使用 GetExitCodeThread 函数检索线程的退出值 //返回值:函数执行成功则返回非零值,执行失败返回0。调用getlasterror获得返回的值 //注意:避免在new和delete之间执行TerminateThread,否则程序将不能在继续使用new了 //TerminateThread强烈不建议使用,因为这个函数的执行是异步的, //你无法保证调用之后会立即退出,同时线程的堆栈均不会被销毁, // 导致内存泄露。如果调用了这个函数,请确保使用WaitForSingleObject来等待线程对象的结束
获取线程退出吗
CWinThread* pp4 = AfxBeginThread(func1, LPVOID(10), 0, 0, 0, NULL); pp4->m_bAutoDelete = FALSE; hand = pp4->m_hThread; DWORD d=-2; Sleep(5000); BOOL b=GetExitCodeThread(hand,&d); //获取线程的退出吗 //参数1:HANDLE 线程句柄 // 句柄必须具有 THREAD_QUERY_INFORMATION 或 THREAD_QUERY_LIMITED_INFORMATION 访问权限 //参数2:LPDWORD 用于保存接收线程终止状态的变量的指针 // d=259 表示线程还在执行没有退出 //返回值:如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError // CString str; str.Format(_T("d=%d"),d); ::OutputDebugString(str); BOOL bbb=CloseHandle(hand);//释放线程句柄 //参数:HANDLE 线程句柄 //返回值:如果该函数成功,则返回值为非零值。如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError
界面线程
区别:工作线程没有消息循环,界面线程有消息循环
模态対话框本身有消息循环,但是在工作线程中也不建议使用,容易出现问题;模态対话框处于阻塞状态,下面的代码无法继续执行
如果在工作线程中创建非模态対话框,会出现两个问题:1.线程结束,非模态対话框也结束,2.由于工作线程没有消息循环,非模态対话框不会响应鼠标、键盘等的操作
但是有人会说:在工作线程中加上消息循环,就解决了非模态対话框问题。这就是界面线程的功能了,默认MFC类型的对话框工程、文档视图类工程等都是使用的MFC界面线程
下面代码就是在工作线程中加的消息循环
MSG msg = {0}; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
1.创建界面资源
创建方法看:https://www.cnblogs.com/liming19680104/p/16909389.html
2.给新界面添加类
类名:CmyUI
3.从 CWinThread 类派生自己的子类:CUIThread
4.在CUIThread.cpp中导入新建界面的头文件
5.在工程cpp中添加 CUIThread.h
6.在线程函数中创建界面
BOOL CUIThread::InitInstance() { CmyUI* pDlg = new CmyUI(); pDlg->Create(IDD_DIALOG1); pDlg->ShowWindow(SW_SHOW); return TRUE; //返回FALSE马上执行CUIThread::ExitInstance函数和析构函数CUIThread::~CUIThread //返回TRUE不退出线程处于消息循环状态,收到WM_QUIT消息时(关闭消息循环)才执行CUIThread::ExitInstance函数和析构函数CUIThread::~CUIThread }
7.在新界面中添加销毁消息和发送WM_QUIT消息
目的:窗口销毁时,终止线程
void CmyUI::OnDestroy() { CDialogEx::OnDestroy(); // TODO: 在此处添加消息处理程序代码 PostQuitMessage(0); //线程终止请求 //该函数向系统表明有个线程有终止请求 //PostQuitMessage寄送一个WM_QUIT消息给线程的消息队列并立即返回; // 此函数向系统表明有个线程请求在随后的某一时间终止 }
8.创建界面线程
CWinThread* p=AfxBeginThread(RUNTIME_CLASS(CUIThread));//创建界面线程 /* 参数1:CUIThread新添加的子线程类名 参数2:线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级 参数3:新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈(1M) 参数4:CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread 0 : 创建线程后就开始运行 参数5:指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性. 如果为 NULL,那么新创建的线程就具有和主线程一样的安全性 返回值: 成功时返回一个指向新线程的线程对象的指针【指向新线程的this】,否则NULL */
以上实例工程下载:
链接:https://pan.baidu.com/s/11iW7gYCB0n6p2xH0IUFwHA 提取码:6666
以上实例工程下载:链接:https://pan.baidu.com/s/1Pz434_CR3ZpSL4C5cRe0GQ 提取码:6666
链接:https://pan.baidu.com/s/1824caRXgNLFS33EElbjctw 提取码:6666
方式三:_beginthreadex
一般在c、c++ 环境中使用
工作线程间通信
方法一:全局变量
注意:在同一进程
int g = 0; //全局变量 UINT funcw(LPVOID pParam) { int n = int(pParam); for (int i=0;i<n;i++) { g++; Sleep(1000); } return 100; } UINT funcr(LPVOID pParam) { int n = int(pParam); CString str; for (int i = 0; i < n; i++) { str.Format(_T("g=%d"),g); ::OutputDebugString(str); Sleep(1000); } return 200; } void Cduoxiancen2Dlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 CWinThread* pp = AfxBeginThread(funcw, LPVOID(1000), 0, 0, 0, NULL); CWinThread* pp1 = AfxBeginThread(funcr, LPVOID(1000), 0, 0, 0, NULL); }
方法二:通过主对话框指针修改和读取属性变量
UINT funcw(LPVOID pParam) { Cduoxiancen2Dlg* p = (Cduoxiancen2Dlg*)pParam; for (int i=0;i<1000;i++) { p->gg = p->gg++; //通过主对话框指针修改值 Sleep(1000); } return 100; } UINT funcr(LPVOID pParam) { Cduoxiancen2Dlg* p = (Cduoxiancen2Dlg*)pParam; CString str; for (int i = 0; i <1000; i++) { str.Format(_T("gg=%d\r\n"),p->gg); ::OutputDebugString(str); Sleep(1000); } return 200; } void Cduoxiancen2Dlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 gg = 10; CWinThread* pp = AfxBeginThread(funcw, this); //传递主对话框指针 CWinThread* pp1 = AfxBeginThread(funcr, this); }
方法三:发自定义消息方式PostThreadMessage
#define WM_MyMessage (WM_USER+100) //自定义消息号 UINT funcw(LPVOID pParam) //写线程 { DWORD ID = (DWORD)pParam; //获取读线程ID int n = 0; for (int i=0;i<1000;i++) { n = n + 2; BOOL b=PostThreadMessage(ID, WM_MyMessage,n,NULL); //将一个队列消息放入(寄送)到指定线程的消息队列里 /* PostThreadMessage可以用于线程之间的异步通讯,不等待线程处理消息就返回 参数1:DWORD 目标线程ID 参数2:UINT消息号(消息的类型) 参数3:WPARAM 指定附加的消息特定信息 参数4: LPARAM 指定附加的消息特定信息 返回值:如果函数调用成功,返回非零值。如果函数调用失败,返回值是零。 若想获得更多的错误信息,请调用GetLastError函数。 如果idThread不是一个有效的线程标识符或由idThread确定的线程没有消息队列,GetLastError返回ERROR_INVALID_THREAD_ID */ Sleep(1000); } return 100; } UINT funcr(LPVOID pParam) { //从消息队列里获取写线程发送过来的消息 MSG msg = { 0 }; while (GetMessage(&msg, NULL, 0, 0)) { switch (msg.message) { case WM_MyMessage: //根据消息号进行判断 int n = (int)msg.wParam; CString str; str.Format(_T("n=%d\r\n"),n); ::OutputDebugString(str); break; } } return 200; } void Cduoxiancen2Dlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 CWinThread* pp1 = AfxBeginThread(funcr, NULL); //读线程 pp1->m_nThreadID;//当前线程的 ID CWinThread* pp = AfxBeginThread(funcw, (LPVOID)pp1->m_nThreadID);//写线程 //传递的参数是读线程的ID //功能:写线程的变量值发生变化后,就发送消息告诉读线程 }
界面线程与主线程的通信
方法一:通过界面线程的返回值
在主线程的CPP中:
CWinThread* p=AfxBeginThread(RUNTIME_CLASS(CUIThread));//创建界面线程 /* 参数4:设置挂起,目的为了给新线程中的变量num赋值 */ ((CUIThread*)p)->num = 123; //p指向新线程的this //这一步就是主线程修改了子线程中的成员变量值
实例工程下载:
链接:https://pan.baidu.com/s/1Ji6aD8duln_lUtCXqZkJww 提取码:6666
方法二:AfxGetApp利用进程指针
线程同步
原子性加减函数
原子性函数:能够保证在一个线程访问变量时其它线程不能访问
for (int i=0;i<100;i++) { InterlockedIncrement64(&g_num);//对参数变量进行原子性加1 /* 如果是64位机子用InterlockedIncrement64 如果是32位机子用InterlockedIncrement 参数:LPLONG64 指向要递增的变量的指针 返回值:LONG64 返回加1之后的值 【个人:其它线程处于等待状态】 */ InterlockedDecrement64(&g_num);//对参数变量进行原子性减1
for (int i=0;i<100;i++) { InterlockedExchangeAdd(&g_num,10);//对参数变量进行原子性加指定值 /* 用于对一个32位数值执行加法的原子操作 参数1:LONG 指向变量的指针 参数2:LONG 要加的值,可以是负数 返回值:返回加之后的值 */ InterlockedExchangeAdd(&g_num,-10); }
LONG L=InterlockedExchange(&g_num,10); //参数2的值替换参数1的值 /* 参数1:LPLONG 参数2:LONG 返回值:函数返回值为原始值 */
LONG g_num = 0; UINT func1(LPVOID pParam) //线程函数 { for (int i=0;i<100;i++) { PVOID pp=InterlockedExchangePointer((PVOID*)&g_num, (PVOID)100);//用第二个参数的值取代第一个参数指向的值 /* 参数1:PVOID * 参数2:PVOID 返回值:PVOID 函数返回值为原始值 */ } return 0; }
关键区域锁
CStringArray suzu; CRITICAL_SECTION g_cs; //关键区域对象 UINT func(LPVOID pParam) //线程函数 { int n = (int)pParam; //关键区域锁、关键代码段锁、临界区域锁 // 阻塞 for (int i = n; i < n + 100;i++) { CString str; str.Format(_T("%d"), i); EnterCriticalSection(&g_cs);//进入关键区域 //当其它线程没有能够进入时,不停的主动尝试进入,直至进入为止。这种主动进入的方式,称为自旋(也叫忙等待) //可以多次进入,必须多次离开 suzu.Add(str); //说明:如果多个线程同时对suzu进行操作,程序就会出错 LeaveCriticalSection(&g_cs);//离开关键区域 } return 0; } void Cduoxiancen3Dlg::OnBnClickedButton1() { InitializeCriticalSection(&g_cs);//初始化关键区域对象 //参数:LPCRITICAL_SECTION 指向关键区域对象的指针,一般是全局变量 for (int i = 0; i < 20;i++) { AfxBeginThread(func, LPVOID(i*10), 0, 0, 0, NULL); } } void Cduoxiancen3Dlg::OnBnClickedButton2() { //查看 int nn=suzu.GetCount(); CString str; str.Format(_T("nn=%d\r\n"), nn); ::OutputDebugString(str); DeleteCriticalSection(&g_cs);//删除关键区域对象 //参数:LPCRITICAL_SECTION }
实例工程下载:
链接:https://pan.baidu.com/s/1IMhHSY9WMa2tTtQyL1JKJQ 提取码:6666
CStringArray suzu; CRITICAL_SECTION g_cs; //关键区域对象 UINT func(LPVOID pParam) //线程函数 { int n = (int)pParam; //关键区域锁、关键代码段锁、临界区域锁 // 阻塞 if (TryEnterCriticalSection(&g_cs)) { //TryEnterCriticalSection发现该资源已经被另一个线程访问,它就返回FALSE //返回FALSE不会处于等待状态,跳过if继续往下执行 for (int i = n; i < n + 100; i++) { CString str; str.Format(_T("%d"), i); suzu.Add(str); //说明:如果多个线程同时对suzu进行操作,程序就会出错 } LeaveCriticalSection(&g_cs);//离开关键区域 } return 0; } void Cduoxiancen4Dlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 suzu.RemoveAll();//从此数组中移除所有元素 InitializeCriticalSection(&g_cs);//初始化关键区域对象 //参数:LPCRITICAL_SECTION 指向关键区域对象的指针,一般是全局变量 for (int i = 0; i < 20; i++) { AfxBeginThread(func, LPVOID(i * 10), 0, 0, 0, NULL); } } void Cduoxiancen4Dlg::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 int nn = suzu.GetCount(); CString str; str.Format(_T("nn=%d\r\n"), nn); ::OutputDebugString(str); DeleteCriticalSection(&g_cs);//删除关键区域对象 }
链接:https://pan.baidu.com/s/1BLVgdaIf18Bw0xwdCot5ZQ 提取码:6666
互斥锁Mutex
支持跨进程使用
同一时间只能一个线程操作资源
HANDLE mutex;//互斥锁句柄 CStringArray suzu; UINT func(LPVOID pParam) //线程函数 { int n = (int)pParam; for (int i = n; i < n + 100; i++) { CString str; str.Format(_T("%d"), i); DWORD d = WaitForSingleObject(mutex, INFINITE);//等待互斥锁处于激活状态或超时间隔已过 /* 单一互斥锁等待 功能:如果互斥锁处于激活状态就返回,并把互斥锁设置成非激活状态,程序继续执行 如果互斥锁处于非激活状态就处于等待状态,等待互斥锁的激活 可以保证只有一个线程进入 参数1:HANDLE 互斥锁句柄 参数2:DWORD 超时间隔(以毫秒为单位) 如果指定了非零值,该函数将等待对象发出信号或间隔 如果参数2为零,则如果对象未发出信号,则函数不会输入等待状态,它始终会立即返回 如果参数2为 INFINITE,则仅当发出对象信号时,该函数才会返回 返回值:如果函数成功,则返回值指示导致函数返回的事件。 可以是下列值之一 WAIT_ABANDONED=0x00000080L 在ReleaseMutex之前线程已经退出 WAIT_OBJECT_0=0x00000000L 指定对象的状态已发出信号 WAIT_TIMEOUT=0x00000102L 超时间隔已过,对象的状态未对齐 WAIT_FAILED=(DWORD) 0xFFFFFFFF 函数失败 说明:返回之后,互斥锁处于非激发状态 */ switch (d) { case WAIT_ABANDONED: break; case WAIT_OBJECT_0: suzu.Add(str); ReleaseMutex(mutex);//释放线程控制权 //让互斥锁处于激活状态 break; } } return 0; } void Cduoxiancen5Dlg::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 mutex = CreateMutex(NULL,FALSE,NULL);//创建互斥锁 /* 参数1:LPSECURITY_ATTRIBUTES 指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄 参数2:BOOL 如果此值为 TRUE ,并且调用方创建了互斥体,则调用线程获取互斥体对象的初始所有权。 否则,调用线程不会获取互斥体的所有权 参数3:LPCTSTR 互斥对象的名字,名称限制为 MAX_PATH 个字符。 名称比较区分大小写 这个名字在跨进程下才有用,本进程没有用 返回值:如果函数成功,则返回值是新创建的互斥体对象的句柄。 如果函数失败,则返回值为 NULL 说明:创建之后,互斥锁处于激发状态 */ for (int i = 0; i < 30; i++) { AfxBeginThread(func, LPVOID(i * 10), 0, 0, 0, NULL); } } void Cduoxiancen5Dlg::OnBnClickedButton2() { int nn = suzu.GetCount(); CString str; str.Format(_T("nn=%d\r\n"), nn); ::OutputDebugString(str); BOOL b=CloseHandle(mutex);//关闭互斥锁 //参数:HANDLE 互斥锁句柄 //返回值:TRUE:执行成功;FALSE:执行失败 }
实例工程下载:
链接:https://pan.baidu.com/s/12q55YBrR4n7CmtKPOYnd-w 提取码:6666
WaitForMultipleObjects 等待多个互斥锁
信号量Semaphore
同一时间多个线程操作多个资源
比如:有一个租车公司只有10台车,可是想租车的司机有50人,所以有10人能租到车(多个线程操作多个资源) ,其余40人处于等待状态
HANDLE ghSemaphore = NULL;//信号量对象句柄 BOOL b; UINT __cdecl ThreadProc(LPVOID lpParameter) { CString strOut; while (TRUE) { DWORD dwWaitResult = WaitForSingleObject(ghSemaphore, 0);//等待信号量 //单一信号量等待,函数返回后,信号量减1 //信号量=0 就处于等待状态 switch (dwWaitResult) { case WAIT_OBJECT_0: //信号量激活返回 strOut.Format(_T("线程%d: 等待成功\r\n"), GetCurrentThreadId()); OutputDebugString(strOut); b=ReleaseSemaphore(ghSemaphore, 1, NULL);//增加激活信号量数 /* 【车用完了,归还车】 参数1:信号量对象句柄 参数2:LONG 所要增加的值,这个值必须大于0, 如果加上这个值导致信号量的当前值大于信号量创建时指定的最大值,那么这个信号量的当前值不变,同时这个函数返回FALSE 参数3:LPLONG 指向返回信号量上次值的变量的指针,如果不需要信号量上次的值,那么这个参数可以设置为NULL 返回值:如果成功返回TRUE,如果失败返回FALSE */ break; case WAIT_TIMEOUT://超时间隔已过,信号量不会减小 strOut.Format(_T("线程%d: 等待失败\r\n"), GetCurrentThreadId()); OutputDebugString(strOut); break; } } return 0; } void Cduoxincen6Dlg::OnBnClickedButton1() { ghSemaphore = CreateSemaphore(NULL, 10, 10, NULL);//创建信号量对象 /* 参数1:指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则子进程无法继承句柄 结构的lpSecurityDescriptor成员为新信号量指定安全描述符。如果此参数为NULL,则信号量将获取默认安全描述符 参数2:Long信号量初始值,必须大于等于0,而且小于等于参数3 【起始能出租多少台车】 参数3:Long设置信号量的最大计数 【租车公司最多有10辆车】 参数4:信号量对象的名称。名称仅限于MAX_PATH个字符。名称比较区分大小写。跨进程时才有用 返回值:Long,如执行成功,返回信号量对象的句柄;零表示出错 */ for (int idx = 1; idx <= 20; ++idx) { AfxBeginThread(ThreadProc, (LPVOID)(idx * 10)); } } void Cduoxincen6Dlg::OnBnClickedButton2() { CloseHandle(ghSemaphore);//关闭信号量 }
实例工程下载:
链接:https://pan.baidu.com/s/10QAQPCvIHrwih4CBhtvSEw 提取码:6666
HANDLE hSM_1;//信号量句柄 HANDLE hSM_2; HANDLE hSM_3; CWinThread* hThread_2; CWinThread* hThread_3; CWinThread* hThread_1;//线程对象的指针 UINT Thread_1(LPVOID param) { for (int i = 0; i < 10; i++) { DWORD dwWait = WaitForSingleObject(hSM_1, INFINITE); //每一个wait过后信号量的数量自动减1,这样就达到了控制同步 ::OutputDebugString(_T("A")); ReleaseSemaphore(hSM_2, 1, NULL); } return 0; } UINT Thread_2(LPVOID param) { for (int i = 0; i < 10; i++) { WaitForSingleObject(hSM_2, INFINITE); ::OutputDebugString(_T("B")); ReleaseSemaphore(hSM_3, 1, NULL); } return 0; } UINT Thread_3(LPVOID param) { for (int i = 0; i < 10; i++) { WaitForSingleObject(hSM_3, INFINITE); ::OutputDebugString(_T("C\r\n")); } return 0; } void Cduoxiancen7Dlg::OnBnClickedButton1() { // 创建三个信号量 hSM_1 = CreateSemaphore(NULL, 1, 1, NULL);//开始为signal状态 hSM_2 = CreateSemaphore(NULL, 0, 1, NULL);//开始为unsignal状态,等待hSM_1释放 hSM_3 = CreateSemaphore(NULL, 0, 1, NULL);//开始为unsignal状态,等待hSM_2 //创建三个线程 hThread_1 = AfxBeginThread(Thread_1,NULL, 0, 0, 0, NULL); hThread_2 = AfxBeginThread(Thread_2, NULL, 0, 0, 0, NULL); hThread_3 = AfxBeginThread(Thread_3, NULL, 0, 0, 0, NULL); //等待三个线程都执行完 WaitForSingleObject(hThread_1hThread->m_hThread, INFINITE);//等待线程处于激活状态 WaitForSingleObject(hThread_2hThread->m_hThread, INFINITE); WaitForSingleObject(hThread_3hThread->m_hThread, INFINITE); //三个线程都执行完 ::OutputDebugString(_T("执行完毕\r\n")); //关闭句柄 CloseHandle(hSM_1); CloseHandle(hSM_2); CloseHandle(hSM_3); }
Event Objects(事件)
Event 方式是最具弹性的同步机制,因为他的状态完全由你去决定,不会像 Mutex 和 Semaphores 的状态会由类似WaitForSingleObject 一类的函数的调用而改变,所以你可以精确的告诉 Event 对象该做什么事?以及什么时候去做!
实例功能:用一个事件控制两个线程,只有第一个线程执行完毕,第二个线程才能执行
CWinThread* hThread1;//线程对象的指针 CWinThread* hThread2; HANDLE g_event = NULL;//事件句柄 UINT func1(LPVOID param) { CString str; DWORD d = WaitForSingleObject(g_event, INFINITE); BOOL b = ResetEvent(g_event);//手动设置事件为无信号 /* 参数:HANDLE 事件句柄 返回值:函数成功,返回非0值,否则返回0值 */ for (int i = 0; i < 10;i++) { str.Format(_T("func1=%d\r\n"),i); ::OutputDebugString(str); Sleep(1000); } b=SetEvent(g_event); //把事件设置为有信号 //参数:HANDLE 事件句柄 return 0; } UINT func2(LPVOID param) { CString str; DWORD d = WaitForSingleObject(g_event, INFINITE); for (int i = 0; i < 10; i++) { str.Format(_T("func2=%d\r\n"), i); ::OutputDebugString(str); Sleep(2000); } CloseHandle(g_event);//关闭事件 return 0; } void Cduoxiancen8Dlg::OnBnClickedButton1() { g_event = ::CreateEvent(NULL, TRUE, TRUE, NULL);//创建事件对象 /* 参数1:一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果是NULL,此句柄不能被继承 参数2:BOOL 指定手动复原还是自动复原。 若设置为TRUE,则表示需要手动将其置为无信号 若为FALSE,则会自动变为无信号【跟互斥锁一样】 参数3:BOOL 指定事件对象的初始状态。 如果为TRUE,初始状态为有信号状态;FALSE为无信号状态 参数4:LPCTSTR 对象名称.是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的 NULL表示无名事件对象 跨进程时才有用 返回值:HANDLE 如果函数调用成功,函数返回事件对象的句柄 如果函数失败,函数返回值为NULL */ hThread1 = AfxBeginThread(func1, NULL, 0, 0, 0, NULL); hThread2 = AfxBeginThread(func2, NULL, 0, 0, 0, NULL); }
实例工程下载:
链接:https://pan.baidu.com/s/1a9lM_MoKAPcNAXpJ9VSTaw
提取码:6666
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
2022-03-14 halcon-gen_rectangle2_contour_xld创建一个矩形XLD轮廓
2022-03-14 halcon-dev_set_line_width设置region,contour输出的线宽
2022-03-14 halcon-draw_region手动画一个闭合区域
2022-03-14 halcon-draw_xld手绘轮廓
2022-03-14 halcon-draw_polygon手绘多边形
2022-03-14 halcon-gen_region_points创建点区域
2022-03-14 halcon-gen_region_line创建直线区域