Linux系统编程-线程

Linux系统编程-线程

一、线程概述

1、线程定义

​ 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

  • 进程—资源分配的最小单位,线程—程序执行的最小单位
  • 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

2、线程的优点

  1. 和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  2. 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于**同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,**这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,需要进程同步和互斥手段的辅助,以保证数据的一致性。

二、Linux线程常用的API

pthread非Linux系统的默认库,需手动链接-线程库 -lpthread:即带有线程的API程序编译时需要加上 -lpthread

​ 多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。如下图所示,涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。
在这里插入图片描述

1、线程自身相关API

(1)线程标识

​ 就像每个进程都有一个ID一样,每个线程也有自己的ID。进程ID在整个系统中是唯一的,但线程不同,线程ID只有在它所属的进程上下文中才有意义。线程ID时用pthread_t数据类型标识的,实现的时候可以用一个结构体来代表pthread_t数据类型,所以可移植的操作系统实现它不能把它作为整数处理。

#include <pthread.h>
pthread_t pthread_self(void);
//获得调用线程的线程ID,ID时一个指向pthread_t的结构体指针

int pthread_equal(pthread_t tid1,pthread_t tid2);
//对两个线程ID进行比较,相等返回非0,不相等返回0

可组合使用与特定场景:

  • 主线程创建一个工作队列,再分配给线程池中的线程去处理工作,但是线程不可以自行从队列中争抢任务,由主线程将工作分配给特定的 任务,主线程可以再每个任务中存储对应的线程ID;
  • 工作线程就只能处理标有自己线程ID的任务。
(2)线程创建

新增的线程通过调用pthread_create()函数来创建

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, 
				   void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
  • 当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID;
  • attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程;
  • 新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg;
  • arg是一个无类型的函数指针,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构体的地址作为arg参数传入。

代码实例:

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

void *func1(void *arg)
{
    
    
        printf("t1 id = %u\n",(unsigned int)pthread_self());
        printf("param = %d\n",*((int*)arg));
}

int main()
{
    
    
    int ret;
    int param = 100;
    pthread_t t1;

    ret = pthread_create(&t1,NULL,func1,(void *)&param);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    printf("main tid = %ld\n",pthread_self());
    return 0;
}

/*运行结果
main tid = 140275395368704*/

/*观察结果发现只有man函数的线程id被打印出来。因为线程创建不能保证哪个线程先运行,因此需要处理主线程和新线程之间的竞争:主线
程需要休眠,如果主线程不休眠它可能会退出,这样新线程没运行整个进程可能就终止了。这种行为特征依赖于操作系统中的线程实现和调度
算法。*/

修改代码,让主线程休眠1秒,等待新线程运行:

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

void *func1(void *arg)
{
    
    
        printf("t1 id = %u\n",(unsigned int)pthread_self());
        printf("param = %d\n",*((int*)arg));
}

int main()
{
    
    
    int ret;
    int param = 100;
    pthread_t t1;

    ret = pthread_create(&t1,NULL,func1,(void *)&param);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    printf("main tid = %ld\n",pthread_self());
    return 0;
}

/*运行结果
main id = 139679240414976
t1 id = 2600695552
param = 100*/

/*让主线程休眠1s后,新线程可以运行,但是让主线程休眠会损耗程序的运行速率,下面会使用等待新线程结束的函数pthread_join()来
改进。*/
(3)线程退出和线程等待

线程退出:
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

  1. 线程只是从启动例程中返回,返回值是线程的退出码;
  2. 线程可以被同一进程中的其他线程取消;
  3. 线程调用pthread_exit。
#include <pthread.h>
int pthread_exit(void *rval_ptr);
//rval_ptr是函数的返回信息,如果为NULL,表示线程进退出,不返回信息
  • rval_ptr是指向线程返回值的指针,它可以用于向等待该线程的其他线程传递一个返回值,即进程中的其他线程可以通过调用pthread_join函数访问到这个指针;
  • 当调用pthread_exit()时,当前线程会立即终止,并将value_ptr指向的值作为线程的终止状态传递给等待该线程的其他线程。如果不等待线程退出,它的终止状态将被丢弃;
  • 与之相反,如果在主线程中调用exit()函数或者从线程函数中返回,会导致整个进程的退出,而不仅仅是终止一个线程。

