操作系统实验-线程同步

OS实验一:线程同步

使用Windows提供的API线程接口实现。

参考:C++创建线程示例C++多线程微软多线程编程文档

线程创建与撤销

参数说明

LPVOID 是无类型指针,做形参可接收任意类型的指针

Void ExitThread(DWORD dwExitCode) 在线程函数内执行该线程的撤销,等价于内部的return。

Bool TerminateThread( HANDLE hThread, DWORD dwExitCode) 在线程函数外部强行终止指定线程,并赋予指定结束码。

Sleep(DWORD dwMilliseconds) 挂起当前线程,dwMilliseconds为挂起传入时间,单位为ms;取值为INFINITE则无限延迟,设为0ms则立即交出时间片。

Bool CloseHandle(HANDLE hObject) 关闭句柄

CreateThread() 参数1为安全策略,参数2为初始栈规模,参数3为回调函数(有指定格式),参数4为回调函数的参数(一个结构体),参数5为线程何时开始运行,参数6为获取线程id的指针。
示例:CreateThread(NULL, 0, test, NULL, 0, NULL) 表示安全策略默认;初始栈规模默认;函数使用形如:

DWORD WINAPI test(LPVOID);

的test函数;无传入参数;线程创建后立即执行;不读取线程id。

源码

#include <thread>
#include <stdio.h>
#include <iostream>
#include <windows.h>
using namespace std;

DWORD WINAPI hello(LPVOID){
    cout<<"hello world!\n";
    return 0;
}

DWORD WINAPI name(LPVOID){
    cout<<"My name is ZYX.\n";
    return 0;
}

DWORD WINAPI timely(LPVOID){
    cout<<"Please wait for 5s.\n";
    Sleep(5000);
    cout<<"I wake up!"<<endl;
    ExitThread(0);  // 等价于 return 0;
}
int main(){
    HANDLE threada, threadb, threadc;
    threada = CreateThread(NULL, 0, hello, NULL, 0, NULL);
    threadb = CreateThread(NULL, 0, name, NULL, 0, NULL);
    threadc = CreateThread(NULL, 0, timely, NULL, 0, NULL);
    Sleep(6000);
    CloseHandle(threada);
    CloseHandle(threadb);
    CloseHandle(threadc);
    cout<<"Main thread ends.";
    return 1;
}

线程同步

参考:C++信号量用法

参数说明

CreateSemaphore() 创建信号量并返回一个句柄,该句柄用于表示特定的信号量对象。
参数1表示安全策略,参数2表示基础资源数量,参数3表示最大资源数量,参数4为信号量命名。
同一命名创建的信号量是共享实时资源数量的

OpenSemaphore() 表示打开一类已有的信号量,赋予一个新的句柄一个新的信号量对象。

WaitForSingleObject(Semaphore, INFINITE) 会等待信号量处于信号态(资源数量>0)再继续执行,同时会使目标信号量减一,即再次占用资源。

源码

#include <iostream>
#include <stdio.h>
#include <Windows.h>
using namespace std;

DWORD WINAPI test(LPVOID);
HANDLE myThread, mySemaphore;

int main(){
    CreateSemaphore(NULL, 0, 1, "A");
    mySemaphore = OpenSemaphore(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, NULL, "A");
    myThread = CreateThread(NULL, 0, test, NULL, 0, NULL);
    WaitForSingleObject(mySemaphore, INFINITE);
    CloseHandle(mySemaphore);
    CloseHandle(myThread);
    cout<<"Main thread ends.";
    return 1;
}

DWORD WINAPI test(LPVOID par){
    Sleep(5000);
    cout<<"I am over.\n";
    ReleaseSemaphore(mySemaphore, 1, NULL);
    return 0;
}

线程互斥

参考:C++互斥量-信号量-临界区Windows临界区使用C++临界区-互斥对象-事件对象区别

源码

#include <iostream>
#include <Windows.h>
#include <stdio.h>
using namespace std;

CRITICAL_SECTION cs;    // 临界区

DWORD WINAPI cFunc1(LPVOID);
DWORD WINAPI cFunc2(LPVOID);

