太久没有写过博客了,还是要坚持下去
最近做过的一个基于Linux的线程控制程序。主要解决了生产者消费者问题读者写着问题

关于Linux线程

windows上有明确的进程和线程划分,而Linux上没有,Windows的进程属于重量级的进程,而Linux的进程属于轻量级的。要在Linux上使用线程,就要用到pthread

生产者消费者问题

本质

生产者消费者问题本质就是两堆线程对同一个缓冲区进行互斥操作,缓冲区空时,消费者不能消费,缓冲区满时,生产者不能生产,就像我们在食堂窗口取餐一样,

所以实现起来也很简单。

生产者和消费者的原理:

#include <iostream>
#include <stdio.h>
#include <pthread.h>
#include <cstdlib>
#include <unistd.h>
#include <semaphore.h>

#define BUFFER_SIZE 10
//为了方便理解PV操作,我们将对应的函数定义为PV
#define P(S) sem_wait(S)
#define V(S) sem_post(S)

//生产者消费者问题
char *buffer;
sem_t *mutex, *empty, *full;
int x, y;
//用作输出缓冲区情况
void output()
{
    for (int i = 0; i < BUFFER_SIZE; i++) {
        printf("%c", buffer[i]);
        printf(" ");
    }
    printf("\n");
}
//生产者
//pthread_self()顾名思义,就是获取线程号,即线程的唯一标识符
void *produce(void *pVoid)
{
    int j = 0;
    do {
        //可以看到,与以上原理是清晰对照的
        P(empty);
        P(mutex);
        printf("%lu%s", pthread_self(), " ——————生产—————— ");
        buffer[(x++) % BUFFER_SIZE] = 'P';
        output();
        j++;
        V(mutex);
        V(full);
    } while (j != 30);
}

void *consume(void *pVoid)
{
    int j;
    j = 0;
    do {
        P(full);
        P(mutex);
        printf("%lu%s", pthread_self(), " ------消费------ ");
        buffer[(y++) % BUFFER_SIZE] = 'C';
        output();
        j++;
        V(mutex);
        V(empty);
    } while (j != 30);
}

void ConAndPro() {
    int i;
    x = 0;
    y = 0;
    //这里由于我们将wait和signal定义以后,传入的参数需要是指针类型的,我们需要在这里为这些指针变量预分配内存空间,否则它们将变为野指针
    buffer = (char *) malloc(BUFFER_SIZE * sizeof(char *));
    mutex = static_cast<sem_t *>(malloc(sizeof(sem_t *)));
    empty = static_cast<sem_t *>(malloc(sizeof(sem_t *)));
    full = static_cast<sem_t *>(malloc(sizeof(sem_t *)));
    //初始化缓存区,N表示空
    for (i = 0; i < BUFFER_SIZE; i++)
    {
        buffer[i] = 'N';
    }
    //semaphore
    sem_init(mutex, 1, 1);
    sem_init(empty, 0, BUFFER_SIZE);
    sem_init(full, 0, 0);
    //multipthread
    pthread_t tid[10];
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    //tid即线程标识符,此处创建线程并初始化
    for (i = 0; i < 5; i++) {
        pthread_create(&tid[i], &attr, consume, NULL);
        pthread_create(&tid[i + 5], &attr, produce, NULL);
    }
/**
使用pthread_join,以使创建的线程在主线程之前执行,这是因为代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
*/
    for (i = 0; i < 10; i++) {
        pthread_join(tid[i], NULL);
    }
}

运行结果:

读者写者问题

思想:
读者写者问题相较于生产者消费者难理解一点。
读者—写者问题是一个经典的并发程序设计问题,是经常出现的一种同步问题。计算机系统中的数据常被多个进程共享,但其中某些进程可能只要求读数据,即读者Reader。另一些进程则要求修改数据,称为写者Writer。就共享数据而言,Reader和Writer是两组并发进程共享一组数据区,并且要求: (1) 允许多个读者同时执行读操作; (2) 不允许读者、写者同时操作; (3) 不允许多个写者同时操作。
我们以写者优先为例
我们先开看运行结果:

当有读者和写者同时等待时,首先满足写者。当新的写者希望写时,不允许该写者后续的读者访问数据区,但必须保证之前的读者读完。很显然没必要每次进入的读者或写者都要进行一整套进入锁定和退出解锁操作,因为期间有可能有其他的同类进入,故只需第一个进入者锁定,最后一个解除即可。使用两个互斥锁mutex_write,mutex_read和两个信号量sem_read,sem_write来确保访问资源的互斥和同步
sem_read,sem_write都是成对出现的,他们的成对存在保护了包裹在中间的变量。 很显然我们没有必要每个写者进入时候都对读线程执行一次加锁操作(pthread_mutex_lock(&write_mutex)),因为读者可以同时进行读操作,后续仍有读者会进来。同样写者虽然不能同时进行写操作,但是他们可以同时进入等待队列,此处为写者优先,故我们可以将写者也进行上述操作,可以先放写者进来,当前边的写者执行完写操作以后,后续的写者可以直接进行写操作,所以让第一个写者进入后对读锁加锁最后一个读者离开时候再进行解锁操作,可以提升程序的效率。

如何实现写者优先的?

  • 对于写者,当第一个写者进入时,对读锁进行加锁,使读者不能进行读操作,当然,若有读者正在读的话,一定要等当前的读者读完以后再进行加锁操作。写者开始写时候,对写锁进行加锁,防止别的写者同时去写。执行完写操作以后先对写锁解锁,然后判断是不是最后一个写者,若是则释放读锁,退出。为了方便看清楚运行过程,便于理解,我将读者写者的进入、读写操作、结束退出的过程都直接标了出来。
void *mywriter(void *arg) {
    printf("写者%d线程进入等待中...\n",pthread_self());
    sem_wait(&sem_write);
    writerCnt++;
    if(writerCnt==1){
        pthread_mutex_lock(&read_mutex);
    }
    sem_post(&sem_write);
    //执行写操作
    pthread_mutex_lock(&write_mutex);
    printf("写者%d开始写文件\n", pthread_self());
    printf("写者%d结束写文件\n", pthread_self());
    printf("---------------------\n");
    pthread_mutex_unlock(&write_mutex);
    //写完以后,写者退出
    sem_wait(&sem_write);
    writerCnt--;
    if (writerCnt==0){
        pthread_mutex_unlock(&read_mutex);
    }
    sem_post(&sem_write);
}

而对于读者,由于这里是写者优先,且读写互斥,所以读者在执行操作之前,先要判断读锁是否已经被锁上,若已经被写者锁上,读者将会进入阻塞,直到写者释放读锁,读者在对读锁进行加锁。同理如果是第一个读者,就对写锁进行加锁,但是每个读者读完都会释放读锁,如果此时写者请求了锁定读锁,那么这个锁会被写者先锁上。但同样你得等他当前在读的读完。

void *myreader(void *arg) {
    printf("编号为%d的读者进入等待中。。。\n", pthread_self());
    pthread_mutex_lock(&read_mutex);
    sem_wait(&sem_read);
    readerCnt++;
    if (readerCnt == 1) {
        pthread_mutex_lock(&write_mutex);
    }
    sem_post(&sem_read);
    printf("编号为%d的读者开始读\n", pthread_self());
    printf("编号为%d的读者读完\n", pthread_self());
    printf("---------------------\n");
    pthread_mutex_unlock(&read_mutex);
    sem_wait(&sem_read);
    readerCnt--;
    if (readerCnt==0){
        pthread_mutex_unlock(&write_mutex);
    }
    sem_post(&sem_read);
    sleep(R_SLEEP);
    pthread_exit((void *) 0);
}
//写者优先执行函数
void writerFirst() {
    readerCnt=0;
    writerCnt=0;
    printf("多线程,写者优先\n");
    pthread_mutex_init(&write_mutex, NULL);
    pthread_mutex_init(&read_mutex, NULL);
    sem_init(&sem_write, 0, 1);
    int i = 0;
    for (i = 0; i < N_READER; i++) {
        pthread_create(&rid[i], NULL, myreader, NULL);
    }
    for (i = 0; i < N_WRITER; i++) {
        pthread_create(&wid[i], NULL, mywriter, NULL);
    }
    sleep(1);
}

完整代码见GitHub