线程等待:

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
//当一个线程调用pthread_exit后,进程中的其他线程可以调用pthread_join获得该线程的退出状态
  • 调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED;
  • 可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL;
  • 如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。

代码实例1:等待线程1退出,并返回一个整形数

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

void *func1(void *arg)
{
    
    
    static int retval = 10;
    printf("t1 id = %u\n",(unsigned int)pthread_self());//获取func1线程id
    printf("param = %d\n",*((int*)arg));//打印参数
    pthread_exit((void*)&retval);//传递返回值
}

int main()
{
    
    
    int ret;
    int param = 100;
    int *retval = NULL;
    pthread_t t1;

    ret = pthread_create(&t1,NULL,func1,(void *)&param);//创建线程
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    printf("main id = %ld\n",pthread_self());//获取主线程id

    pthread_join(t1,(void **)&retval);//接收func1传递的返回值
    printf("main: t1 retval = %d\n",*retval);
    return 0;
}

/*运行结果
main id = 140075215894272
t1 id = 3439183616
param = 100
main: t1 retval = 10*/

代码实例2:等待线程1退出,并返回一串字符

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

void *func1(void *arg)
{
    
    
    //static int retval = 10;
    static char *retstr = "t1 quit!";
    printf("t1 id = %u\n",(unsigned int)pthread_self());
    printf("param = %d\n",*((int*)arg));
    //pthread_exit((void *)&retval);
    pthread_exit((void *)retstr);
}

int main()
{
    
    
    int ret;
    int param = 100;
    //int *retval = NULL;
    char *retstr = NULL;
    pthread_t t1;

    ret = pthread_create(&t1,NULL,func1,(void *)&param);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    printf("main id = %ld\n",pthread_self());

    //pthread_join(t1,(void **)&retval);
    //printf("main: t1 quit: %d\n",*retval);
    pthread_join(t1,(void **)&retstr);
    printf("main: t1 retstr = %s\n",retstr);
    return 0;
}

/*运行结果
main id = 140057102927616
t1 id = 2506086144
param = 100
main: t1 retstr = t1 quit!*/
(4)线程分离

pthread_detach函数用于将一个线程标记为分离状态,子线程结束后资源自动回收,不需要其他线程调用pthread_join函数来等待其终止。这样可以避免出现僵尸线程(zombie thread),从而简化了对线程资源的管理。

不可以和pthread_join同时使用

#include <pthread.h>
int pthread_detach(pthread_t thread);
// 返回值:若成功返回0,否则返回错误编号

pthread_join()函数的替代函数,可回收创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。该函数不会阻塞父线程。pthread_join()函数用于只是应用程序在线程tid终止时回收其存储空间。如果tid尚未终止,pthread_detach()不会终止该线程。

2、与互斥锁相关API

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。

(1)什么是线程同步?
  • 线程同步是指协调多个线程之间的执行顺序,以确保共享资源的正确访问和数据的一致性。当多个线程同时操作共享数据时,如果没有适当的同步机制,就会出现数据竞争和不一致的情况;
  • 线程同步的目的是为了保证共享资源在多线程环境下的安全访问,避免数据冲突和并发缺陷。通过使用同步机制,可以使得多个线程按照一定的顺序来访问共享资源,避免出现竞态条件(Race Condition)和不确定性的结果。

常用的线程同步机制:

  1. **互斥锁(Mutex):**互斥锁是一种常见的同步机制,用于保护共享资源,一次只允许一个线程访问共享资源。当一个线程获取到互斥锁之后,其他线程必须等待该线程释放锁之后才能继续访问;
  2. 信号量(Semaphore):信号量是一种用于控制并发访问数量的同步机制。通过设置一个计数器,限制同时访问某个资源的线程数量。当计数器大于0时,线程可以获取许可进行访问,访问后计数器减少;当计数器为0时,线程等待其他线程释放许可;
  3. **条件变量(Condition Variable):**条件变量用于线程之间的条件等待与信号通知。线程可以在某个条件成立时等待条件变量,而其他线程在满足条件时发出信号通知等待线程继续执行;
  4. 栅栏(Barrier):栅栏用于线程的同步和屏障等待。多个线程在某个位置等待,直到所有线程都到达屏障位置,然后才能继续执行后面的操作。

