Linux 高并发服务器实战 - 3 Linux多线程开发

Linux 高并发服务器实战 - 3 Linux多线程开发

线程概述

在这里插入图片描述

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位

线程是轻量级的进程(LWP:Light Weight Process),在Linux 环境下线程的本质仍然是进程

查看指定进程的LWP号:ps -ls pid

线程共享一份全局内存区域 其中包括初始化数据段、未初始化数据段、堆内存段

在这里插入图片描述

在这里插入图片描述

不同线程拥有不同的代码段、拥有不同的栈空间,其余 共享库、堆空间、是共享的

线程没有创建新的虚拟地址空间,因此它内核区里的大部分资源都是共享的,至于用户区里的,有两部分:栈、代码段不共享

信号掩码->阻塞信号集、未决信号集

在这里插入图片描述

在这里插入图片描述

进程fork() 时的代价还是蛮高的,虽然是写时共享,但是一但写时,需要大量操作

可以把一个进程的程序,看成是一个线程的程序。

一般情况下,main函数所在的线程我们称之为主线程(main 线程),其余创建的线程称之为子线程

程序中默认只有一个线程,pthread_create()函数调用,就有了两个线程

注意函数指针的类型是void* 参数类型也是void*

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine) (void *), void *arg);

​ - 功能:创建一个子线程

​ - 参数:

​ - thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。

​ - attr : 设置线程的属性,一般使用默认值,NULL

​ - start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码

​ - arg : 给第三个参数使用,传参

​ - 返回值:

​ 成功:0

​ 失败:返回错误号。这个错误号和之前errno不太一样。

​ 获取错误号的信息: char * strerror(int errnum);

注意线程不是C标准库里的,在编译的时候要用-l 引入第三方库,这里可以直接用 -pthread代替

也可以-l pthread 库真正的名字是 libpthread.so

子线程执行的代码只是回调函数里的代码

只要在主线程中执行了return 0 整个进程就结束了

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

void * callback(void * arg) {
    printf("child thread...\n");
    printf("arg value: %d\n", *(int *)arg);
    return NULL;
}

int main() {

    pthread_t tid;

    int num = 10;

    // 创建一个子线程
    int ret = pthread_create(&tid, NULL, callback, (void *)&num);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    } 

    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    sleep(1);

    return 0;   // exit(0);
}

终止线程

如果主线程执行完了,直接return 0 ,子线程还没来得及执行,进程就已经结束了,这显然不对。

当主线程执行完后,我们应该让主线程退出,pthread_exit(NULL) (在谁里面调用就是让谁退出)。这样进程还会存在,不影响其他线程的执行

在子线程里return NULL 相当于pthread_exit(NULL)

#include <pthread.h>

void pthread_exit(void *retval);

​ 功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程

​ 参数:

​ retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。

pthread_t pthread_self(void);

​ 功能:获取当前的线程的线程ID

int pthread_equal(pthread_t t1, pthread_t t2);

​ 功能:比较两个线程ID是否相等

​ 不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的

​ 是使用结构体去实现的。

所以不能直接用 == 去做比较,如果两个都是结构体,直接比较地址肯定是不对的

#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    return NULL;    // pthread_exit(NULL);
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    printf("main thread exit\n");

    return 0;   // exit(0);
}

连接已终止的线程(得到它返回的那个值,并回收子线程资源)

子进程由父进程手动地用wait回收

对于线程,它不一定要通过父线程回收,它可以由任何线程回收。一般来说是主线程回收子线程

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

​ - 功能:和一个已经终止的线程进行连接

​ 回收子线程的资源

​ 这个函数是阻塞函数,调用一次只能回收一个子线程

​ 一般在主线程中使用

​ - 参数:

​ - thread:需要回收的子线程的ID

​ - retval: 接收子线程退出时的返回值 (注意这是一个二级指针)

​ 如果不需要接收这个返回值,那就用NULL

​ - 返回值:

​ 0 : 成功

​ 非0 : 失败,返回的错误号

子线程结束的时候是有返回值传回去的

*这里也可以 return (void )&value;

int value = 10;
void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    // sleep(3);
    // return NULL; 
    // int value = 10; // 局部变量
    pthread_exit((void *)&value);   // return (void *)&value;
}

注意不要返回局部变量

因为局部变量是放在栈空间里的,当这个线程结束之后,栈空间就释放了,你再去通过地址去取它。它里面已经是随机数了,所以要用全局

// 主线程调用pthread_join()回收子线程的资源
	// 把一级指针的地址传进去,作为参数去接收那个值,再用一级指针的一个*去访问
    int * thread_retval;
    ret = pthread_join(tid, (void **)&thread_retval);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    printf("exit data : %d\n", *thread_retval);