int cCnt = 0;

bool CriticalTest(){
    // 初始化临界区
    InitializeCriticalSection(&cs);
    HANDLE h1, h2;
    h1 = CreateThread(NULL, 0, cFunc1, NULL, 0, NULL);
    h2 = CreateThread(NULL, 0, cFunc2, NULL, 0, NULL);
    WaitForSingleObject(h1, INFINITE);
    WaitForSingleObject(h2, INFINITE);
    DeleteCriticalSection(&cs);
    CloseHandle(h1);
    CloseHandle(h2);
    return 1;
}

DWORD WINAPI cFunc1(LPVOID){
    cout<<"func1调用并等待进入临界区\n";
    EnterCriticalSection(&cs);
    cout<<"func1进入临界区\n";
    cCnt += 1;
    printf("count = %d\n", cCnt);
    cout<<"func1退出临界区\n";
    LeaveCriticalSection(&cs);
    return 1;
}

DWORD WINAPI cFunc2(LPVOID){
    cout<<"func2调用并等待进入临界区\n";
    EnterCriticalSection(&cs);
    cout<<"func2进入临界区\n";
    cCnt += 1;
    printf("count = %d\n", cCnt);
    cout<<"func2退出临界区\n";
    LeaveCriticalSection(&cs);
    return 1;
}

HANDLE Mutex;

DWORD WINAPI mFunc1(LPVOID);
DWORD WINAPI mFunc2(LPVOID);

int mCnt = 0;

bool MutexTest(){
    // 初始化临界区
    Mutex = CreateMutex(NULL, 0, "A");

    HANDLE h1, h2;
    h1 = CreateThread(NULL, 0, mFunc1, NULL, 0, NULL);
    h2 = CreateThread(NULL, 0, mFunc2, NULL, 0, NULL);
    
    WaitForSingleObject(h1, INFINITE);
    WaitForSingleObject(h2, INFINITE);
    
    CloseHandle(Mutex);
    CloseHandle(h1);
    CloseHandle(h2);
    return 1;
}

DWORD WINAPI mFunc1(LPVOID){
    cout<<"func1调用并等待互斥信号\n";
    WaitForSingleObject(Mutex, INFINITE);
    cout<<"func1通过互斥信号\n";
    mCnt += 1;
    printf("count = %d\n", mCnt);
    cout<<"func1释放互斥信号\n";
    ReleaseMutex(Mutex);
    return 1;

}

DWORD WINAPI mFunc2(LPVOID){
    cout<<"func2调用并等待互斥信号\n";
    WaitForSingleObject(Mutex, INFINITE);
    cout<<"func2通过互斥信号\n";
    mCnt += 1;
    printf("count = %d\n", mCnt);
    cout<<"func2释放互斥信号\n";
    ReleaseMutex(Mutex);
    return 1;

}

int main(){
    cout<<"---------临界区实现线程同步---------"<<endl;
    CriticalTest();
    cout<<"----------互斥实现线程同步----------"<<endl;
    MutexTest();
    return 1;
}

管道通信

参考:C++的创建命名管道C++进程通信之命名管道C++命名管道详解

参数说明

PeekNamedPipe 函数将数据从命名管道或匿名管道复制到缓冲区中,而不将其从管道中删除。 它还返回有关管道中的数据的信息。

BOOL PeekNamedPipe(
  [in]            HANDLE  hNamedPipe,
  [out, optional] LPVOID  lpBuffer,
  [in]            DWORD   nBufferSize,
  [out, optional] LPDWORD lpBytesRead,
  [out, optional] LPDWORD lpTotalBytesAvail,
  [out, optional] LPDWORD lpBytesLeftThisMessage
);

该函数有特性:

函数始终在单线程应用程序中立即返回,即使管道中没有数据也是如此。 命名管道句柄 (阻塞或非阻塞) 的等待模式对函数没有影响。

这使得它不受等待模式限制,不会被阻塞。

对于 ReadFile() ,其是否等待受命名管道句柄的等待模式影响,但 PIPE_NOWAIT 同时对 ReadFile, WriteFile, ConnectNamedPipe 生效,这会导致连接管道时就不等待,管道的建立提升难度。

