windows下c++ vc2008 线程讲解
作者:互联网
OSAPI设计用于跨平台编程
这套代码既适合于Windows,也适合于Linux
为什么呢?其实吧,这两个实现方式是不一样的,但是实现了一个更高级的抽象的调用方式,这个方式不管是哪个系统下的,调用方式是一样的,
使用条件编译可以实现同时在两种系统下都可以使用
Linux实现的线程是这样的,第一条语句定义了如果没有不是在win32上则失效了,同理windows也是这样实现的
#ifndef _WIN32
可以不用这个osapi,对于windows系统调用系统的api也行,但是不方便
简单实现多线程:这里是创建两个线程和一个主线程以及join等待其他线程
1、#include "osapi/osapi.h" #这个文件由阿发提供,没有人提供我这么办?
#define _CRT_SECURE_NO_WARNINGS /* VS2013,2015需要这一行 */
#include <stdio.h>
#include "osapi/osapi.h" //这个文件由阿发提供,
// 定义一个类
class Buddhist : public OS_Thread //这个类存在于osapi.h中,但是其实没有,在Thread.h中,进一步include
{
private:
virtual int Routine() //重写了这个Routine函数,并且是虚函数,具有多态的功能
{
// 线程体: 执行它的任务
for(int i=0; i<100; i++)
{
printf("ma mi ma mi hong ...\n");
OS_Thread::Sleep(1);
}
return 0;
}
};
class Confucian : public OS_Thread
{
private:
virtual int Routine() //这个函数数线程的主函数,入口函数
{
for(int i=0; i<500; i++)
{
printf("人之初,性本善 ...\n");
OS_Thread::Sleep(1);
}
return 0;
}
};
int main()
{
Buddhist task1; // 这只是创建一个C++里的对象
task1.Run(); //创建并启动线程,这个是操作系统完成的。Run()的内部告诉OS来创建一个线程
Confucian task2;
task2.Run();
//OS_Thread::Join(&task1); //等待task1退出,才能执行主线程。 Join是OS_Thread类的一个虚函数
//
printf("--------- 主线程开始 -------\n");
for(int i=0; i<10; i++)
{
printf("********* \n");
OS_Thread::Sleep(1);
}
getchar();
return 0; //正常退出线程
}
多个线程访问全局变量时候,实现互斥锁,--不能中断对数据的操作
互斥锁,C++里一般称为Mutex, Java里则一般称为Lock
1. 多个线程之间,可以通过全局对象/堆对象来共享数据
2. 当访问共享数据时(有读有写),为了保证数据的完整性,需要使用互斥锁机制
3. 一个使用原则:尽量缩短对lock的占有时间
使用原则:
当一个线程占有锁时,应该尽快地完成对共享数据的访问。因为别的线程还在等待这个锁呢。
互斥锁机制:
“在一个线程获取锁(Locked)之后,另一个线程的Lock操作会一直等待(阻塞),直到该锁被释放(Unlocked)”
#define _CRT_SECURE_NO_WARNINGS /* VS2013,2015需要这一行 */
#include <stdio.h>
#include "osapi/osapi.h"
/*
此程序:问题:一个全局变量,g_key,KeyGenerator这个线程可以改写他的内容,
当写着写着,发生一个可怕的事情,还没写完呢!就切换到另一个线程了。
这个程序告诉我,怎样防止这个事情
//很有可能在i=2的时候就断了,不是我们想要的我希望i从0到16,不能被中断
*/
//互斥锁,C++里一般称为Mutex, Java里则一般称为Lock
OS_Mutex g_mutex; //创建一把锁,这是一个操作系统的方法,自己写不了
char g_key[16]; // Generator更新它,Checker获取它。这是一个全局数据,多个线程可以访问他
class KeyGenerator : public OS_Thread
{
private:
virtual int Routine()
{
int times = 0;
while(1)
{
// 更新key
g_mutex.Lock(); //上锁
for(int i=0; i<16; i++)
{
OS_Thread::Msleep(5);
g_key[i] = times;
}
g_mutex.Unlock();//解锁
times ++;
if(times >= 128) times = 0;
//OS_Thread::Msleep(50);
}
return 0;
}
};
class KeyChecker : public OS_Thread
{
private:
// 线程主函数
virtual int Routine()
{
while(1)
{
// 数据处理
// 检查完整性
g_mutex.Lock();
for(int i=1; i<16; i++)
{
if(g_key[i] != g_key[i-1])
{
printf("不完整!!\n");
PrintKey();
//return 0;
}
}
g_mutex.Unlock();
//OS_Thread::Msleep(50);
}
return 0; // 正常退出
}
void PrintKey()
{
printf("Key: ");
for(int i=0; i<16; i++)
printf("%02X ", g_key[i]);
printf("\n");
}
};
int main()
{
KeyGenerator a;
a.Run();
KeyChecker b;
b.Run();
getchar();
return 0;
}
线程安全--多个线程调用同一个函数时候可能引发这个函数功能失常,
可重入的函数
可重入(reentrant)的函数,又称线程安全(thread
safe)的函数。
是指一个函数,在多个线程里同时调用(并发调用)
的时候,其功能仍然正常。
以下函数很可能是不可重入的:
(1)一个全局函数(写在类体之外的函数)
如果它借助于全局对象来实现,并且有写操作,那
么就是不可重入的。
(2) 一个类的成员函数
它访问并修改了成员变量,那么一般情况下它就是
不可重入的。
如何将不可重入的函数,改为可重入的?
(1) 不借助外部的变量来实现
尽量用本函数内定义的局部变量来实现。
或者在本函数动态创建对象、并在退出前销毁对象
(没有外部依赖,不操作外部变量)
(2) 实在不行的话,加上互斥锁控制
(回到上一讲)
#define _CRT_SECURE_NO_WARNINGS /* VS2013,2015需要这一行 */
#include <stdio.h>
#include "osapi/osapi.h"
int result;
OS_Mutex g_mutex;
// 求和: 1 + 2 + ... + n
int sum(int n) //这个是线程安全的,运行没有任何东西则是安全的
{
g_mutex.Lock();
result = 0; //假设这个是全局变量则变成,线程不安全了,不可重入了,解决方法使用互斥锁
for(int i=1; i<=n; i++)
{
result += i;
}
int r = result;
g_mutex.Unlock();
return r;
}
class MyTask : public OS_Thread
{
public:
virtual int Routine()
{
while(1)
{
int ret = sum(100);
if(ret != 5050)
printf("%d \n", ret);
OS_Thread::Msleep(5);
}
return 0;
}
};
int main()
{
MyTask t;
t.Run();
MyTask t2;
t2.Run();
getchar();
return 0;
}
线程间的通知机制 - 信号量
轮询机制的缺点:
查询不能太频繁(浪费cpu),也不能太不频繁
(缓冲区满)。难以把握。
需要设计一个合理的轮询机制
所以,最好是有一个通知机制:生产者把物品放进
去之后,通知到消费者。消费者在接到通知之后,
再去取物品。
OS_Semaphore g_sem;
第一个线程: Producer
g_sem.Post(); // 通知
第二个线程:Consumer
g_sem.Wait(); // 等待通知
(2) 用信号量实现线程间的通知机制
线程在等待信号量的时候,它是不占cpu的,相当于
被阻塞的状态。
标签:重入,函数,windows,osapi,int,线程,vc2008,include 来源: https://blog.csdn.net/weixin_42053726/article/details/88072587