pthread_exit那边返回的是一个一级指针,如果你也传一个一级指针到pthread_join里是没法操作的

//这样你是没法改变data的值的
int data = 20
void change(int data){
    data = 10;
}

//你想要改变,就得加个*
int data = 20
void change(int* data){
    *data = 10;
}
change(&data);
//这样才能改变

//同样的,你想改变一级指针里的值,也需要再用一个* 用二级指针来变
// 把一级指针的地址转成二级指针,那就是二级指针啦
    ret = pthread_join(tid, (void **)&thread_retval);

// 我们用:(void **)&thread_retval 去改变了 thread_retval里的值,然后再用(*thread_retval)去访问这个一级指针
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

int value = 10;

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    // sleep(3);
    // return NULL; 
    // int value = 10; // 局部变量
    pthread_exit((void *)&value);   // return (void *)&value;
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    // 主线程调用pthread_join()回收子线程的资源
    int * thread_retval;
    // 把一级指针的地址转成二级指针,那就是二级指针啦
    ret = pthread_join(tid, (void **)&thread_retval);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    printf("exit data : %d\n", *thread_retval);

    printf("回收子线程资源成功!\n");

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    return 0; 
}

线程的分离

int pthread_detach(pthread_t thread);

​ - 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。

​ 1.不能多次分离,会产生不可预料的行为。

​ 2.不能去连接一个已经分离的线程,会报错。

​ - 参数:需要分离的线程的ID

​ - 返回值:

​ 成功:0

​ 失败:返回错误号

pthread_detach 之后 就不需要 pthread_join再去释放它的资源,它自己就返回给系统了

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

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    return NULL;
}

int main() {

    // 创建一个子线程
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 输出主线程和子线程的id
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    // 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
    ret = pthread_detach(tid);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error2 : %s\n", errstr);
    }

    // 设置分离后,对分离的子线程进行连接 pthread_join()
    // ret = pthread_join(tid, NULL);
    // if(ret != 0) {
    //     char * errstr = strerror(ret);
    //     printf("error3 : %s\n", errstr);
    // }

    pthread_exit(NULL);

    return 0;
}

线程取消

#include <pthread.h>

int pthread_cancel(pthread_t thread);

​ - 功能:取消线程(让线程终止)

​ 取消某个线程,可以终止某个线程的运行,

​ 但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止。

​ 取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换,这个位置称之为取消点。

系统调用的地方是 取消点,因为要切换到内核区

创建出来子线程后,下面又取消了线程,可能运行到一半就结束了

每次运行可能都不一样,遇到取消点取消

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

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    for(int i = 0; i < 5; i++) {
        printf("child : %d\n", i);
    }
    return NULL;
}

int main() {
    
    // 创建一个子线程
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 取消线程
    pthread_cancel(tid);

    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    // 输出主线程和子线程的id
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    
    pthread_exit(NULL);

    return 0;
}

线程属性

设置属性:

pthread_attr_setxxx

PTHREAD_CREATE_DETACHED是设置线程分离

(自己释放资源)

设置线程属性让他分离:

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

获取子线程栈的大小:

// 获取线程的栈的大小
    size_t size;
    pthread_attr_getstacksize(&attr, &size);
    printf("thread stack size : %ld\n", size);
/*
    int pthread_attr_init(pthread_attr_t *attr);
        - 初始化线程属性变量

    int pthread_attr_destroy(pthread_attr_t *attr);
        - 释放线程属性的资源

    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
        - 获取线程分离的状态属性

    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
        - 设置线程分离的状态属性
*/     

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

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    return NULL;
}