代码实例:不使用线程同步可能出现的结果

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int g_flag = 0;

void *func1(void *arg)
{
    
    
    printf("t1 id = %u\n",(unsigned int)pthread_self());
    while(1){
    
    
        printf("t1: g_flag = %d\n",g_flag++);
        sleep(1);
    }
}

void *func2(void *arg)
{
    
    
    printf("t2 id = %u\n",(unsigned int)pthread_self());
    while(1){
    
    
        printf("t2: g_flag = %d\n",g_flag++);
        sleep(1);
        //当线程2捕捉到g_flag等于4时,程序退出
        if(g_flag == 4){
    
    
            exit(0);
        }
    }
}

int main()
{
    
    
    int ret;
    pthread_t t1;
    pthread_t t2;

    ret = pthread_create(&t1,NULL,func1,NULL);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    ret = pthread_create(&t2,NULL,func2,NULL);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }
    
    printf("main id = %ld\n",pthread_self());
    printf("man: g_flag = %d\n",g_flag++);
    sleep(1);
    while(1);
    return 0;
}
/*定义一个全局变量g_flag,让主函数创建两个线程,在主函数和两个线程中同时让g_flag++并且打印g_flag的值,当线程2捕捉到g_flag等
于4时退出程序*/

运行结果:可以看到,man函数和两个线程都能拿到g_flag的值,但是想让线程2捕捉到g_flag等于4时退出程序的结果并不唯一,因为不知道线程之间的执行顺序,所以也不能保证g_flag的等于4的值一定被线程2捕捉到,下面会使用线程同步机制来确保线程2拿到g_flag等于4的值让程序退出。

在这里插入图片描述

(2)互斥锁
  • 互斥锁(Mutex Lock)是一种并发编程中的同步机制,用于保护共享资源的访问,确保在任何给定时间只有一个线程可以执行受保护的代码段;
  • 互斥锁是二进制锁,它有两个状态:锁定(locked)和解锁(unlocked)。当一个线程获取到互斥锁时,它将锁定状态设置为锁定,其他线程尝试获取锁时将被阻塞,直到锁被释放;
  • 互斥锁主要用于解决并发环境下的竞争条件,例如多个线程试图同时访问和修改同一共享资源的情况。通过使用互斥锁,我们可以确保同一时间只有一个线程能够进入临界区(Critical Section),执行对共享资源的访问和操作,避免数据竞争和不一致的结果。

创建及销毁互斥锁:

#include <pthread.h>
//创建锁,要用默认的属性初始化互斥量,只需把attr设置为NULL
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
						const pthread_mutexattr_t *restrict attr);

//销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 返回值:若成功返回0,否则返回错误编号

加锁及解锁:

#include <pthread.h>
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

//测试加锁:语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待
int pthread_mutex_trylock(pthread_mutex_t *mutex);

//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
/*返回:若成功返回0,否则返回错误编号*/

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。

使用互斥锁的基本流程:

  1. 初始化互斥锁:在使用互斥锁前,需要对其进行初始化;
  2. 获取互斥锁:线程通过调用互斥锁的"锁定"操作,尝试获取互斥锁。如果互斥锁当前处于解锁状态,线程将获取到锁,并将其状态设置为锁定;否则,线程将被阻塞,直到锁被释放;
  3. 执行临界区代码:一旦线程成功获取到互斥锁,它就可以进入临界区,执行需要保护的代码,访问共享资源;
  4. 释放互斥锁:线程执行完临界区代码后,应该调用互斥锁的"解锁"操作来释放锁,将互斥锁的状态设置为解锁,以允许其他线程获取锁并访问共享资源。

代码实例:使用互斥锁让程序退出

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int g_flag = 0;
pthread_mutex_t mutex;

void *func1(void *arg)
{
    
    
    while(1){
    
    
        pthread_mutex_lock(&mutex);
        printf("t1: g_flag = %d\n",g_flag);
        g_flag++;
        pthread_mutex_unlock(&mutex);
        sleep(1);//延时1s让t2抢到锁
    }
}

