Linux多线程开发(开发重点)

知识点 1 :线程概述

并行与并发的区别:

并发:宏观上看多个任务一起执行(一台咖啡机多人轮流取咖啡)

并行:微观上多个任务一起进行(两台咖啡机两人同时取咖啡) 需要多个核

可以理解为:在Linux中线程就是进程(类似的)

线程也是完成并发的多个任务(可以相同可以不同),一般来说是相同的  轻量级(共享)

进程也是完成并发的多个任务(可以相同可以不同)   重量级(不共享)

ps -Lf pid   可以查看该pid下的所有线程    PID 进程号   LWP   线程号

 知识点 2  :线程和进程的区别 

通过子进程可以fork创建出子进程,会复制父进程的虚拟地址空间,父子进程之间的共享(进程间通信)困难。线程间的信息是可以共享的

 代价较高:复制父进程的虚拟地址空间(时间)

因为线程间是可以共享数据的

因为线程间是共享虚拟地址空间的,也就是共享内存,不需要复制,速度更快

知识点 3 :线程和进程的虚拟地址空间

进程:写时复制,读时共享

线程: 都是共享的,除了栈空间text代码段会分成线程的几个部分,栈空间可以分成主线程和子线程。

知识点 4 :线程之间共享和非共享资源

其实共享的资源就是内核的资源共享

每个线程的ID不同肯定是不共享的

知识点 5 :NPTL(了解即可)

知识点 6 :线程操作API  man pthread

1 查看pthread所有功能函数  man pthread

2 创建线程   pthread_create

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

如果程序中默认一个进程,fork之后会产生一个子进程,那么此时有两个进程

如果程序中默认一个线程,pthread_create之后会产生一个子线程,那么此时有两个线程

第一个参数:线程ID类型的指针 :创建tid的地址

第二个参数:属性:NULL(自己不设置)

第三个参数:逻辑代码:通过函数指针实现 callback回调

callback 返回void*  穿的参数也是void *

第四个参数:不需要传递参数就用 NULL  也可以传递参数(后面有例子)

pthread_create  返回int 类型   

void * 万能指针

错误号  strerror(错误号)返回字符串char * 类型

案例:

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


//子线程执行的代码
void* callback(void *arg)
{
    printf("子线程\n");
    return NULL;
}



int main()
{
    pthread_t tid;

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

    if (ret == 0)
    {
        //创建子线程成功
        printf("子线程创建成功");
    }
    else
    {
        //创建子线程失败并报错返回错误号
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }
    for (int i = 0; i < 5; i++)
    {
       printf(% d\n, i);
    }
    return 0;// 相当于exit(0) 退出进程
}

因为线程他不是系统自带的库,是第三方库,所以报错,应该指定库名pthread   libpthread.so

也可以通过-pthread进行编译和链接

运行结果:
发现子线程里面的内容没有执行:

因为main函数里面所有的内容都是主线程,callback是子线程,只要主线程里面有return 0 一执行就退出了线程,不再执行子线程

解决:return 0 前面加sleep (1);  加unix头文件    后面还有更好的方法 pthread_exit

拓展:如果想要创建多个子线程,可以多次调用pthread_create  callback可以一样也可以内容不同

对于第四个参数传递其他参数的情形:void *类型指针

如果  int num=10    传入参数强转 (void * )&num

 那么这个时候第四个参数num就传递给了callback的void *arg部分,要想获得arg的值,本来是void *类型强转成int * 最后再用*取他的值

3 终止线程   pthread_exit

4 获取子线程ID  pthread_self

案例:看看子线程打印的子线程ID和主线程打印的子线程ID是否相同

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


void* callback(void* arg)
{
    //获取子线程id
    printf("子线程tid: %ld\n",pthread_self());
}

int main()
{
    pthread_t tid;
    //创建子线程
    int ret= pthread_create(&tid, NULL, callback, NULL);
    if (ret == 0)
    {
        //子线程创建成功
        printf("子线程创建成功!\n");
    }
    else
    {
        //子线程创建失败并返回错误号
       char * errstr = strerror(ret);
       printf("error: %s\n", errstr);
    }

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

    //比较主线程中子线程的id和子线程中子线程id是否相同
    printf("子线程id: %ld\n, 主线程的子线程id:%ld\n ", tid,pthread_self());

    //主线程终止
    pthread_exit(NULL);

    return 0;
}

运行结果:

如果i更大一点,循环次数更多一点,就会出现主线程子线程交替运行的情况

若加上

打印不出来,主线程退出之后后面的代码就不再执行了 exit也不再执行,不影响子线程

5 比较线程号是否相等 pthread_equal

yi线程号是长整型,为啥不用==直接判断,而用pthread_equal函数?