int main() {

    // 创建一个线程属性变量
    pthread_attr_t attr;
    // 初始化属性变量
    pthread_attr_init(&attr);

    // 设置属性
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 创建一个子线程
    pthread_t tid;

    int ret = pthread_create(&tid, &attr, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 获取线程的栈的大小
    size_t size;
    pthread_attr_getstacksize(&attr, &size);
    printf("thread stack size : %ld\n", size);

    // 输出主线程和子线程的id
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    // 释放线程属性资源
    pthread_attr_destroy(&attr);

    pthread_exit(NULL);

    return 0;
}

线程同步

多线程和多进程都是实现并发执行!

多线程实现并发案例

查看当前线程id: pthread_self()

3个线程卖100张票

3 个线程,抢CPU并执行,抢到了就执行

在这里插入图片描述

(1)出现0/负数的情况

​ 都是>0的时候进来的,然后分别休眠6000微秒,然后最先醒的是1,它–,变成了0,再醒的又–,成了-1

(2)出现卖同一张票的情况

​ 速度非常快时,上一个线程还没走ticket–,下一个线程就打印了,卖的同一张票

多个线程,对同一个资源同时进行处理,会出现问题

对共享数据的操作,必须一个线程把这段操作的内容全部执行完了,下一个再执行。(原子操作,一个线程执行完了,另一个线程才能再操作)

临界区:对临界资源的操作,刚刚那段代码整个while就是临界区

在这里插入图片描述

只是限制临界区那一块,对整体来说,影响不大

在这里插入图片描述

线程拿不到锁,就先等待在这里

在这里插入图片描述

互斥量的类型 pthread_mutex_t

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

​ - 初始化互斥量

​ - 参数 :

​ - mutex : 需要初始化的互斥量变量

​ - attr : 互斥量相关的属性,NULL

​ - restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。

​ pthread_mutex_t *restrict mutex = xxx;

​ pthread_mutex_t * mutex1 = mutex;

int pthread_mutex_destroy(pthread_mutex_t *mutex);

​ - 释放互斥量的资源

int pthread_mutex_lock(pthread_mutex_t *mutex);

​ - 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待

int pthread_mutex_trylock(pthread_mutex_t *mutex);

​ - 尝试加锁,如果加锁失败,不会阻塞,会直接返回。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

​ - 解锁

在这里插入图片描述

如果把while循环放在加锁和解锁中间,那么这一百张票都是某一个线程卖完的

所以加缩

如果在main函数中创建互斥量,他是局部变量,main()主线程退出之后,局部变量就消失了

所以要放在全局区

在main() pthread_exit(NULL) 后写释放互斥量资源

把锁放到while里

// 卖票
    while(1) {

        // 加锁
        pthread_mutex_lock(&mutex);

        if(tickets > 0) {
            usleep(6000);
            printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
            tickets--;
        }else {
            // 注意这里也要加一个解锁,因为已经要break了,走不到下面的解锁,所以这里先解一下
            // 解锁
            pthread_mutex_unlock(&mutex);
            break;
        }

        // 解锁
        pthread_mutex_unlock(&mutex);
    }
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 全局变量,所有的线程都共享这一份资源。
int tickets = 1000;

// 创建一个互斥量
pthread_mutex_t mutex;

void * sellticket(void * arg) {

    // 卖票
    while(1) {

        // 加锁
        pthread_mutex_lock(&mutex);

        if(tickets > 0) {
            usleep(6000);
            printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
            tickets--;
        }else {
            // 解锁
            pthread_mutex_unlock(&mutex);
            break;
        }

        // 解锁
        pthread_mutex_unlock(&mutex);
    }

    

    return NULL;
}

int main() {

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

    // 创建3个子线程
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);

    // 回收子线程的资源,阻塞
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    pthread_exit(NULL); // 退出主线程

    // 释放互斥量资源
    pthread_mutex_destroy(&mutex);

    return 0;
}

死锁

如果临界区有一千行代码,那么这一千行都在串行,降低了执行效率

所以:拆开临界区加互斥量,提高效率。变成多个临界区和多个互斥量

在这里插入图片描述

这里的重复加锁是说:重复加相同的锁

pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
//整个都堵在这了

多线程抢多锁

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;

void * workA(void * arg) {

    pthread_mutex_lock(&mutex1);
    sleep(1);
    pthread_mutex_lock(&mutex2);

    printf("workA....\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
    return NULL;
}


void * workB(void * arg) {
    pthread_mutex_lock(&mutex2);
    sleep(1);
    pthread_mutex_lock(&mutex1);

    printf("workB....\n");

    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);

    return NULL;
}

int main() {

    // 初始化互斥量
    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);

    // 创建2个子线程
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, workA, NULL);
    pthread_create(&tid2, NULL, workB, NULL);

    // 回收子线程资源
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    // 释放互斥量资源
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);

    return 0;
}

读写锁

多个线程同时读临界资源是没任何问题的

在对数据的读写操作中,更多的是读操作,写操作较少

----> 读写锁

在这里插入图片描述

有三个线程要加读锁,一个线程要加写锁,那么先加写锁,其他读锁阻塞

/*
    读写锁的类型 pthread_rwlock_t
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

    案例:8个线程操作同一个全局变量。
    3个线程不定时写这个全局变量,5个线程不定时的读这个全局变量
*/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 创建一个共享数据
int num = 1;
// pthread_mutex_t mutex;
pthread_rwlock_t rwlock;