void *func2(void *arg)
{
    
    
    pthread_mutex_lock(&mutex);
    while(1){
    
    
        printf("t2: g_flag = %d\n",g_flag++);
        if(g_flag == 4){
    
    
            sleep(1);
            printf("t2: quit===============\n");
            pthread_mutex_unlock(&mutex);
            exit(0);
        }
    }
}
int main()
{
    
    
    int ret;
    pthread_t t1;
    pthread_t t2;
    pthread_mutex_init(&mutex,NULL);

    ret = pthread_create(&t1,NULL,func1,NULL);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    ret = pthread_create(&t2,NULL,func2,NULL);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }
    printf("main:pthread create success!\n");
    printf("man: g_flag = %d\n",g_flag);

    pthread_mutex_destroy(&mutex);
    while(1);
    return 0;
}
/*给两个线程同时上一把锁,如果线程1先抢到了锁,就让线程1解锁后先休眠1秒,让线程2去抢这把锁,线程2抢到锁后就遍历g_flag到4才
解锁,线程2中g_flag=4程序会退出*/

运行结果:

在这里插入图片描述

(3)死锁

死锁是指在并发程序中,两个或多个线程或进程因为互相等待对方释放资源而无法继续执行的情况。在死锁中,每个线程都在等待其他线程释放资源,导致所有线程都被阻塞,无法进行下一步操作。

代码实例:出现死锁的情况

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

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

    printf("t1 id = %u\n",(unsigned int)pthread_self());
    pthread_exit(NULL);
    pthread_mutex_unclock(&mutex1);
}

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

    printf("t2 id = %u\n",(unsigned int)pthread_self());
    pthread_exit(NULL);
    pthread_mutex_unclock(&mutex2);
}
int main()
{
    
    
    int ret;
    pthread_t t1;
    pthread_t t2;

    pthread_mutex_init(&mutex1,NULL);
    pthread_mutex_init(&mutex2,NULL);
    ret = pthread_create(&t1,NULL,func1,NULL);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }

    ret = pthread_create(&t2,NULL,func2,(void *)&param);
    if(ret != 0){
    
    
        printf("create pthread erro\n");
    }
    printf("main id = %ld\n",pthread_self());

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);
    return 0;
}
/*线程1在已经拥有一把锁的情况下想要拿到线程2的锁,同样线程2在已经拥有一把锁的情况下还想要拿到线程1的锁,然后造成了死锁。*/

运行结果:

在这里插入图片描述

解决死锁的方法:

  1. 资源有序分配:为了避免循环等待条件,可以对资源进行排序,并确保线程按照相同的顺序请求资源,从而避免产生循环等待;
  2. 避免持有并等待:一种解决死锁问题的方法是要求线程在申请资源时,释放已持有的资源。这样,当线程请求新的资源时,它没有任何资源保持,从而避免了持有并等待的情况。

3、与条件变量相关API

(1)条件变量概念

条件变量(Condition Variable)是并发编程中一种线程同步机制,用于实现线程之间的等待和通知机制。它是一种与特定条件相关的线程同步原语。

条件变量用于线程间的协调,允许一个线程在满足某个特定条件之前等待,并在其他线程满足条件后被通知继续执行。它通常与互斥锁(Mutex)结合使用,以提供更精细的线程同步和共享数据的访问控制。

条件变量的典型用法包括以下步骤:

  1. 线程获取互斥锁,锁定共享资源;
  2. 检查条件谓词(描述条件是否满足的表达式),判断是否满足等待条件,如果不满足则等待条件变量;
  3. 线程释放互斥锁并进入等待状态,直到其他线程发出通知;
  4. 当其他线程满足条件变量的条件并发出通知时,等待中的线程被唤醒;
  5. 线程重新获取互斥锁,继续执行后续操作。
(2)条件变量相关API
1.创建及销毁条件变量
  • 静态态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIER;
  • 动态初始化:int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
