C++线程与网络接口技术(第二节:线程共享、安全与通知等)

1.线程间共享数据
各线程间可访问全局变量
互斥锁:协调线程,使其步调一致(Mutex)。
g_mutex.lock()//上锁,g_mutex.unlock();//解锁
锁资源由操作系统定义。
互斥锁的使用原则:减少占用时间(尽量缩短对共享数据的访问时间)。
实例:互斥锁

#include <stdio.h>

#include "osapi/osapi.h"

char g_key[16]; // Generator更新它,Checker获取它

class KeyGenerator : public OS_Thread
{
private:
    virtual int Routine()
    {
        int times = 0;
        while(1)
        {
            // 更新key
            for(int i=0; i<16; i++)
            {
                g_key[i] = times;
            }

            times ++;
            if(times >= 128) times = 0;
            OS_Thread::Msleep(50);
        }
        return 0; 
    }
};
class KeyChecker : public OS_Thread
{
private:
    // 线程主函数
    virtual int Routine()
    {
        while(1)
        {       
            // 检查完整性
            for(int i=1; i<16; i++)
            {
                if(g_key[i] != g_key[i-1])
                {                          
                printf("g_key[%d]=%02X,g_key[%d]=%02X\n",
                    i,g_key[i],i-1,g_key[i-1]);
                    PrintKey();
                    printf("不完整!!\n");
                    return 0;
                }
            }

            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;
}

检查线程机制,发现出现错误,出现了读写不一致。
应用锁机制:

#include <stdio.h>
#include "osapi/osapi.h"
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();
                }
            }
            ***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;
}

上述锁机制还可以继续改进:因为拷贝的时间相对较短,因此可以替拷贝加锁,然后解锁。具体代码如下:

#include<stdio.h>
#include "osapi/osapi.h"
OS_Mutex g_mutex;//锁机制使用时间不能太长
char g_key[16];
class KeyGenerator :public OS_Thread
{

private:
    virtual int Routine(){
    int times = 0;
    while(1)
    {
        // 先生成key: 需要80ms
        char key_new[16];           
        for(int i=0; i<16; i++)
        {
            OS_Thread::Msleep(5);
            key_new[i] = times;
        }

        // 更新key: 占有锁的时间非常短
        g_mutex.Lock();
        memcpy(g_key, key_new, 16);
        g_mutex.Unlock();
        times ++;
        if(times >= 128) times = 0;
        printf("写!");
    }
    return 0; 
    }
};
class KeyChecker : public OS_Thread
{
private:
    // 线程主函数
    virtual int Routine()
    {
        while(1)
        {       
            // 尽量缩短对共享数据的访问时间
            char copy[16];
            g_mutex.Lock();
            memcpy(copy, g_key, 16);
            g_mutex.Unlock();

            // 数据处理
            // 检查完整性            
            for(int i=1; i<16; i++)
            {
                if(copy[i] != copy[i-1])
                {
                    printf("不完整!!\n");
                    PrintKey();
                    //return 0;
                }
            }   printf("读!\n");     

            OS_Thread::Msleep(50);//必须加上Sleep函数,否则无法实现调度
        }
        return 0; // 正常退出
    }
    void PrintKey()
    {
        printf("Key: ");
        for(int i=0; i<16; i++)
            printf("%02X ", g_key[i]);                  
        printf("\n");
    }
};
int main(){
    KeyGenerator mytask1;
    mytask1.Run();
    KeyChecker mytask2;
    mytask1.Run();
    getchar();
    return 0;
}

主要就是:
更新时上锁:
g_mutex.Lock();
memcpy(g_key,key_new,16);//将生成的16个放入g_key中。
g_mutex.UnLock();
读时上锁:
g_mutex.Lock();
mecpy(copy,g_key,16);//考入copy数组中
g_mutex.UnLock();
2.线程安全函数—-可重入函数
可重入函数:指一个函数,在多个线程里同时调用(并发调用)的时候,其功能任然正常。(线程安全函数)
定义全局变量 int result;

#include <stdio.h>
#include "osapi/osapi.h"
int result;
int sum(int n){
    result=0;
    for (int i=1;i<=n;i++)
    {
        result+=i;
    }
    return result;
}
class MyTask:public OS_Thread
{
private:
    virtual int Routine()
    {
        while (1)
        {
            int ret=sum(100);
            if (ret !=5050)
            {
                printf("%d\n",ret);
            }

            OS_Thread::Msleep(50);
        }
        return 0;
    }
};
int main(){

    MyTask a;
    a.Run();//启动写线程
    MyTask b;
    b.Run();
    getchar();
    return 0;
}

//出现错误
即全局变量用作为函数参数容易出现错误。
单线程、多线程都可重入时即为可重入函数。
可用互斥锁实现,但最好不要用,耗用系统资源。
3.线程间的通知机制—-信号量机制
生产者–消费者模式:生产者不断往缓存中写东西,消费者不断取走东西。需要保证能够及时取走。
保证能够及时取走的机制:(1)轮询机制(2)信号量机制
(1)轮询机制

