信号量完成线程间的互斥与同步

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sys/sem.h>

union semun
{
    int   val; // SETVAL
    struct semid_ds *buf;
    unsigned short *array;
};

class Semaphore
{
private:
    const int MAGIC_NUMBER = 'M';
    int sem_id;
    int InitSemaphore(int value)
    {
        union semun semObject;
        semObject.val = value;
        if (semctl(sem_id, 0, SETVAL, semObject) == -1) {
            perror("Init Semaphore Error");
            return -1;
        }
        return 0;       
    }
public:
    Semaphore(const char* pathname, int value)
    {
        key_t key;
        if ((key = ftok(pathname , MAGIC_NUMBER)) == -1) {
            perror("ftok error");
            exit(1);
        }

        if ((sem_id = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666)) == -1) {
            if (errno == EEXIST) {
                if ((sem_id = semget(key, 1, 0666)) == -1) {
                    perror("semget error");
                    exit(1);
                }
            } else {
                perror("semget error, other errno");
                exit(1);
            }
            
        }

        InitSemaphore(value);
    }

    ~Semaphore()
    {
        union semun tmp;
        if (semctl(sem_id, 0, IPC_RMID, tmp) == -1) {
            perror("semctl error");
            exit(1);
        }
    }
    // when semid == 0, wait
    /**
     * short sem_num:要操作的信号量在集合中的索引,从0开始计数。
        short sem_op:进行的操作。可以是正数、负数或零。通常情况下,正数表示释放资源,负数表示请求资源,零表示等待资源。
        short sem_flg:控制操作的标志位。常用的标志有:
        SEM_UNDO:使操作可以被撤销,即在进程意外终止时自动撤销操作。
        IPC_NOWAIT:如果操作无法立即执行,不要阻塞进程,立即返回并报告错误。
        SEM_NOERROR:如果出现错误(如信号量不存在),不要报告错误。
    */
    void Wait()
    {
        struct sembuf buf;
        buf.sem_num = 0; // sequence num;
        buf.sem_op  = -1; // /*P操作*/
        buf.sem_flg = SEM_UNDO;

        if (semop(sem_id, &buf, 1) == -1) {
            perror("Post semop error");
        }
    }
    // semid + 1 to notify Wait operator
    void Notify()
    {
        struct sembuf buf;
        buf.sem_num = 0; // sequence num;
        buf.sem_op  = 1; // /*V操作*/
        buf.sem_flg = SEM_UNDO;

        if (semop(sem_id, &buf, 1) == -1) {
            perror("Post semop error");
        }
    }

};

生产者与消费者模型

#include <iostream>
#include <unistd.h> // sleep
#include <pthread.h>
#include "semaphore.h"
using namespace std;
#define N 5

Semaphore mutex("/", 1);            // 临界区互斥信号量
Semaphore empty("/tmp", N);         // 记录空缓冲区数,初值为N
Semaphore full("/tmp/zone", 0);     // 记录满缓冲区数,初值为0
int buffer[N];                      // 缓冲区,大小为N
int i = 0;
int j = 0;

void *producer(void *arg)
{
    empty.Wait(); // empty减1
    mutex.Wait();

    buffer[i] = 10 + rand() % 90;
    printf("Producer %d write Buffer[%d]: %d\n", arg, i + 1, buffer[i]);
    i = (i + 1) % N;

    mutex.Notify();
    full.Notify(); // full加1
}

void *consumer(void *arg)
{
    full.Wait(); // full减1
    mutex.Wait();

    printf("                               \033[1;31m");
    printf("Consumer %d read Buffer[%d]: %d\n", arg, j + 1, buffer[j]);
    printf("\033[0m");
    j = (j + 1) % N;

    mutex.Notify();
    empty.Notify(); // empty加1
}

int main()
{
    pthread_t id[10];

    // 开10个生产者线程,10个消费者线程
    for (int k = 0; k < 10; ++k)
        pthread_create(&id[k], NULL, producer, (void *)(k + 1));

    for (int k = 0; k < 10; ++k)
        pthread_create(&id[k], NULL, consumer, (void *)(k + 1));
    printf(".........\n");
    while(1) sleep(100);
    return 0;
}

读者与写者的模型

#include <iostream>
#include <unistd.h> // sleep
#include <pthread.h>
#include "semaphore.h"
using namespace std;

int count = 0;            // 记录当前的读者数量
Semaphore mutex("/", 1);  // 用于保护更新count变量时的互斥
Semaphore rw("/home", 1); // 用于保证读者和写者的互斥