答:不同的操作系统,pthread_t类型的实现不同,有的是无符号的长整型(大多),有的是用结构体实现的(那比较的是地址就不可用==),跨平台一定要使用pthread_equal

6 连接已终止的线程  pthread_join

注意:任何的线程都可以去回收已经终止的线程(主线程回收子线程的数据)

连接:回收(如果多个子线程运行完不回收就会出现僵尸线程)

和wait函数一样 阻塞   

多次回收:多次调用join

**retval 

1 二级指针:*retval就已经能指向这个值了,为什么用二级指针?

2 接收子线程退出exit时的返回值

可以传递NULL 也可以是其他值(void*类型)

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


void* callback(void* arg)
{
    //获取子线程id
    printf("子线程tid: %ld\n",pthread_self());
    return(NULL);
}

int main()
{
    pthread_t tid;
    //创建子线程
    int ret= pthread_create(&tid, NULL, callback, NULL);
    if (ret == 0)
    {
        //子线程创建成功
        printf("子线程创建成功!\n");
    }
    else
    {
        //子线程创建失败并返回错误号
       char * errstr = strerror(ret);
       printf("error: %s\n", errstr);
    }

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

    //比较主线程中子线程的id和子线程中子线程id是否相同
    printf("子线程id: %ld\n, 主线程的子线程id:%ld\n ", tid,pthread_self());

    //主线程调用pthread_join回收子线程的资源
    pthread_join(tid,NULL);  //不想要返回什么值传递NULL

    //主线程终止
    pthread_exit(NULL);


    return 0;
}

无法看出是否回收资源

若加上:

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


void* callback(void* arg)
{
    //获取子线程id
    printf("子线程tid: %ld\n", pthread_self());
    sleep(3);//unistd.h 想让子线程晚点结束
    return(NULL);
}

int main()
{
    pthread_t tid;
    //创建子线程
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if (ret == 0)
    {
        //子线程创建成功
        printf("子线程创建成功!\n");
    }
    else
    {
        //子线程创建失败并返回错误号
        char* errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

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

    //比较主线程中子线程的id和子线程中子线程id是否相同
    printf("子线程id: %ld\n, 主线程的子线程id:%ld\n ", tid, pthread_self());

    //主线程调用pthread_join回收子线程的资源
    int ret = pthread_join(tid, NULL);  //不想要返回什么值传递NULL 只要子线程没有结束,就不会回收
    if (ret == 0)
    {
        //回收成功
        printf("回收子线程资源成功!\n");
    }
    else
    {
        //回收失败并返回错误号
        char* errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    //主线程终止
    pthread_exit(NULL);


    return 0;
}

阻塞了,等待三秒(等待子线程结束了,才能继续往下运行)

若更改子线程的内容:

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


void* callback(void* arg)
{
    //获取子线程id
    printf("子线程tid: %ld\n", pthread_self());
    int value = 10;
    pthread_exit((void *) & value)//内部参数void *retval  value是int类型数值,取地址变int类型指针&value 强转
}

int main()
{
    pthread_t tid;
    //创建子线程
    int ret = pthread_create(&tid, NULL, callback, NULL);
    if (ret == 0)
    {
        //子线程创建成功
        printf("子线程创建成功!\n");
    }
    else
    {
        //子线程创建失败并返回错误号
        char* errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

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

    //比较主线程中子线程的id和子线程中子线程id是否相同
    printf("子线程id: %ld\n, 主线程的子线程id:%ld\n ", tid, pthread_self());

    int * retval;

    //主线程调用pthread_join回收子线程的资源
    int ret = pthread_join(tid, void(**) & retval);  //第二个参数为二级指针,所以取地址 转**
    if (ret == 0)
    {
        //回收成功
        printf("回收子线程资源成功!\n");
    }
    else
    {
        //回收失败并返回错误号
        char* errstr = strerror(ret);
        printf("error: %s\n", errstr);
    }

    printf("返回数据:%d\n", *retval);//返回的是int类型,就是自己的定义

    //主线程终止
    pthread_exit(NULL);


    return 0;
}

返回的数值是一个随机的数值(每一次都不一样),和value不一样(因为value是一个局部变量,子线程执行完之后有自己的栈(主栈和子栈),子线程运行完之后,栈空间释放,就获取不到了)

所以需要定义一个全局变量,都是共享的

7 线程的分离  pthread_detach

设置


#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;
}

子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放了

如果将一个分离的线程连接,会报错:非法参数

8 线程取消   pthread_cancel


#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;
}

多次调用,结果可能不同

注意:线程取消并不是立马终止

知识点 7 :线程属性

1 查看与线程属性相关的函数  man phtread_attr_

2 设置线程分离的状态属性   pthread_attr_setdetachstate

之前有利用过pthread_detach的方法使得线程分离

线程分离的作用:线程分离之后,子线程结束之后,不需要主线程进行资源回收


#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);
    }


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

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

    pthread_exit(NULL);

    return 0;
}

