Linux系统编程 day09 线程同步

1.互斥锁

互斥锁是一种同步机制,用于控制多个线程对共享资源的访问,确保在同一时间只有一个线程可以访问特定的资源或执行特定的操作。如果没有互斥锁,对于多个线程的程序则可能会出现一些未知的问题。比如下面有两个线程,一个是打印“hello world”的线程,一个是打印“HELLO WORLD”的线程。但是不同的是这个词汇是分两次分别打印的,程序代码如下。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <time.h>

// 打印 hello world
void *mythread1(void *args)
{
    while(1)
    {
        printf("hello ");
        sleep(rand() % 3);
        printf("world\n");
        sleep(rand() % 3);
    }
    pthread_exit(NULL);
}

// 打印HELLO WORLD
void *mythread2(void *args)
{
    while(1)
    {
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
    }
    pthread_exit(NULL);
}

int main()
{
    int ret = 0;
    pthread_t thread1, thread2;

    // 初始化随机数种子
    srand(time(NULL));

    ret = pthread_create(&thread1, NULL, mythread1, NULL);
    if(ret != 0)
    {
        printf("pthread_create error: [%s]\n", strerror(ret));
        return -1;
    }

    ret = pthread_create(&thread2, NULL, mythread2, NULL);
    if(ret != 0)
    {
        printf("pthread_create error: [%s]\n", strerror(ret));
        return -1;
    }

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

将上述文件命名为01.pthread_lock.c,使用命令make 01.pthread_lock,则会自动编译01.pthread_lock.c生成01.pthread_lock,运行该程序可以看到如下现象。

在这里插入图片描述

可以发现两个进程打印出来的“hello world”和“HELLO WORLD”并不是连续在一起的,所以需要加互斥锁将打印的代码进行加锁。下面是互斥锁的一些函数。


/**
 * @brief   定义一把互斥锁
 */
pthread_mutex_t mutex_var;

/** 
 * @brief   初始化互斥锁
 * @param   mutex       互斥锁
 * @param   mutexattr   互斥锁属性,传入NULL为默认属性
 * @return  是否初始化成功,初始化成功返回0,初始化失败返回错误码
 * @retval  int
 */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

/** 
 * @brief   互斥锁加锁
 * @param   mutex       互斥锁
 * @return  是否加锁成功,加锁成功返回0,加锁失败会阻塞在这里,发生错误返回错误码
 * @retval  int
 */
int pthread_mutex_lock(pthread_mutex_t *mutex);

/** 
 * @brief   互斥锁尝试加锁
 * @param   mutex       互斥锁
 * @return  是否加锁成功,加锁成功返回0,加锁失败直接返回0,发生错误返回错误码
 * @retval  int
 */
int pthread_mutex_trylock(pthread_mutex_t *mutex);

/** 
 * @brief   互斥锁解锁
 * @param   mutex       互斥锁
 * @return  是否解锁成功,解锁成功返回0,解锁失败返回错误码
 * @retval  int
 */
int pthread_mutex_unlock(pthread_mutex_t *mutex);

/** 
 * @brief   摧毁互斥锁
 * @param   mutex       互斥锁
 * @return  摧毁成功返回0,摧毁失败返回错误码
 * @retval  int
 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);

现在在每个线程运行的函数中加入互斥锁,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <time.h>

// 定义一把互斥锁
pthread_mutex_t mutex;

// 打印 hello world
void *mythread1(void *args)
{
    while(1)
    {
        // 加锁
        pthread_mutex_lock(&mutex);
        printf("hello ");
        sleep(rand() % 3);
        printf("world\n");
        sleep(rand() % 3);
        // 解锁
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}

// 打印HELLO WORLD
void *mythread2(void *args)
{
    while(1)
    {
        // 加锁
        pthread_mutex_lock(&mutex);
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
        // 解锁
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}

int main()
{
    int ret = 0;
    pthread_t thread1, thread2;

    // 初始化随机数种子
    srand(time(NULL));

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    ret = pthread_create(&thread1, NULL, mythread1, NULL);
    if(ret != 0)
    {
        printf("pthread_create error: [%s]\n", strerror(ret));
        return -1;
    }

    ret = pthread_create(&thread2, NULL, mythread2, NULL);
    if(ret != 0)
    {
        printf("pthread_create error: [%s]\n", strerror(ret));
        return -1;
    }

    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 摧毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

编译后运行如下结果:

在这里插入图片描述

可以观察到这次的运行结果并不会出现线程间交叉打印的情况。

2.死锁

死锁是由程序员对互斥锁的使用不当而产生的,并不是操作系统提供的一种机制。死锁的产生主要由两种形式。

第一种是自己锁自己,也就是调用了两次加锁。也就是说线程1加锁了,再没释放锁的时候线程1又申请加锁,此时就会产生死锁,且死锁阻塞到该位置,还有一种情况是线程1加锁了,但是线程1的整个程序里面没有释放锁的相关操作或者根本执行不到释放锁的代码,此时若线程2申请加锁则程序阻塞在此处,若线程1继续申请加锁则阻塞在线程1加锁处。

第二种是相互锁住,线程1拥有A锁请求B锁,线程2拥有B锁请求A锁,这样就相互阻塞住了。

合理的编写程序能够避免死锁,解决司所有一下一些方法:

  • 1.让线程按照一定的顺序去访问共享资源。
  • 2.在访问其它锁的时候先释放自己的锁。
  • 3.调用pthread_mutex_trylock,该函数若加锁不成功会立刻返回,此时就不会造成阻塞死等。

3.读写锁

读写锁也叫共享独占锁,读时共享,写时独占,极大地提高了程序运行的效率。也就是当读写锁以读模式锁住的时候,它是共享的,当读写锁以写模式锁住的时候,是独占的。读写锁适合的场景是读的次数远大于写的情况下的。

读写锁有以下一些特性:

  • 1.读写锁是写模式加锁的时候,在解锁前所有要对该锁加锁的线程都会阻塞。
  • 2.读写锁是读模式加锁的时候,如果线程以读模式加锁则会成功,以写模式加锁会阻塞。
  • 3.读写锁是读模式加锁的时候,如果既有读线程也有写线程,则尝试加锁都会被阻塞。若锁被释放,则写线程会请求锁成功,而读线程会继续阻塞。也就是读写都有的时候优先满足写模式的锁,也就是写比读优先级更高。

下面是读写锁的接口函数:

/**
 * @brief   定义读写锁变量
 */
pthread_rwlock_t rwlock_var;

/**
 * @brief   读写锁初始化
 * 
 *          初始化读写锁 
 * @param   rwlock  读写锁
 * @param   attr    读写锁属性,传入NULL为默认属性
 * @return  初始化成功返回0,反之返回错误码
 * @retval  int
 */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

/**
 * @brief   读写锁加读锁
 * @param   rwlock  读写锁
 * @return  成功加锁返回0,失败则返回错误码,锁被占用则一直阻塞
 * @retval  int
 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

/**
 * @brief   读写锁尝试加读锁
 * @param   rwlock  读写锁
 * @return  成功加锁返回0,失败则返回错误码,锁被占用也会直接返回
 * @retval  int
 */
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

/**
 * @brief   读写锁加写锁
 * @param   rwlock  读写锁
 * @return  成功加锁返回0,失败则返回错误码,锁被占用则一直阻塞
 * @retval  int
 */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

/**
 * @brief   读写锁尝试加写锁
 * @param   rwlock  读写锁
 * @return  成功加锁返回0,失败则返回错误码,锁被占用也会直接返回
 * @retval  int
 */
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

/**
 * @brief   读写锁解锁
 * @param   rwlock  读写锁
 * @return  成功解锁返回0,失败则返回错误码
 * @retval  int
 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

/**
 * @brief   摧毁读写锁
 * @param   rwlock  读写锁
 * @return  成功摧毁返回0,失败则返回错误码
 * @retval  int
 */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

下面举一个关于读写锁的例子,有3个写线程,5个读线程,写线程修改数字,读线程读取数字输出到终端。代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <pthread.h>

// 线程总数
#define     THREAD_COUNT    8

// 定义读写锁
pthread_rwlock_t rwlock;

// 计数
int number = 0;

// 读线程回调函数
void *thread_read(void *args)
{
    int i = *(int *)args;
    int cur;

    while(1)
    {
        // 读写锁加读锁
        pthread_rwlock_rdlock(&rwlock);
        cur = number;
        printf("[%d]-R: [%d]\n", i, cur);
        // 读写锁加写锁
        pthread_rwlock_unlock(&rwlock);
        sleep(rand() % 3);
    }
}

// 写线程回调函数
void *thread_write(void *args)
{
    int i = *(int *)args;
    int cur;
    
    while(1)
    {
        // 读写锁加写锁
        pthread_rwlock_wrlock(&rwlock);
        cur = number;
        cur += 1;
        number = cur;
        printf("[%d]-W: [%d]\n", i, cur);
        // 读写锁解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(rand() % 3);
    }
}


int main()
{
    int ret = 0;
    int arr[THREAD_COUNT];  // 记录第几个线程
    pthread_t threads[THREAD_COUNT];    // 线程数组

    // 初始化随机数种子
    srand(time(NULL));
    
    // 初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);

    // 创建3个写线程
    for(int i = 0; i < 3; i++)
    {
        arr[i] = i;
        ret = pthread_create(&threads[i], NULL, thread_write, &arr[i]);
        if(ret != 0)
        {
            printf("pthread_create error: [%s]\n", strerror(ret));
            return -1;
        }
    }

    // 创建5个读线程
    for(int i = 3; i < THREAD_COUNT; i++)
    {
        arr[i] = i;
        ret = pthread_create(&threads[i], NULL, thread_read, &arr[i]);
        if(ret != 0)
        {
            printf("pthread_create error: [%s]\n", strerror(ret));
            return -1;
        }
    }

    // 回收子线程
    for(int i = 0; i < THREAD_COUNT; i++)
    {
        pthread_join(threads[i], NULL);
    }

    // 释放锁
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

编译程序运行的结果如下:

在这里插入图片描述

可以发现读线程输出的数字都是写线程修改后的,并不会出现读线程的数据是不符合写进程修改后的。如果没有加读写锁,则会让读写乱套,当写线程修改完数据之后读线程也可能正好输出修改前的值。

4.条件变量(生产者消费者模型)

条件变量主要是用于生产者消费者模型的。所谓生产者消费者模型就是生产者负责生成东西,而消费者负责对生产者生产者的东西用于消费。在这种情况下,生产者消费的东西其实也就是临界资源,所以需要使用互斥锁进行访问。而在生产者消费者模型中无法确定是生产者先访问还是消费者先访问,所以在生产者没有生产东西的时候消费者也可能需要进行消费,或者生产者生产的进度会更不上消费者消耗的进度。因此,为了保证消费者每次消费都是在生产者生成东西之后或者生产的东西有剩余,所以需要使用到条件变量,此时原来并行的生产者消费者就变成了串行的生产者消费者。关于条件变量的接口如下:

/**
 * @brief   条件变量定义,定义一个条件变量
 */
pthread_cond_t cond_var;

/**
 * @brief   初始化条件变量
 * @param   cond        条件变量
 * @param   cond_attr   条件变量属性,传入NULL为默认属性
 * @return  初始化成功返回0,失败返回错误码
 * @retval  int
 */ 
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

/**
 * @brief   唤醒至少一个阻塞在该条件变量上的线程
 * @param   cond    条件变量
 * @return  唤醒成功返回0,失败返回0
 * @retval  int
 */ 
int pthread_cond_signal(pthread_cond_t *cond);

/**
 * @brief   唤醒所有阻塞在该条件变量上的线程
 * @param   cond    条件变量
 * @return  唤醒成功返回0,失败返回0
 * @retval  int
 */ 
int pthread_cond_broadcast(pthread_cond_t *cond);

/**
 * @brief   阻塞等待条件变量满足
 * 
 *          当条件变量不满足的时候,会阻塞线程并进行解锁
 *          当条件变量满足的时候,会解除线程阻塞并加锁
 * @param   cond    条件变量
 * @param   mutex   互斥锁
 * @return  成功返回0,失败返回0
 * @retval  int
 */ 
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

/**
 * @brief   销毁条件变量
 * @param   cond    条件变量
 * @return  销毁成功返回0,失败返回0
 * @retval  int
 */ 
int pthread_cond_destroy(pthread_cond_t *cond);

需要注意的是条件变量本身并不是锁,但是它可以造成线程的阻塞,所以通常需要与互斥锁配合使用。使用互斥锁是为了保护共享数据,使用条件变量可以使线程阻塞,等待某个条件的发生,当条件满足的时候解除阻塞。

下面给一个关于生产者消费者的例子,有一个链表,生产者负责不断地从堆区申请空间开辟内存并且存入数据并将节点放到链表上,而消费者负责在链表上不断地读取数据并且释放节点,代码如下所示。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>

// 链表节点定义
typedef struct node
{
    int data;
    struct node *next;
} node_t;

// 互斥锁
pthread_mutex_t mutex;
// 条件变量
pthread_cond_t cond;
// 头结点
node_t *head = NULL;

// 生产者线程
void *thread_product(void *args)
{
    while(1)
    {
        // 申请节点
        node_t *pnode = (node_t *)malloc(sizeof(node_t));
        if(pnode == NULL)
        {
            perror("malloc error");
            exit(-1);
        }
        pnode->data = rand() % 1000;
        
        // 加互斥锁
        pthread_mutex_lock(&mutex);
        pnode->next = head;
        head = pnode;
        printf("[Productor]: %d\n", pnode->data);
        // 解锁
        pthread_mutex_unlock(&mutex);
        // 通知消费者线程
        pthread_cond_signal(&cond);
        sleep(rand() % 3);
    }

    pthread_exit(NULL);
}

// 消费者线程
void *thread_consume(void *args)
{
    while(1)
    {
        // 加互斥锁
        pthread_mutex_lock(&mutex);
        if(head == NULL)
        {
            // 条件满足解除阻塞并加锁
            // 条件不满足阻塞并解锁
            pthread_cond_wait(&cond, &mutex);
        }
        node_t *pnode = head;
        head = pnode->next;
        printf("[Consumer]: %d\n", pnode->data);
        free(pnode);
        pnode = NULL;
        // 解锁
        pthread_mutex_unlock(&mutex);
        sleep(rand() % 3);
    }
    pthread_exit(NULL);
}

int main()
{
    int ret;
    // 生产者与消费者线程
    pthread_t thread_productor, thread_consumer;

    // 初始化随机数种子
    srand(time(NULL));

    // 初始化条件变量与互斥锁
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    ret = pthread_create(&thread_productor, NULL, thread_product, NULL);
    if(ret != 0)
    {
        printf("pthread_create error: [%s]\n", strerror(ret));
        return -1;
    }

    ret = pthread_create(&thread_consumer, NULL, thread_consume, NULL);
    if(ret != 0)
    {
        printf("pthread_create error: [%s]\n", strerror(ret));
        return -1;
    }

    // 回收线程
    pthread_join(thread_productor, NULL);
    pthread_join(thread_consumer, NULL);

    // 销毁互斥锁与条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

运行可以发现消费者消费的是生产者最后一次生产出来的。

在这里插入图片描述
如果将上述的代码改为多个消费者线程和多个生产者线程的时候,则上述的代码就会出现Segmentation fault (core dumped)的情况。首先需要分析这个问题是如何产生的。这个问题的原因就是访问了非法的空间内存,而这种情况只会在消费者线程中出现。而经过定位可以发现对NULL指针进行了操作,也就是head = pnode->next; printf("[Consumer]: %d\n", pnode->data);。首先在生产者线程中调用了pthread_cond_signal函数的时候会通知一个或者多个消费者线程唤醒满足,而此时这几个线程都不在处于阻塞状态,就会进行解锁,当连续多个消费者线程依次解锁的时候向下运行就会导致访问到非法空间。比如链表中只有一个节点,而此时有三个消费者线程处于阻塞状态且阻塞在pthread_cond_wait处。此时生产者线程调用了pthread_cond_signal则会唤醒这消费者三个线程的一个或者是两个,又或者是三个。此时三个的条件变量都满足,第一个消费者线程抢到了锁消费了一个结点。而此时生产者线程没有创造新的节点,由于第二个线程已经满足了条件变量,此时它抢到了互斥锁,则也会向下执行,由于前面的线程已经消费了唯一的一个线程,所以此时就会访问到非法的内存空间。应对这种情况则是在pthread_cond_wait后面对头结点再一次进行判断,若满足条件则向下执行,不满足条件则解锁并使用continue进入下一次循环。以5个生产者线程10个消费者线程为例,代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <string.h>

// 线程数量
#define     PRODUCER_THREAD_COUNT     5         // 生产线程数量
#define     CONSUMER_THREAD_COUNT     10        // 消费线程数量

// 链表节点
struct node 
{
    int data;
    struct node *next;
};

// 头结点
struct node *head = NULL;

// 互斥锁
pthread_mutex_t mutex;

// 条件变量
pthread_cond_t cond;

void *thread_producer(void *arg)
{
    struct node *pnode = NULL;
    int idx = (int)(long)arg;
    while(1)
    {
        pnode = (struct node *)malloc(sizeof(struct node));
        if(pnode == NULL)
        {
            perror("malloc error");
            exit(-1);
        }
        pnode->data = rand() % 100;

        // 加锁
        pthread_mutex_lock(&mutex);
        pnode->next = head;
        head = pnode;
        printf("P[%d]: %d\n", idx, pnode->data);
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
        sleep(rand() % 3);
    }
    pthread_exit(NULL);
}

void *thread_consumer(void *arg)
{
    struct node *pnode = NULL;
    int idx = (int)(long)arg;
    while(1)
    {   
        pthread_mutex_lock(&mutex);
        if(head == NULL)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        // 若头节点为空
        if(head == NULL)
        {
            // 解锁
            pthread_mutex_unlock(&mutex);
            continue;
        }
        pnode = head;
        head = head->next;
        printf("C[%d]: %d\n", idx, pnode->data);
        pthread_mutex_unlock(&mutex);
        free(pnode);
        pnode = NULL;
        sleep(rand() % 3);
    }
    pthread_exit(NULL);
}



int main()
{
    int ret = 0;
    pthread_t producer_thread[PRODUCER_THREAD_COUNT], consumer_thread[CONSUMER_THREAD_COUNT];

    srand((unsigned int)time(NULL));

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 初始化条件变量
    pthread_cond_init(&cond, NULL);

    // 创建生产者线程
    for(int i = 1; i <= PRODUCER_THREAD_COUNT; i++)
    {
        ret = pthread_create(&producer_thread[i - 1], NULL, thread_producer, (void *)(long)i);
        if(ret != 0)
        {
            printf("pthread_create error: %s\n", strerror(ret));
            exit(-1);
        }
    }

    // 创建消费者线程
    for(int i = 1; i <= CONSUMER_THREAD_COUNT; i++)
    { 
        ret = pthread_create(&consumer_thread[i - 1], NULL, thread_consumer, (void *)(long)i);
        if(ret != 0)
        {
            printf("pthread_create error: %s\n", strerror(ret));
            exit(-1);
        }
    }

    // 回收线程
    for(int i = 0; i < PRODUCER_THREAD_COUNT; i++)
    {
        pthread_join(producer_thread[i], NULL);
    }
    for(int i = 0; i < CONSUMER_THREAD_COUNT; i++)
    {
        pthread_join(consumer_thread[i], NULL);
    }

    // 摧毁互斥锁
    pthread_mutex_destroy(&mutex);

    return 0;
}

运行结果如下:

在这里插入图片描述

5.信号量

信号量可以理解为是升级版的互斥锁,因为互斥锁只能运行一个线程进行访问,而信号量可以支持多个线程或者进程。关于信号量的接口如下所示:

/**
 * @brief   定义信号量
 */
sem_t sem_var;

/**
 * @brief   初始化信号量
 * @param   sem     信号量
 * @param   pshared 0表示线程同步,1表示进程同步
 * @param   value   最多有多少个线程操作共享数据
 * @return  成功返回0,失败返回-1,并设置errno的值。
 * @retval  int
 */
int sem_init(sem_t *sem, int pshared, unsigned int value);

/**
 * @brief   相当于sem--,当sem为0的时候,引起阻塞,即加锁。
 * @param   sem     信号量
 * @return  成功返回0,失败返回-1,并设置error的值
 * @retval  int
 */
int sem_wait(sem_t *sem);

/**
 * @brief   与sem_wait一样,但是不会阻塞
 * @param   sem     信号量
 * @return  成功返回0,失败返回-1,并设置error的值
 * @retval  int
 */
int sem_trywait(sem_t *sem);

/**
 * @brief   相当于sem++
 * @param   sem     信号量
 * @return  成功返回0,失败返回-1,并设置errno的值
 * @retval  int
 */
int sem_post(sem_t *sem);

/**
 * @brief   摧毁信号量
 * @param   sem     信号量
 * @return  成功返回0,失败返回-1,并设置errno的值
 */
int sem_destroy(sem_t *sem);

什么时候用信号量呢?也就是当一个共享资源能够被多个现成进行操作的时候。比如停车场,里面会有很多个车位,可以同时允许多个车位进行停靠,代码如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

// 车位数量
#define     PARKING_COUNT      10

// 线程数量
#define     ENTRY_PARKING_THREAD_COUNT      10          // 进入停车场线程
#define     EXIT_PARKING_THREAD_COUNT       10          // 离开停车场线程

// 结点模拟停车场
struct park 
{
    int use;                    // 是否被使用
    int parking_space;          // 停车位编号
    int license_plate_number;   // 停车车牌号
}park[PARKING_COUNT];

// 信号量
sem_t entry_sem;    // 进入停车场信号量
sem_t exit_sem;     // 离开停车场信号量

// 进入停车场线程
void *thread_entryParking(void *arg)
{
    // 标记是否停车
    int parking_space = -1;
    int license_plate_number = 0;
    while(1)
    {
        // 车位标志
        parking_space = 0;
        license_plate_number = rand() % 10000 + 10000;

        printf("车牌号[%d]驶来了\n", license_plate_number);
        sleep(rand() % 5);

        // 开始停车
        sem_wait(&entry_sem);
        // 寻找车位
        for(int i = 0; i < PARKING_COUNT; i++)
        {
            if(park[i].use == 0)
            {
                parking_space = park[i].parking_space;
                break;
            }
        }

        // 没有找到停车位
        if(parking_space == 0)
        {
            sem_post(&entry_sem);
            printf("车牌号[%d]因为没有车位离开了\n", license_plate_number);
            continue;
        }

        // 开始停车
        park[parking_space - 1].license_plate_number = license_plate_number;
        park[parking_space - 1].use = 1;
        printf("车牌号[%d]停进了停车场的[%d]车位\n", license_plate_number, park[parking_space - 1].parking_space);
        
        // 通知可以有车离开
        sem_post(&exit_sem);
        
    }
    pthread_exit(NULL);
}

// 离开停车场
void *thread_exitParking(void *arg)
{
    // 车位
    int parking_space = 0;
    while(1)
    {
        sleep(rand() % 5);

        // 随机车位
        parking_space = rand() % PARKING_COUNT + 1;

        sem_wait(&exit_sem);

        // 该车位没有车继续下一次
        if(park[parking_space - 1].use == 0)
        {
            sem_post(&exit_sem);
            continue;
        }

        printf("车牌号[%d]离开了停车场的[%d]车位\n", park[parking_space - 1].license_plate_number, park[parking_space - 1].parking_space);
        
        // 标记车位没有使用
        park[parking_space - 1].use = 0;

        // 可停车数加1
        sem_post(&entry_sem);

    }
    pthread_exit(NULL);
}

int main()
{
    int ret = 0;
    // 进入停车场线程
    pthread_t entryParking_thread[ENTRY_PARKING_THREAD_COUNT];
    // 离开停车场线程
    pthread_t exitParking_thread[EXIT_PARKING_THREAD_COUNT];

    srand((unsigned int)time(NULL));

    // 初始化信号量
    sem_init(&entry_sem, 0, PARKING_COUNT);
    sem_init(&exit_sem, 0, 0);

    // 初始化停车场
    memset(&park, 0x00, sizeof park);
    for(int i = 0; i < PARKING_COUNT; i++)
    {
        park[i].parking_space = i + 1;
    }

    // 创建停车线程
    for(int i = 0; i < ENTRY_PARKING_THREAD_COUNT; i++)
    {
        ret = pthread_create(&entryParking_thread[i], NULL, thread_entryParking, NULL);
        if(ret != 0)
        {
            printf("pthread_create error: %s\n", strerror(ret));
            exit(-1);
        }
    }

    // 创建离开停车场线程
    for(int i = 0; i < EXIT_PARKING_THREAD_COUNT; i++)
    {
        ret = pthread_create(&exitParking_thread[i], NULL, thread_exitParking, NULL);
        if(ret != 0)
        {
            printf("pthread_create error: %s\n", strerror(ret));
            exit(-1);
        }
    }

    // 回收线程
    for(int i = 0; i < ENTRY_PARKING_THREAD_COUNT; i++)
    {
        pthread_join(entryParking_thread[i], NULL);
    }
    for(int i = 0; i < EXIT_PARKING_THREAD_COUNT; i++)
    {
        pthread_join(exitParking_thread[i], NULL);
    }

    // 摧毁信号量
    sem_destroy(&entry_sem);
    sem_destroy(&exit_sem);

    return 0;
}

运行上述代码,结果如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值