#include <stdio.h>
#include "osapi/osapi.h"
#include <time.h>
#include <stdlib.h>
int g_buf[100];//缓存区
OS_Mutex g_mutex;//互斥锁
int g_count=0;
class Producer:public OS_Thread{

public:
    virtual int Routine(){
        while (1)
        {
            int n=rand()%20+1;//生成一个1....20之间的数
            OS_Thread::Msleep(50*n);//50到1000毫秒
            g_mutex.Lock();//上锁
            //存放一个数字代表一个物品 
            g_buf[g_count]=n;
            g_count++;
            printf("放入的物品为:%d\n",n);
            g_mutex.Unlock();//解锁

        }

        return 0;
    } 
};
class Consumer:public OS_Thread
{
public:
    virtual int Routine(){

        while (1)
        {
            OS_Thread::Msleep(500);//每800毫秒取一次
            g_mutex.Lock();//上锁
            if (g_count>0)//g_count小于0就白查了
            {
                for (int i=0;i<g_count;i++)
                {
                    printf("====消费者消费的物品为:%d\n",g_buf[i]);
                }
                g_count=0;
            }
            g_mutex.Unlock();//解锁

        }
        return 0;
    } 

};
int main(){
    srand(time(NULL));//让其产生随机数
    Producer p;
    p.Run();
    Consumer c;
    c.Run();
    getchar();
    return 0;
}

轮询机制的缺点:
1)轮询机制的时间间隔不宜太长,否则缓存区会满;
2)轮询机制的时间不宜太短,否则会百等

(2)信号量机制(semaphore)
及时取,生产一个,消费一个,协调生产和消费。
post()
wait():当信号量大于1时,减1,为0时,自动阻塞
//信号量机制不用加sleep,在消费者中,加sleep代表轮询机制。
具体实现:

#include "osapi/osapi.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
OS_Mutex g_mutex;//锁机制
int g_buf[100];//缓冲区,100个数;
int g_count =0;
OS_Semaphore g_sem(0);//信号量的值初始化为0
// 第一个线程:生产者
class Producer : public OS_Thread
{
public:
    int Routine()
    {
        while(1)
        {

            int r = rand() % 20 + 1; // 生成一个1..20之间的随机数
            OS_Thread::Msleep(50 * r); // 睡觉的间隔50-1000毫秒

            // 存放一个物品(这里就是存一个数, 代表一下物品的意思)
            g_mutex.Lock();
            g_buf[g_count] = r;
            g_count ++;
            printf("放入物品: %d \n", r);
            g_mutex.Unlock();
            g_sem.Post();
        }
        return 0;
    }
};
// 第二个线程:消费者
class Consumer : public OS_Thread
{
public:
    int Routine()
    {
        // 轮询机制:频繁查询当前物品的个数
        while(1)
        {

            //OS_Thread::Msleep(800);
            g_sem.Wait();//检测信号量是否有值,信号量机制自己阻塞;(判断轮询与信号量的方法)
            //wait()重载了超时等待函数,加参数,可以进行超时处理
            g_mutex.Lock();
            if(g_count > 0)
            {
                for(int i=0; i<g_count; i++)
                {
                    printf(" ==== 消费物品: %d \n", g_buf[i]);
                }
                g_count = 0;
            }
            g_mutex.Unlock();
        }
        return 0;
    }
};
int main()
{
    srand(time(NULL));//让其随机生成数
    // 启动第一个线程
    Producer p;
    p.Run();
    // 启动第二个线程
    Consumer c;
    c.Run();
    // 按回车退出程序
    getchar();
    return 0;
}

相比于轮询机制,加了post(),wait(),其余在信号量的消费者中不能用Msleep,因为wait有阻塞机制。post的初始值随初始值而定。
信号量机制:只是相对及时。

4.OSAPI的使用
windows api也可以实现线程编程:

#include <stdio.h>
#include<process.h>
#include <windows.h>
#include <string.h>
//使用windows api实现线程
unsigned int WINAPI OS_Thread(void *param){
    //定义回调函数,必须加上WinAPI
    for (int i=0;i<10;i++)
    {
        printf("do my job:%d\n",i);
        Sleep(1000);
    }
    return 0;
}
unsigned int WINAPI OS_Thread1(void *param){
    //定义回调函数,必须加上WinAPI
    for (int i=0;i<10;i++)
    {
        printf("do your job:%d\n",i);
        Sleep(500);
    }
    return 0;
}
int main(){
    unsigned int thraddr;
    HANDLE handle=(HANDLE) _beginthreadex(NULL,
        0,
        OS_Thread,
        //回调函数(按照MSDN的定义来实现线程定义)
        NULL,0,&thraddr);
    HANDLE handle1=(HANDLE) _beginthreadex(NULL,
        0,
        OS_Thread1,
        //回调函数(按照MSDN的定义来实现线程定义)
        NULL,0,&thraddr);
    getchar();
    return 0;
}

但这种方法不可取,很麻烦,可以直接将其封装后,再使用。

  • 0
    点赞
  • 0
    收藏 更改收藏夹
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风之清扬

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值