线程与线程同步心得,以及类的成员函数作为线程启动函数的一些问题

线程与线程同步

关于线程的基础知识讲解,这位博主讲的非常好:编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程
(为啥上传图片时,右下角水印会有一个奇怪的qq号?)
(1)WINDOWS下创建线程的几种方式及其区别
相关链接:CreateThread和_beginthread区别及使用;
三种创建线程方法的区别
总结:其实这几种创建线程的方式都是可以的,只不过少数情况下CreateThread不太安全,为了以防外一可以统一使用_beginthread创建线程;并且CreateThread创建的线程要手动关闭线程与句柄;_beginthread内部会自动调用_endthread与CloseHandle关闭线程与句柄;_beginthreadex内部会自动调用 _endthreadex关闭线程,需要手动关闭句柄。

为什么要线程同步?
创建线程是如果不指定优先级,那么多个线程在执行过程中会在同一时刻去抢占CPU运行资源,这样会导致最后的执行结果是乱序的,达不到我们想要的效果,如下:
在这里插入图片描述

(2)多线程同步的问题
WaitForSingleObject函数讲解:WaitForSingleObject函数用法;
WaitForSingleObjec;(比较简单易懂)
讨论:关于WaitForSingleObject信号量的问题
看了很多博客,多线程访问同一个资源时,以前只知道用信号量/互斥量去同步多个线程,但是WaitForSingleObject貌似可以直接用来等待另一个线程结束。
互斥量与信号量的区别
互斥量同步线程
在这里插入图片描述
可以看到,每次有一个线程调用ThrFun()函数时,一开始就将互斥量挂起,此时互斥量处于无信号状态,别的线程调用ThrFun()函数时就会一直被WaitForSingleObject等待着,直到函数结束时才释放互斥量,此时互斥量有了信号,某一个线程的WaitForSingleObject就会去获取这个信号并让线程函数继续执行,其实也就是利用互斥量对整个资源”上锁”,使用完资源再”解锁”,这样子就不会造成线程执行混乱或是死锁。

信号量同步线程
当WaitForSingleObject函数的第一个参数句柄为信号量时并且信号量初始数量为1时,如果多个线程在执行同一个资源函数时,第一个执行函数的线程一旦不释放信号量,那么WaitForSingleObject是不会在等待超时后自动释放信号量的!这样子其他的线程就会无限等待,造成线程死锁!
在这里插入图片描述
注意:(1)信号量的初始数量和最大数量是CreateSemaphore函数的第二个和第三个参数,可以根据需求自行修改。
(2)如果在上述问题中,不手动释放互斥量,那么在等待超时后系统会自动释放互斥量,这也是信号量与互斥量重要的区别之一!
在这里插入图片描述
③ 当WaitForSingleObject函数的第一个参数句柄为线程句柄时,则表示等待这个线程执行结束,再往后执行,有时也可以达到线程同步的效果:
在这里插入图片描述

可以看到结果也是线程同步了,所以WaitForSingleObject的句柄参数为线程时,用法其实与互斥量句柄是一样的,都是等待WaitForSingleObject来获取可以继续执行的信号。

线程死锁:
一般服务端读到消息,会把消息放到自己的消息队列中,发送消息也是放到自己的消息队列中,如果对消息队列同时去读和去写,也就是对个线程对同一个消息队列操作,那就必须要加锁,在多线程中对公用变量加锁是必须的,这是A线程获得了互斥量,假设它调用了B线程的函数,而B线程刚好也需要互斥量,这时候就会死锁;而不是网上流传的两个互斥量被两个线程一起死锁。

(3) 如何让类的非静态函数作为线程的启动函数
C++的函数大多都是封装成类的内部函数,一般项目的主逻辑函数也是如此,但这又有一个问题:我们都知道主逻辑函数的工作量较大,所以一般都是通过线程去启动主逻辑函数,而线程的启动函数必须是全局函数或者静态函数,如果在类的内部主逻辑函数声明成静态也可以,万一主逻辑函数没有声明成静态呢?那么主逻辑函数就无法被线程启动了吗?下面说说我的一些看法。
①为什么类的非静态函数不能作为线程启动函数,而且类的静态函数不能调用非静态成员?
我的理解是:静态函数在被声明的时候,其实就已经是一个实体了,可以直接去用,它并没有默认this指针,静态函数是一个共享的数据;而非静态成员不是这样,因为在创建类的某个对象之前,这个对象不存在,它的非静态成员当然也不存在,所以类的非静态函数当然就不能作为线程启动函数了,并且非静态成员也不能被静态函数调用。
②线程间接执行类的非静态成员函数
如果主逻辑函数A没有在类的内部被声明成静态,我们又想通过线程去执行它,可以在类的内部声明一个静态函数B,这个静态函数定义一个类的对象指针,并通过线程用this指针给其赋值,就可以通过这个对象指针调用主逻辑函数A,然后线程通过执行静态函数B可以间接调用主逻辑函数A。

//.h文件
#pragma once
#include <Windows.h>
#include <iostream>

class ThreadClass
{
public:
	ThreadClass(void);
	virtual ~ThreadClass(void);

	void MainProFun();//主逻辑函数,不能作为线程启动函数
	static DWORD WINAPI ThreadDo(LPVOID);//静态函数,可以作为线程启动函数
	HANDLE thr;//线程句柄
};
//.cpp文件
#include "StdAfx.h"
#include "ThreadClass.h"

ThreadClass::ThreadClass(void)
{
	thr = CreateThread(NULL, NULL, ThreadDo, this, NULL, NULL);
}

ThreadClass::~ThreadClass(void)
{
	CloseHandle(thr);
}

void ThreadClass::MainProFun()
{
	for(int i = 1; i <= 10; i++)
	{
		std::cout << "主逻辑函数\n";
		Sleep(1000);
	}
}

DWORD WINAPI ThreadClass::ThreadDo(LPVOID para)
{
	ThreadClass *obj = (ThreadClass *)para;

	obj->MainProFun();
	return 0;
}

写静态函数B,一定要避免下面这种错误写法:

DWORD WINAPI ThreadClass::ThreadDo(LPVOID para)
{
	/*ThreadClass *obj = new ThreadClass这种写法是错误的,下面这种写法也是错误示范,
	因为这两种写法相当于在对象内部创建了新的对象,而且我这里是把线程启动放在了构造函数内部,
	创建了新对象后,对象内部的线程函数又创建了新对象,
	然后又创建线程函数...所以会一直卡在构造函数这一步,相当于是无限递归,造成资源浪费
	*/
	ThreadClass obj;

	obj.MainProFun(NULL);
	return 0;
}

上面的错误写法会导致程序执行结果如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值