#include <pthread.h>
//初始化条件变量,cond指向要初始化的条件变量。参数 attr:指向条件变量的属性对象,通常传递 NULL。
int pthread_cond_init(pthread_cond_t *restrict cond, 
					  const pthread_condattr_t *restrict attr);

//销毁条件变量,cond指向要销毁的条件变量。
int pthread_cond_destroy(pthread_cond_t cond);
// 返回值:若成功返回0,否则返回错误编号
2.等待
  • 在调用 pthread_cond_wait() 之前,必须先获得与条件变量关联的互斥锁 mutex 的锁,然后该函数会自动释放 mutex 的锁,并让线程进入等待状态,直到被另一个线程通过 pthread_cond_signal() 或pthread_cond_broadcast() 唤醒。
#include <pthread.h>
//cond:指向要等待的条件变量。mutex:指向与条件变量关联的互斥锁。
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

//语义和wait相同,timeout设置等待时间,超时仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
					       pthread_mutex_t *restrict mutex,
						   cond struct timespec *restrict timeout);
// 返回:若成功返回0,否则返回错误编号
3.触发
  • pthread_cond_signal() 会唤醒等待 cond 条件变量的一个线程,如果没有线程在等待条件变量,则该函数调用没有任何效果;
  • pthread_cond_broadcast() 会唤醒等待 cond 条件变量的所有线程,即广播唤醒所有等待的线程。
#include <pthread.h>
//用于唤醒一个正在等待条件变量的线程。cond指向要唤醒的条件变量。
int pthread_cond_signal(pthread_cond_t *cond);

//用于唤醒所有正在等待条件变量的线程。指向要广播的条件变量。
int pthread_cond_broadcast(pthread_cond_t *cond);
// 返回:若成功返回0,否则返回错误编号

代码实例:使用条件变量让程序退出

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
int g_flag = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;

void *func1(void *arg)
{
    
    
    while(1){
    
    
        pthread_mutex_lock(&mutex);
        printf("t1: g_flag = %d\n",g_flag++);
        if(g_flag == 3){
    
    
            //等待信号,当g_flag等于3时唤醒线程2
            pthread_cond_signal(&cond);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

void *func2(void *arg)
{
    
    
    while(1){
    
    
		//等待唤醒信号
        pthread_cond_wait(&cond,&mutex);
        printf("t2: g_flag = %d\n",g_flag);
        sleep(1);
        printf("t2: quit===============\n");
        exit(0);
    }
}

int main()
{
    
    
        int ret;
        pthread_t t1;
        pthread_t t2;
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);

        ret = pthread_create(&t1,NULL,func1,NULL);
        if(ret != 0){
    
    
                printf("create pthread erro\n");
        }

        ret = pthread_create(&t2,NULL,func2,NULL);
        if(ret != 0){
    
    
                printf("create pthread erro\n");
        }
        printf("main:pthread create success!\n");
        printf("man: g_flag = %d\n",g_flag);

        pthread_join(t1,NULL);
        pthread_join(t2,NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);
        return 0;
}
/*使用等待条件的方法在func1中判断当g_flag的值,g_flag为3时将信号传给func2,唤醒线程2执行退出命令

运行结果:使用脚本让程序连续运行,每次线程2等到g_flag等于3时就退出程序

在这里插入图片描述

使用条件变量的好处:

  1. 避免资源浪费:当条件不满足时,线程可以调用pthread_cond_wait函数来等待条件满足。在等待期间,该线程会释放持有的锁,允许其他线程获得锁并继续执行临界区代码。这样可以避免空转和资源浪费;
  2. 避免竞争条件:条件变量的使用可以帮助避免竞争条件的发生。线程间的协作通过条件变量来实现,确保在满足特定条件之前,线程不会执行关键代码段;
  3. 提高并发性:条件变量使得线程能够在需要等待条件满足时休眠,而不是通过忙等待消耗处理器资源。这样可以提高系统的并发性和效率;
  4. 简化同步逻辑:使用条件变量可以简化同步逻辑,使代码更加清晰易懂。条件变量提供了一种灵活而有效的机制来控制线程的行为,使得编写正确的多线程代码变得更加容易。

参考链接

猜你喜欢

转载自blog.csdn.net/dearzhangxp/article/details/139156167