void *writer(void *arg)
{
    rw.Wait(); // 互斥访问共享文件

    printf("  Writer %d start writing...\n", arg);
    sleep(1);
    printf("  Writer %d finish writing...\n", arg);

    rw.Notify(); // 释放共享文件
}

void *reader(void *arg)
{
    mutex.Wait();      // 互斥访问count变量
    if (count == 0) // 当第一个读线程读文件时
        rw.Wait();     // 阻止写线程写
    ++count;        // 读者计数器加1
    mutex.Notify();      // 释放count变量

    printf("Reader %d start reading...\n", arg);
    sleep(1);
    printf("Reader %d finish reading...\n", arg);

    mutex.Wait();      // 互斥访问count变量
    --count;        // 读者计数器减1
    if (count == 0) // 当最后一个读线程读完文件
        rw.Notify();     // 允许写线程写
    mutex.Notify();      // 释放count变量
}

int main()
{
    pthread_t id[8]; // 开6个读线程,2个写线程

    pthread_create(&id[0], NULL, reader, (void *)1);
    pthread_create(&id[1], NULL, reader, (void *)2);

    pthread_create(&id[2], NULL, writer, (void *)1);
    pthread_create(&id[3], NULL, writer, (void *)2);

    pthread_create(&id[4], NULL, reader, (void *)3);
    pthread_create(&id[5], NULL, reader, (void *)4);
    sleep(2);
    pthread_create(&id[6], NULL, reader, (void *)5);
    pthread_create(&id[7], NULL, reader, (void *)6);

    sleep(4);
    return 0;
}
哲学家拿筷子问题
#include <iostream>
#include <vector>
#include <unistd.h> // sleep
#include <pthread.h>
#include "semaphore.h"
using namespace std;

vector<semaphore *> chopstick; // 信号量数组
semaphore mutex("/", 1);       // 设置取左右筷子的信号量 <-- 关键

void *P1(void *arg) // 第1个哲学家线程
{
    mutex.Wait();         // 在取筷子前获得互斥量
    chopstick[0]->Wait(); // 取左边筷子
    chopstick[1]->Wait(); // 取右边筷子
    mutex.Notify();         // 释放取筷子的信号量

    printf("Philosopher 1 eat.\n");

    chopstick[0]->Notify(); // 放回左边筷子
    chopstick[1]->Notify(); // 放回右边筷子
}

void *P2(void *arg) // 第2个哲学家线程
{
    mutex.Wait();         // 在取筷子前获得互斥量
    chopstick[1]->Wait(); // 取左边筷子
    chopstick[2]->Wait(); // 取右边筷子
    mutex.Notify();         // 释放取筷子的信号量

    printf("Philosopher 2 eat.\n");

    chopstick[1]->Notify(); // 放回左边筷子
    chopstick[2]->Notify(); // 放回右边筷子
}

void *P3(void *arg) // 第3个哲学家线程
{
    mutex.Wait();         // 在取筷子前获得互斥量
    chopstick[2]->Wait(); // 取左边筷子
    chopstick[3]->Wait(); // 取右边筷子
    mutex.Notify();         // 释放取筷子的信号量

    printf("Philosopher 3 eat.\n");

    chopstick[2]->Notify(); // 放回左边筷子
    chopstick[3]->Notify(); // 放回右边筷子
}

void *P4(void *arg) // 第4个哲学家线程
{
    mutex.Wait();         // 在取筷子前获得互斥量
    chopstick[3]->Wait(); // 取左边筷子
    chopstick[4]->Wait(); // 取右边筷子
    mutex.Notify();         // 释放取筷子的信号量

    printf("Philosopher 4 eat.\n");

    chopstick[3]->Notify(); // 放回左边筷子
    chopstick[4]->Notify(); // 放回右边筷子
}

void *P5(void *arg) // 第5个哲学家线程
{
    mutex.Wait();         // 在取筷子前获得互斥量
    chopstick[4]->Wait(); // 取左边筷子
    chopstick[0]->Wait(); // 取右边筷子
    mutex.Notify();         // 释放取筷子的信号量

    printf("Philosopher 5 eat.\n");

    chopstick[4]->Notify(); // 放回左边筷子
    chopstick[0]->Notify(); // 放回右边筷子
}