void * writeNum(void * arg) {

    while(1) {
        pthread_rwlock_wrlock(&rwlock);
        num++;
        printf("++write, tid : %ld, num : %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }

    return NULL;
}

void * readNum(void * arg) {

    while(1) {
        pthread_rwlock_rdlock(&rwlock);
        printf("===read, tid : %ld, num : %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }

    return NULL;
}

int main() {

   pthread_rwlock_init(&rwlock, NULL);

    // 创建3个写线程,5个读线程
    pthread_t wtids[3], rtids[5];
    for(int i = 0; i < 3; i++) {
        pthread_create(&wtids[i], NULL, writeNum, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_create(&rtids[i], NULL, readNum, NULL);
    }

    // 设置线程分离
    for(int i = 0; i < 3; i++) {
       pthread_detach(wtids[i]);
    }

    for(int i = 0; i < 5; i++) {
         pthread_detach(rtids[i]);
    }

    pthread_exit(NULL);

    pthread_rwlock_destroy(&rwlock);

    return 0;
}

生产者消费者模型

在这里插入图片描述

生产者还没生产出来就消费 会出问题、或者生产的多了,已经超过了容量了

使用互斥量、条件变量、信号量

条件变量

现有的问题:

(1)数据不同步 -> 用互斥量处理

如果想用if来判断是否容器空了,不空的话执行,并释放锁

在这里插入图片描述

那总会有一个拿到锁后释放不了,因为现在head == NULL了,进入了死循环(死锁)

在这里插入图片描述

如果这样,可以解决

但是这样不太好,如果一直没有数据,消费者一直不断地判断是不是有数据,效率太低。

正确的情况应该是当消费者没有可消费了的时候,告诉生产者让他生产、生产者生产好之后再告诉他让他消费

条件变量不是锁:它的作用是某个条件满足时,阻塞线程,某个条件满足时、解除阻塞

真正解决同步问题,是靠锁解决,它只是配合锁的一个工具

pthread_cond_t

条件变量的类型 pthread_cond_t

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

​ - 等待,调用了该函数,线程会阻塞。

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

​ - 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。

int pthread_cond_signal(pthread_cond_t *cond);

​ - 唤醒一个或者多个等待的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

​ - 唤醒所有的等待的线程

当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。

它会先把锁给放了,让生产者拿到,去生产东西,最后生产完了再还回来继续上了锁

pthread_cond_wait(&cond, &mutex);

生产者

void * producer(void * arg) {

    // 不断的创建新的节点,添加到链表中
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        
        // 只要生产了一个,就通知消费者消费
        // 这边一调用,消费者那里就不阻塞了
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

消费者

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        // 判断是否有数据
        if(head != NULL) {
            // 有数据
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // 没有数据,需要等待
            // 当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

整体

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

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;

struct Node{
    int num;
    struct Node *next;
};

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

void * producer(void * arg) {

    // 不断的创建新的节点,添加到链表中
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        
        // 只要生产了一个,就通知消费者消费
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        // 判断是否有数据
        if(head != NULL) {
            // 有数据
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // 没有数据,需要等待
            // 当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    // 创建5个生产者线程,和5个消费者线程
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    pthread_exit(NULL);

    return 0;
}

信号量(信号灯)

类似于条件变量,都是用于阻塞线程的(但是一样地,没有办法多线程数据安全哦)

在这里插入图片描述

生产者一开始是8,是>0的,所以它要去生产 所以-1

消费者csem就会+1,可以消费一个

如果生产者生产了两个,那就csem+2,消费者可以消费2个

如果不给生产者加回来,那生产者到了0的时候,就不能再生产了

生产者的可以理解为容量

信号量的类型 sem_t

int sem_init(sem_t *sem, int pshared, unsigned int value);

​ - 初始化信号量

​ - 参数:

​ - sem : 信号量变量的地址

​ - pshared : 0 用在线程间 ,非0 用在进程间

​ - value : 信号量中的值

int sem_destroy(sem_t *sem);

​ - 释放资源

int sem_wait(sem_t *sem);

​ - 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞

int sem_trywait(sem_t *sem);

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

int sem_post(sem_t *sem);

​ - 对信号量解锁,调用一次对信号量的值+1

int sem_getvalue(sem_t *sem, int *sval);

sem_t psem;

  sem_t csem;

  init(psem, 0, 8);

  init(csem, 0, 0);



  producer() {sem_wait(&psem);
      
      // 锁的操作和同步操作放在这中间sem_post(&csem)

  }



  customer() {sem_wait(&csem);
      
      // 锁的操作和同步操作放在这中间sem_post(&psem)

  }

锁是保证不能让同时操作,信号量/条件变量是保证值要在一个范围内,比如说不能大于最大容量了,也不能小于0了…

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

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建两个信号量
sem_t psem;
sem_t csem;

struct Node{
    int num;
    struct Node *next;
};

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

void * producer(void * arg) {

    // 不断的创建新的节点,添加到链表中
    while(1) {
        sem_wait(&psem);
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        sem_post(&csem);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        head = head->next;
        printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sem_post(&psem);
       
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    sem_init(&psem, 0, 8);
    sem_init(&csem, 0, 0);

    // 创建5个生产者线程,和5个消费者线程
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

条件变量wait、signal函数是在加锁解锁里面

信号量的操作是在加锁解锁外面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值