源码

服务端:

#include <Windows.h>
#include <iostream>
using namespace std;
#define MAX_BUF 255

HANDLE pipe;
HANDLE rec;

int readFile(HANDLE pipe, char * str, string& fstr){
    bool suc = false;
    DWORD len = 0;
    int cnt = 0;
    PeekNamedPipe(pipe, str, MAX_BUF * sizeof(char), &len, NULL, NULL);
    if(len <= 0){   // 无消息可读
        return 0;
    }
    while(1){
        suc = ReadFile(pipe, str, MAX_BUF * sizeof(char), &len, NULL);    // 每次读取到最多255个字符
        fstr.append(str);   // 接入到字符串
        if(len>0) cnt++;
        if(!suc || len<MAX_BUF){    // 读取失败或者读取完毕(个数小于最大数)
            return cnt;
        }
    }
}

DWORD WINAPI recvCheck(LPVOID){
    while(1){
        Sleep(1000);    // 每隔一秒检测是否有接收到信息
        char buf[MAX_BUF] = "";
        string finalStr = "";
        int flag;
        flag = readFile(pipe, buf, finalStr);
        switch(flag){
            case 0:{
                break;
            }
            default:{
                cout<<"From Client: "<<finalStr<<endl;
            }
        }
    }
    return 1;
}

int main(){
    cout<<"服务端上线\n"<<"创建命名管道并等待连接...\n";
    pipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\zyx"),  // 管道名称
        PIPE_ACCESS_DUPLEX,     // 双向管道
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,  // 信息流且等待
        PIPE_UNLIMITED_INSTANCES,   // 示例数量无上限(255)
        0,      // 输出缓冲区保留0
        0,      // 输入缓冲区保留0
        1000,   // 等待时长无限
        0); // 安全策略默认
    if(pipe == INVALID_HANDLE_VALUE || pipe == NULL){
        cout<<"管道创建失败!\n"<<GetLastError();
        return 1;
    }
    // 等待连接
    if(ConnectNamedPipe(pipe, NULL) != NULL){
        cout<<"连接成功,开始通信!\n";
        rec = CreateThread(NULL, 0, recvCheck, NULL, 0, NULL);
        string inC;
        DWORD dwWrite;
        bool flag;
        while(1){
            getline(cin, inC);  // cin忽略空白字符,因此它实际上接收不到为空的内容,换成getline就可以
            if(inC.length()==0){    // 输入为空
                // 啥也不做
            } else {
                if(inC=="end") break;   // 退出
                else{   // 写入数据并传送
                    flag = WriteFile(pipe, inC.c_str(), inC.length(), NULL, NULL);
                    if(!flag){
                        cout<<"Failed to sent data!\n";
                        return 1;
                    }
                    cout<<"To Client: "<<inC<<endl;
                }
            }
        }
    } else {
        cout<<"管道连接失败!";
    }

    cout<<"已经关闭管道\n";
    DisconnectNamedPipe(pipe);  // 关闭管道连接
    TerminateThread(rec, 0);   // 终止接收检测
    CloseHandle(rec);
    CloseHandle(pipe);
    cout<<"服务端下线\n";
    system("pause");
    return 1;
}

客户端:

// 客户端
#include <Windows.h>
#include <iostream>
#include <conio.h>
using namespace std;
#define MAX_BUF 255
    
HANDLE pipe, rec;

int readFile(HANDLE pipe, char * str, string& fstr){
    bool suc = false;
    DWORD len = 0;
    int cnt = 0;
    PeekNamedPipe(pipe, str, MAX_BUF * sizeof(char), &len, NULL, NULL);
    if(len <= 0){   // 无消息可读
        return 0;
    }
    while(1){
        suc = ReadFile(pipe, str, MAX_BUF * sizeof(char), &len, NULL);    // 每次读取到最多255个字符
        fstr.append(str);   // 接入到字符串
        if(len>0) cnt++;
        if(!suc || len<MAX_BUF){    // 读取失败或者读取完毕(个数小于最大数)
            return cnt;
        }
    }
}