int main()
{
    semaphore *sem1 = new semaphore("/home", 1);
    semaphore *sem2 = new semaphore("/home/songlee", 1);
    semaphore *sem3 = new semaphore("/home/songlee/java", 1);
    semaphore *sem4 = new semaphore("/home/songlee/ADT", 1);
    semaphore *sem5 = new semaphore("/home/songlee/Test", 1);
    chopstick.push_back(sem1);
    chopstick.push_back(sem2);
    chopstick.push_back(sem3);
    chopstick.push_back(sem4);
    chopstick.push_back(sem5);

    pthread_t id;

    pthread_create(&id, NULL, P1, NULL);
    pthread_create(&id, NULL, P2, NULL);
    pthread_create(&id, NULL, P3, NULL);
    pthread_create(&id, NULL, P4, NULL);
    pthread_create(&id, NULL, P5, NULL);

    sleep(1);
    delete sem1;
    delete sem2;
    delete sem3;
    delete sem4;
    delete sem5;
    return 0;
}

导言
在多线程和多进程编程中,实现正确的同步是至关重要的。而锁和信号量作为两种常见的同步机制,起着至关重要的作用。本文将深入探讨锁和信号量的原理、用法以及它们之间的异同,并分析它们的优缺点,以帮助读者更好地理解和应用这两种同步机制。

锁的原理与用法
原理:锁是一种互斥机制,用于保护临界区,确保同一时刻只有一个线程能够进入临界区进行操作。
用法:常见的锁包括互斥锁(mutex)、读写锁(read-write lock)等。通过pthread_mutex_lock()和pthread_mutex_unlock()等操作来获取和释放锁,以确保线程安全性。
信号量的原理与用法
原理:信号量是一种计数器,用于控制对共享资源的访问数量。在POSIX线程库中,信号量是通过sem_t类型表示的。
用法:通过sem_wait()和sem_post()等操作来对信号量进行等待和通知,以实现进程间的同步和资源的控制。
锁与信号量的异同
相同之处:
都是用于实现多线程或多进程的同步机制,确保对共享资源的安全访问。
都可以用于保护临界区,防止数据竞争和数据不一致性。
不同之处:
锁通常只允许一个线程进入临界区;而信号量可以允许多个线程同时访问共享资源。
锁更适用于保护临界区,而信号量更适用于控制资源的访问数量和进程间的同步。
锁与信号量的优缺点
锁的优缺点:
优点:简单易用,适用于保护临界区,避免数据竞争。
缺点:可能导致死锁和饥饿,对性能有一定的影响,无法控制资源的访问数量。
信号量的优缺点:
优点:灵活多样,可以控制资源的访问数量,适用于进程间的同步和通信。
缺点:相对复杂,容易出错,可能会引入竞争条件和死锁。
至此我们就能回答题目的问题

为什么有信号量还需要锁
虽然信号量和锁都可以用于实现多线程或多进程的同步,但它们在应用场景和实现机制上有所不同,因此在某些情况下可能需要同时使用信号量和锁来实现更复杂的同步需求。例如,在使用信号量控制资源访问数量的同时,还需要使用锁来保护临界区,以确保对共享资源的安全访问。

但事实上信号量可以实现锁的功能,二元信号量也能做到临界资源互坼,那锁为什么没被淘汰。

在历史上,"锁"的概念比"信号量"的概念出现要早。"锁"最初是由Dijkstra在1965年提出的,他提出了互斥锁(Mutex)的概念,用于解决进程间竞争共享资源的问题。而"信号量"则是在Dijkstra的互斥锁概念之后不久提出的,用于解决进程间的同步问题。

锁仍然被广泛使用的原因是它具有一些信号量无法实现的功能和特性。以下是锁相比信号量的一些优势和功能:

更简单的接口: 锁通常提供了更简单、更直观的接口,使得程序员更容易理解和使用。相比之下,信号量可能需要更多的代码来实现相同的功能,因为它通常具有更多的状态和操作。

更精细的控制: 锁通常提供了更精细的同步控制,例如读写锁(ReadWrite Lock)、条件变量(Condition Variable)等。这些高级的同步原语可以实现更复杂的同步模式,例如读者-写者问题、生产者-消费者问题等,而信号量可能无法提供这样的灵活性和精细度。

更高效的实现: 锁的实现通常比信号量更加高效,因为它只需要维护一个布尔值或整数来表示锁的状态,而信号量可能需要维护更多的状态信息。在某些场景下,使用锁可能会带来更好的性能和效率。

死锁检测和优化: 一些锁的实现(例如互斥锁)可能具有死锁检测和优化的功能,可以帮助程序员及时发现和解决潜在的死锁问题。相比之下,信号量通常没有内置的死锁检测机制,程序员需要自己实现。

结语
在多线程和多进程编程中,选择合适的同步机制是至关重要的。锁和信号量作为两种常见的同步机制,各有优缺点,在不同的场景下有着不同的应用。通过深入理解它们的原理和用法,以及分析它们之间的异同,我们可以更好地应用这两种同步机制,提高程序的并发性能和可靠性。
————————————————

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值