运行结果:无法看出来有没有回收

可以通过栈的大小观察得到:


#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)//必需ld

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

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

    pthread_exit(NULL);

    return 0;
}

默认栈的大小:8388608

知识点 8 :线程同步

案例:多线程实现买票(3个窗口总共100张票)

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

void* sellticket(void* arg)
{
	int tickets = 100;
	while (tickets > 0)
	{
		//有票
		printf("%ld正在出售第 %d 张票\n", pthread_self(), tickets);
		ticket--;
	}
	return NULL;
}


int main()
{
	//创建3个子线程(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);

	return 0;
}

运行结果:

子线程112:100-80

子线程408:100-1

子线程704:100-26

子线程112:79-1

子线程704:25-1

但是我们的要求是总共100张,这里是每个窗口都卖100张,说明代码逻辑有问题

解决:

tickets改为全局变量

运行还是发现问题:三个线程卖了同一张门票

为了看的更明显:usleep(6000)每个线程都卖了同一张票 且0 -1

原因:

卖同一张门票:A线程执行之后休眠,BC线程可能抢占资源也运行了

-1:AB线程都进入程序中,A先执行到0了,B也要运行就变成-1

解决方法:线程同步:每次都只有一个线程取操作这块共享资源

​​​​原子操作:不可分割,最小的,一口气都执行完,中间不可以有其他线程进入

线程同步:主要是针对临界区(保证安全性)

知识点 9 :互斥锁

当线程A拿到锁M,那么线程B就阻塞了。如果线程B拿到锁了,那么会锁死,线程B就拿不到了

知识点 10 :互斥锁相关操作函数

初始化init   销毁destroy  lock 锁定   trylock 尝试锁定   unlock 解锁

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

//共享同一份数据
int tickets = 100;

//创建一个互斥量(全局变量)
pthread_mutex_t mutex;


//这样就能实现A线程执行会加锁,BC线程无法加入除非A线程解锁
void* sellticket(void* arg)
{
	//加锁
	pthread_mutex_lock(&mutex);  

	while (tickets > 0)  //产生问题的临界区
	{
		//有票
		printf("%ld正在出售第 %d 张票\n", pthread_self(), tickets);
		ticket--;
	}

	//解锁
	pthread_mutex_unlock(&mutex);

	return NULL;
}


int main()
{
	//创建一个互斥量(不在main函数里面创建,不然是局部变量,结束之后就被清除了,所以应该是全局变量)

	//初始化互斥量
	pthread_mutex_Init(&mutex);
	


	//创建3个子线程(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;
}

运行结果:都是由同一个线程来全部完成了(while循环的问题)

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

//共享同一份数据
int tickets = 1000;

//创建一个互斥量(全局变量)
pthread_mutex_t mutex;


//这样就能实现A线程执行会加锁,BC线程无法加入除非A线程解锁
void* sellticket(void* arg)
{


	while (1)  //产生问题的临界区
	{
		if (tickets > 0)
		{
			//加锁
			pthread_mutex_lock(&mutex);

			//有票
			printf("%ld正在出售第 %d 张票\n", pthread_self(), tickets);
			ticket--;
		}
		else
		{
			printf("卖完了!");
			break;

			//解锁
			pthread_mutex_unlock(&mutex);
		}

		//解锁
		pthread_mutex_unlock(&mutex);
	}




	return NULL;
}


int main()
{
	//创建一个互斥量(不在main函数里面创建,不然是局部变量,结束之后就被清除了,所以应该是全局变量)

	//初始化互斥量
	pthread_mutex_Init(&mutex);
	


	//创建3个子线程(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;
}

tickets的值要大一点 不然运行速度太快,都是一个线程完成的

知识点 11 :死锁

类型一:重复加锁  阻塞

类型二:

#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;
}

知识点 12 :读写锁

1 概念

2 读写锁相关操作函数

#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;
}

知识点 13 :生产者消费者模型

生产者:负责生产商品

生产者发现容器满了需要通知消费者

消费者发现容器空了需要通知生产者

实际生活中不止一个生产者一个消费者

容器相当于是一份共享的数据,生产者和消费者相当于线程,多个线程同时对一份数据进行操作,肯定会产生数据安全问题。

/*
    生产者消费者模型(粗略的版本)
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

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

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_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_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, 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_exit(NULL);

    return 0;
}

知识点 14 : 条件变量

条件变量不是锁,但是可以引起线程阻塞(某个条件满足之后阻塞或者某个条件满足之后解除阻塞)

条件变量只是配合互斥量一起使用,共同解决线程同步的问题

#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;
}

知识点 15 :信号量

现在

  • 26
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值