DWORD WINAPI recvCheck(LPVOID){
    while(1){
        Sleep(1000);    // 每隔一秒检测是否有接收到信息
        char buf[MAX_BUF] = "";
        string finalStr = "";
        int flag;
        flag = readFile(pipe, buf, finalStr);
        switch(flag){
            case 0:{
                break;
            }
            default:{
                cout<<"From Server: "<<finalStr<<endl;
            }
        }
    }
    return 1;
}

int main(){
    cout<<"客户端上线\n"<<"按任意键开始连接命名管道...\n";
    _getch();
    cout<<"等待命名管道...\n";
    if(WaitNamedPipe(TEXT("\\\\.\\Pipe\\zyx"), NMPWAIT_WAIT_FOREVER) == FALSE){ // 若命名管道没有打开则直接退出
        cout<<"命名管道未上线";
        return 0;
    }
    cout<<"成功打开命名管道\n";

    // 开始连接命名管道
    pipe = CreateFile(TEXT("\\\\.\\Pipe\\zyx"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(pipe == NULL || pipe == INVALID_HANDLE_VALUE){
        cout<<"管道连接失败!\n"<<GetLastError();
        return 1;
    }
    cout<<"连接成功,开始通信!\n";
    rec = CreateThread(NULL, 0, recvCheck, NULL, 0, NULL);
    string inC;
    DWORD dwWrite;
    while(1){
        char buf[MAX_BUF] = "";
        string finalStr = "";
        int flag;
        getline(cin, inC);
        if(inC.length()==0){    // 尝试接收管道信息
            // 啥也不做
        } else {
            if(inC=="end") break;   // 退出
            else{   // 写入数据并传送
                flag = WriteFile(pipe, inC.c_str(), inC.length(), NULL, NULL);
                if(!flag){
                    cout<<"Failed to sent data!";
                    return 1;
                }
                cout<<"To Server: "<<inC<<endl;
            }
        }
    }

    cout<<"已经关闭管道\n";
    DisconnectNamedPipe(pipe);
    TerminateThread(rec, 0);   // 终止接收检测
    CloseHandle(rec);
    CloseHandle(pipe);
    cout<<"客户端下线\n";
    system("pause");
    return 0;
}

通过创建子线程,定时检测有无收到管道另一端发送的数据,来实现伪实时交互,但实际上退出还是需要两端都输入end,因为一端并不能实时检测管道是否关闭并同步结束另外几个线程(把发送数据写成子线程也许可以)。

实际上管道的关闭体现在发送数据出错。

读者-写者问题

参考:读者0写者问题

概念理解:

  1. 关于读者优先,实验说明书中有如下描述:

    读者优先的附加限制:若一个读者申请进行读操作时已有另一个读者正在进行读操作,则该读者可直接开始读操作。
    
    读者优先指的是除非有写者在写文件,否则读者不需要等待。
    

    后半句附加了一个条件:有读者在等待队列时写者不做写操作,这与前半段的描述实际上是不完全一样的,但后半段概念上与"写者优先"相似,我称之为"绝对优先"思想;个人偏向于前半段的描述是对"读者优先"的准确理解,实际上读写者都在队列中时依然是按照队列顺序进行操作的。

  2. 有一个细节是读者可以多个读者同时读,因此它在"写者优先"下判定优先锁的时候是在启动时间立刻判定,若是有一个读者与写者同时启动,由于计算机内部"同时"可能意味着微小的相差,因此实际上"同时"启动的读者可能抢在写者的前面判定了优先锁,它就打破了"写者优先"的垄断,但这是不可控力导致的不绝对优先,代码逻辑上仍然是锁死了"写者优先"的。

    而写者必须等待上个写者写完才能继续执行写操作,因此在"读者绝对优先"思想中,他判定优先锁理论上是在上一个写者写完(或上一群读者全部读完),而不是在启动时;换言之,重要的是轮到它获取资源时有没有读者在等待,而不是它申请资源时有没有读者在等待,因此判定优先锁的位置和方式不同于读者。

    但是要实现这样的优先就必须要让得到资源的写者在检测到读者时让出资源,导致出现多余的线程切换,因此实现的代码中并没有采用"写者绝对优先思想"。

  3. 最终实现的"读者优先"代码参考的是:申请资源时有无读者在等候。

  4. 因此有同时启动的读者和写者出现的测试案例,实际上是没有意义的。

源码

写者优先:

#include <Windows.h>
#include <iostream>
#include <fstream>
using namespace std;

#define MAX_TRD 255
int trd_count = 0;

struct info{
    int ord;    // 线程号
    int beg;    // 开始时间
    int pause;  // 持续时间
};

int read_count = 0;
int write_count = 0;

HANDLE rec, red, wfr, wte;

DWORD WINAPI write(LPVOID param){   // 写者
    info *pam = (info*)param;
    int beg = pam->beg, pause = pam->pause, ord = pam->ord;
    printf("Writer thread %d is created.\n", ord, beg);
    Sleep(beg); // beg秒后启动
    WaitForSingleObject(wte, INFINITE);
    write_count += 1;
    if(write_count == 1){   // 第一位(等待/执行)的写者占用优先锁
        WaitForSingleObject(wfr, INFINITE);
    }
    ReleaseSemaphore(wte, 1, NULL);
    printf("Writer thread %d applys for the resource.\n", ord);
    WaitForSingleObject(rec, INFINITE); // 申请资源
    printf("Writer thread %d is writing.\n", ord);
    Sleep(pause);   // 操作持续时间
    printf("Writer thread %d ends.\n", ord);
    WaitForSingleObject(wte, INFINITE);
    write_count -= 1;
    if(write_count == 0){   // 最后一位执行的写者释放优先锁
        ReleaseSemaphore(wfr, 1, NULL);
    }
    ReleaseSemaphore(wte, 1, NULL);
    printf("Writer thread %d releases the resource.\n", ord);   
    ReleaseSemaphore(rec, 1, NULL); // 释放资源
    return 1;
}

DWORD WINAPI read(LPVOID param){   // 读者
    info *pam = (info*)param;
    int beg = pam->beg, pause = pam->pause, ord = pam->ord;
    printf("Reader thread %d is created.\n", ord);
    Sleep(beg); // beg秒后启动
    WaitForSingleObject(wfr, INFINITE);   // 有无加入申请资源队列的资格
    WaitForSingleObject(red, INFINITE); // 申请资源
    read_count += 1;    // 写者数量
    if(read_count == 1){    // 第一位读者申请资源
        printf("Reader thread %d applys for the resource.\n", ord);
        WaitForSingleObject(rec, INFINITE); // 申请资源
    }
    ReleaseSemaphore(red, 1, NULL); // 释放读者数量
    ReleaseSemaphore(wfr, 1, NULL); // 不抢占优先锁,只检测是否被占用,若不被占用则有申请资源的资格,因而能算作"队列中的读者",可以计数
    printf("Reader thread %d is reading.\n", ord);
    Sleep(pause);   // 操作持续时间
    printf("Reader thread %d ends.\n", ord);  
    WaitForSingleObject(red, INFINITE); // 申请资源
    read_count -= 1;  
    if(read_count == 0){    // 最后一位读者释放资源
        printf("Reader thread %d releases the resource.\n", ord);
        ReleaseSemaphore(rec, 1, NULL); // 释放资源
    }
    ReleaseSemaphore(red, 1, NULL); // 释放读者数量
    return 1;
}

int main(){
    rec = CreateSemaphore(NULL, 1, 1, TEXT("resource"));
    red = CreateSemaphore(NULL, 1, 1, TEXT("reader"));
    wfr = CreateSemaphore(NULL, 1, 1, TEXT("writerFirst"));
    wte = CreateSemaphore(NULL, 1, 1, TEXT("writer"));
    info *tmp;
    HANDLE trd[MAX_TRD]; 
    
    fstream input;
    input.open("./input.txt", ios::in);
    string str;
    while(!input.eof()){
        string met;
        int ord, beg, pause;
        input>>ord;
        if(input.eof()){
            break;
        }
        input>>met;
        input>>beg;
        input>>pause;
        tmp = new info();
        tmp->ord = ord;
        tmp->beg = beg*1000;
        tmp->pause = pause*1000;
        if(met == "W"){
            trd[trd_count] = CreateThread(NULL, 0, write, tmp, 0, NULL);
            trd_count += 1;
        }
        if(met == "R"){
            trd[trd_count] = CreateThread(NULL, 0, read, tmp, 0, NULL);
            trd_count += 1;
        }
    }

    for(int i = 0; i < trd_count; i++){
        WaitForSingleObject(trd[i], INFINITE);
        CloseHandle(trd[i]);
    }

    CloseHandle(rec);
    CloseHandle(red);
    return 1;

}

读者优先:

#include <Windows.h>
#include <iostream>
#include <fstream>
using namespace std;

#define MAX_TRD 255
int trd_count = 0;

struct info{
    int ord;    // 线程号
    int beg;    // 开始时间
    int pause;  // 持续时间
};

int read_count = 0;

HANDLE rec, red;

DWORD WINAPI write(LPVOID param){   // 写者
    info *pam = (info*)param;
    int beg = pam->beg, pause = pam->pause, ord = pam->ord;
    printf("Writer %d is created.\n", ord, beg);
    Sleep(beg); // beg秒后启动
    printf("Writer %d applys for the resource.\n", ord);
    WaitForSingleObject(rec, INFINITE); // 申请资源
    printf("Writer %d is writing.\n", ord);
    Sleep(pause);   // 操作持续时间
    printf("Writer %d ends and releases the resource.\n", ord);
    ReleaseSemaphore(rec, 1, NULL); // 释放资源
}

DWORD WINAPI read(LPVOID param){   // 读者
    info *pam = (info*)param;
    int beg = pam->beg, pause = pam->pause, ord = pam->ord;
    printf("Reader %d is created.\n", ord);
    Sleep(beg); // beg秒后启动
    WaitForSingleObject(red, INFINITE); // 申请资源
    read_count += 1;    // 写者数量
    if(read_count == 1){    // 第一位读者申请资源
        printf("Reader %d applys for the resource.\n", ord);
        WaitForSingleObject(rec, INFINITE); // 申请资源
    }
    ReleaseSemaphore(red, 1, NULL); // 释放读者数量
    printf("Reader %d is reading.\n", ord);
    Sleep(pause);   // 操作持续时间
    printf("Reader %d ends.\n", ord);  
    WaitForSingleObject(red, INFINITE); // 申请资源
    read_count -= 1;  
    if(read_count == 0){    // 最后一位读者释放资源
        printf("Reader %d releases the resource.\n", ord);
        ReleaseSemaphore(rec, 1, NULL); // 释放资源
    }
    ReleaseSemaphore(red, 1, NULL); // 释放读者数量
}

int main(){
    rec = CreateSemaphore(NULL, 1, 1, TEXT("resource"));
    red = CreateSemaphore(NULL, 1, 1, TEXT("reader"));
    info *tmp;
    HANDLE trd[MAX_TRD]; 
    
    fstream input;
    input.open("./input.txt", ios::in);
    string str;
    while(!input.eof()){
        string met;
        int ord, beg, pause;
        input>>ord;
        if(input.eof()){
            break;
        }
        input>>met;
        input>>beg;
        input>>pause;
        tmp = new info();
        tmp->ord = ord;
        tmp->beg = beg*1000;
        tmp->pause = pause*1000;
        if(met == "W"){
            trd[trd_count] = CreateThread(NULL, 0, write, tmp, 0, NULL);
            trd_count += 1;
        }
        if(met == "R"){
            trd[trd_count] = CreateThread(NULL, 0, read, tmp, 0, NULL);
            trd_count += 1;
        }
    }

    for(int i = 0; i < trd_count; i++){
        WaitForSingleObject(trd[i], INFINITE);
        CloseHandle(trd[i]);
    }

    CloseHandle(rec);
    CloseHandle(red);
    return 1;

}

posted @   Festu  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
历史上的今天:
2022-05-08 2022-r00t-新生赛
点击右上角即可分享
微信分享提示