Linux多线程

目录

  • 一、Linux线程概念
    • 1.1 什么是线程?
    • 1.2 线程的优点
    • 1.3 线程的缺点
    • 1.4 线程异常
    • 1.5 线程用途
    • 1.6 进程VS线程
    • 1.7 关于进程和线程的问题
  • 二、Linux线程控制
    • 2.1 POSIX线程库
    • 2.2 创建线程
    • 2.3 线程ID及进程地址空间布局
  • 三、Linux线程终止
  • 四、线程等待
    • 4.1 为什么需要线程等待?
  • 五、分离线程
  • 六、Linux线程互斥
    • 6.1 进程线程间的互斥相关背景概念:
    • 6.2 互斥锁mutex
    • 6.3 互斥锁接口
      • 6.3.1 初始化互斥锁
      • 6.3.1 销毁互斥锁
      • 6.3.2 互斥量加锁和解锁
    • 6.4 互斥锁原理
    • 6.5 可重入VS线程安全
      • 6.5.1 概念
      • 6.5.2 常见的线程不安全的情况
      • 6.5.3 常见的线程安全的情况
      • 6.5.4 常见不可重入的情况
      • 6.5.5 常见可重入的情况
      • 6.5.6 可重入与线程安全的联系
      • 6.5.7 可重入与线程安全区别
  • 七、常见锁概念
    • 7.1 死锁
    • 7.2 死锁的四个必要条件
    • 7.3 避免死锁
    • 7.4 避免死锁的算法
  • 八、Linux线程同步
    • 8.1 条件变量
    • 8.2 同步概念与竞态条件
    • 8.3 条件变量函数
      • 8.3.1 初始化
      • 8.3.2 销毁
      • 8.3.3 等待条件满足
      • 8.3.4 唤醒等待
      • 8.3.5 为什么pthread_cond_wait 需要传入互斥锁?
      • 8.3.6 条件变量使用规范
  • 九、Linux多线程内容一览图

一、Linux线程概念

1.1 什么是线程?

1、在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。
2、一切进程至少都有一个执行线程。
3、线程在进程内部运行,本质是在进程地址空间内运行。
4、在Linux系统中,在CPU看来,看到的PCB都要比传统的进程更加轻量化。
5、透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

在这里插入图片描述

1.2 线程的优点

1、创建一个新线程的代价要比创建一个新进程小得多。
2、与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。
3、线程占用的资源要比进程少很多。
4、能充分利用多处理器的可并行数量。
5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
6、计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
7、I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

1.3 线程的缺点

1、性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
2、健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3、缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4、编程难度提高
编写与调试一个多线程程序比单线程程序困难得多。

1.4 线程异常

1、单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
2、线程是进程的执行分支,线程出异常,就类似进程出异常,触发信号机制终止进程,一旦进程终止,该进程内的所有线程也就随即退出。

1.5 线程用途

1、合理的使用多线程,能提高CPU密集型程序的执行效率。
2、合理的使用多线程,能提高IO密集型程序的用户体验(如我们可以一边听歌一边下载开发工具,就是多线程运行的一种表现)。

1.6 进程VS线程

1、进程是承担分配系统资源的基本实体。
2、线程是CPU调度的基本单位。
3、线程共享进程数据,但也拥有自己的一部分数据。
例如:
(1)线程ID
(2)一组寄存器
(3)栈
(4)errno
(5)信号屏蔽字
(6)调度优先级
等数据就是线程自己独有的数据。

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
(1)文件描述符表
(2)每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
(3)当前工作目录
(4)用户id和组id

进程和线程的关系如下图:
在这里插入图片描述

1.7 关于进程和线程的问题

如何看待之前文章学习的单进程?
具有一个线程执行流的进程,即单进程单线程。

二、Linux线程控制

2.1 POSIX线程库

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”开头的。
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

2.2 创建线程

在这里插入图片描述

功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, 
			const pthread_attr_t *attr, 
			void *(*start_routine)(void*), 
			void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是一个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

错误检查:
1、传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
2、但是pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误代码通过返回值返回。
3、pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小。

2.3 线程ID及进程地址空间布局

1、pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
2、前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
3、pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:

pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。(共享区的地址)
在这里插入图片描述

三、Linux线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。

pthread_exit函数:

功能:线程终止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

pthread_cancel函数:

功能:取消一个执行中的线程
原型
int pthread_cancel(pthread_t thread);
参数
thread:线程ID
返回值:成功返回0;失败返回错误码

四、线程等待

4.1 为什么需要线程等待?

1、已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。
2、主线程要知道新线程执行结果情况。

功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终止掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

线程的创建和等待的时间线流程大概呈下图的形式:
在这里插入图片描述
验证线程的三种退出方式的退出码:

int exitcode=10;

int a=5;

void* threadRun(void* args)
{
    int* number=static_cast<int*>(args);
    int cnt=10;
    while(cnt)
    {
        cout<<"number:"<<number<<endl;
        sleep(1);
        cnt--;
    }
    //方式一:
    //pthread_exit(&exitcode);

    //方式二:
    return &a;
}

int main()
{
    PTHREAD_CANCELED;
    pthread_t tid;
    pthread_create(&tid,nullptr,threadRun,(void*)24);
    int cnt=5;
    while(cnt)
    {
        sleep(1);
        cnt--;
    }
    //方式三:
    pthread_cancel(tid);
    //pthread_exit(&cnt);
    void* ret=nullptr;
    //通过pthread_join的第二个参数获取新线程的退出码
    pthread_join(tid,(void**)&ret);

    cout<<"new thread exitcode: "<<(long long int)ret<<endl;

    return 0;
}

五、分离线程

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

//其它线程对目标线程进行分离
int pthread_detach(pthread_t thread);
//线程自己分离自己
pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

六、Linux线程互斥

6.1 进程线程间的互斥相关背景概念:

临界资源:多线程执行流共享的资源就叫做临界资源。
临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

6.2 互斥锁mutex

1、大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
2、但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
3、多个线程并发的访问共享变量,会带来一些问题。

下面模拟一个多线程抢票的场景:

没加锁时:

#define NUM 5

struct threadData 
{
public:
    threadData(int num) 
        :_threadName("thread-"+to_string(num))
    {}

public:
    int _num;
    string _threadName;
};

pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
int tickets=1000;

void* threadRun(void* argv)
{
    threadData* td=static_cast<threadData*>(argv);

    while(true)
    {
        //pthread_mutex_lock(&mtx);
        if(tickets>0)
        {
            usleep(100);
            cout<<td->_threadName<<" get a ticket:"<<tickets<<endl;
            tickets--;
            //pthread_mutex_unlock(&mtx);
            usleep(1000);
        }
        else
        {
            // pthread_mutex_unlock(&mtx);
            break;
        }
    }

    delete td;
    return nullptr;
}


int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        threadData* td=new threadData(i+1);
        pthread_create(&tid,nullptr,threadRun,td);
        tids.push_back(tid);
    }

    for(int i=0;i<NUM;i++)
    {
        pthread_join(tids[i],nullptr);
        printf("join success,tid:0x%x\n",tids[i]);
    }
    return 0;
}

在这里插入图片描述
居然抢到了负数,这显然是错误的。
为什么可能无法获得正确的结果呢?原因有如下三点:
1、if 语句判断条件为真以后,代码可以并发的切换到其他线程。
2、usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段。
3、ticket - - 操作本身就不是一个原子操作。

tickets–的反汇编如下:
在这里插入图片描述
– 操作并不是原子操作,而是对应三条汇编指令:
(1)move:将共享变量ticket从内存加载到寄存器中
(2)sub : 更新寄存器里面的值,执行-1操作
(3)move:将新值从寄存器写回共享变量ticket的内存地址

要解决以上问题,需要做到三点:
1、代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
2、如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
3、如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量,也叫互斥锁。
在这里插入图片描述

6.3 互斥锁接口

6.3.1 初始化互斥锁

初始化互斥量有两种方法:
方法1,静态分配:

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

方法二:动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:互斥锁的属性,设为nullptr即可,表示默认属性

6.3.1 销毁互斥锁

销毁互斥量需要注意:
1、使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁。
2、不要销毁一个已经加锁的互斥量。
3、已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数为想要销毁的锁

6.3.2 互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号

调用 pthread_lock 时,可能会遇到以下情况:
1、互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
2、发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

给上面的抢票系统加锁:

#define NUM 5

struct threadData 
{
public:
    threadData(int num) 
        :_threadName("thread-"+to_string(num))
    {}

public:
    int _num;
    string _threadName;
};

pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
int tickets=1000;

void* threadRun(void* argv)
{
    threadData* td=static_cast<threadData*>(argv);

    while(true)
    {
        pthread_mutex_lock(&mtx);
        if(tickets>0)
        {
            usleep(100);
            cout<<td->_threadName<<" get a ticket:"<<tickets<<endl;
            tickets--;
            pthread_mutex_unlock(&mtx);
            usleep(1000);
        }
        else
        {
            pthread_mutex_unlock(&mtx);
            break;
        }
    }

    delete td;
    return nullptr;
}


int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        threadData* td=new threadData(i+1);
        pthread_create(&tid,nullptr,threadRun,td);
        tids.push_back(tid);
    }

    for(int i=0;i<NUM;i++)
    {
        pthread_join(tids[i],nullptr);
        printf("join success,tid:0x%x\n",tids[i]);
    }
    return 0;
}

在这里插入图片描述

6.4 互斥锁原理

经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题。
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时,另一个处理器的交换指令只能等待总线周期。

加锁和解锁的操作:
在这里插入图片描述

6.5 可重入VS线程安全

6.5.1 概念

线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

6.5.2 常见的线程不安全的情况

1、不保护共享变量的函数
2、函数状态随着被调用,状态发生变化的函数
3、返回指向静态变量指针的函数
4、调用线程不安全函数的函数

6.5.3 常见的线程安全的情况

1、每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
2、类或者接口对于线程来说都是原子操作。
3、多个线程之间的切换不会导致该接口的执行结果存在二义性。

6.5.4 常见不可重入的情况

1、调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
2、调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
3、可重入函数体内使用了静态的数据结构。

6.5.5 常见可重入的情况

1、不使用全局变量或静态变量。
2、不使用用malloc或者new开辟出的空间。
3、不调用不可重入函数。
4、不返回静态或全局数据,所有数据都由函数的调用者提供。
5、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

6.5.6 可重入与线程安全的联系

1、函数是可重入的,那就是线程安全的
2、函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
3、如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

6.5.7 可重入与线程安全区别

1、可重入函数是线程安全函数的一种。
2、线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
3、如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

所以关于线程安全和可重入函数的结论只有两点:
1、线程安全描述的是线程并发的问题,可重入函数描述的是函数特点的问题。
2、不可重入函数可能会出现线程安全的问题,但是可重入函数一定是线程安全的。

七、常见锁概念

7.1 死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请别人占用的不会释放的资源而处于的一种永久等待状态,叫做死锁。

7.2 死锁的四个必要条件

1、互斥条件:一个资源每次只能被一个执行流使用。
2、请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺。
4、循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

7.3 避免死锁

1、破坏死锁的四个必要条件
2、加锁顺序一致
3、避免锁未释放的场景
4、资源一次性分配(资源一次性给,加锁的场景和次数就会变少,产生死锁的场景也就会变少)

7.4 避免死锁的算法

(1)死锁检测算法
(2)银行家算法

八、Linux线程同步

8.1 条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

8.2 同步概念与竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
竞态条件:因为时序问题而导致程序异常,我们称之为竞态条件。在多线程场景下,这种问题也不难理解。

8.3 条件变量函数

8.3.1 初始化

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL//表示默认属性

8.3.2 销毁

int pthread_cond_destroy(pthread_cond_t *cond);

8.3.3 等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,等待时需要先解锁,避免出现死锁,后面详细解释

8.3.4 唤醒等待

唤醒所有等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒一个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);

简单示例:

pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void* thread1(void* argv)
{
    //pthread_mutex_lock(&mtx);
    pthread_cond_wait(&cond,&mtx);
    cout<<"I am thread1..."<<endl;
    //pthread_mutex_unlock(&mtx);
    return nullptr;
}

void* thread2(void* argv)
{
    sleep(5);
    pthread_cond_signal(&cond);
    return nullptr;
}


int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,nullptr,thread1,nullptr);
    pthread_create(&tid2,nullptr,thread2,nullptr);

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);

    return 0;
}

8.3.5 为什么pthread_cond_wait 需要传入互斥锁?

条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

在这里插入图片描述
按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,先解锁,然后再等待条件变量满足,如下代码:

pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void* thread1(void* argv)
{
    pthread_mutex_lock(&mtx);
    pthread_cond_wait(&cond,&mtx);
    cout<<"I am thread1..."<<endl;
    pthread_mutex_unlock(&mtx);
    return nullptr;
}

void* thread2(void* argv)
{
    sleep(5);
    pthread_cond_signal(&cond);
    return nullptr;
}


int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,nullptr,thread1,nullptr);
    pthread_create(&tid2,nullptr,thread2,nullptr);

    pthread_join(tid1,nullptr);
    pthread_join(tid2,nullptr);

    return 0;
}
// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) 
{
	pthread_mutex_unlock(&mutex);
	//解锁之后,等待之前,条件可能已经满足,信号已
	//经发出,但是该信号可能被错过
	pthread_cond_wait(&cond);
	pthread_mutex_lock(&mutex);
} 
pthread_mutex_unlock(&mutex);

由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到
互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。

int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。

8.3.6 条件变量使用规范

等待条件代码:

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号代码:

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

九、Linux多线程内容一览图

在这里插入图片描述

以上就是今天想要跟大家分享的内容啦,你学会了吗?如果感觉到有所帮助的话,那就点点小心心,点点关注呗,后期还会持续更新Linux系统编程的相关知识哦,我们下期见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/266287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL——复合查询

目录 一.基本查询回顾 二. 多表查询 三.自连接 四.子查询 1.单行子查询 2.多行子查询 3.多列子查询 4.在from子句中使用子查询 5.合并查询 一.基本查询回顾 准备数据库&#xff1a; 查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为…

stm32项目(14)——基于stm32f103zet6的循迹避障小车

1.功能设计 stm32循迹避障小车&#xff0c;使用超声波测距&#xff0c;使用红外循迹模块追踪黑线&#xff0c;实现循迹功能。此外&#xff0c;还可以检测烟雾、火焰、人体、温湿度。温湿度显示在LCD屏幕上。检测到有人、有火焰、有烟雾时&#xff0c;蜂鸣器报警&#xff01; 功…

ESP32运行MicroPython——环境搭建

1、准备工作 硬件&#xff1a;ESP32-DevKitC V4 开发板、USB串口线 软件&#xff1a; flash_download_tool_3.9.5&#xff08;乐鑫烧录工具&#xff09;、官方下载地址 CP210x&#xff08;USB驱动程序&#xff09;、官方下载地址 ESP32_GENERIC-20231005-v1.21.0.bin&#xff…

串口通信(7)-C#串口通信通信帮助类实例

本文讲解C#串口通信通信帮助类实例 首先创建winform项目添加界面和控件 UI界面 namespace SerialPortDemo {partial class MainForm{/// <summary>/// 必需的设计器变量。/// </summary>private System.ComponentModel.IContainer components = null;/// <sum…

2024年个人目标制定清单~有没有适合你的那一款

在2024年&#xff0c;个人的生活目标可以有多种多样&#xff0c;这主要取决于个人的价值观、兴趣和生活情况。 个人生活目标&#xff1a; 健康和健身&#xff1a;保持身体健康和良好的心理状态是许多人重要的生活目标。这可能包括定期运动&#xff0c;均衡饮食&#xff0c;以…

MySQL报错:1366 - Incorrect integer value: ‘xx‘ for column ‘xx‘ at row 1的解决方法

我在插入表数据时遇到了1366报错&#xff0c;报错内容&#xff1a;1366 - Incorrect integer value: Cindy for column name at row 1&#xff0c;下面我演示解决方法。 根据上图&#xff0c;原因是Cindy’对应的name字段数据类型不正确。我们在左侧找到该字段所在的grade_6表&…

陪诊软件|北京陪诊系统提升医疗服务无限可能

我们深知陪诊软件搭建系统在医疗服务中的重要性。它不仅可以提高医患沟通的效率&#xff0c;还可以提供更个性化、便捷的服务体验。因此&#xff0c;我们为您搭建的陪诊软件系统集合了丰富的功能&#xff0c;旨在提升您的医疗服务质量。 首先&#xff0c;我们的陪诊软件搭建系统…

1.关于浏览器

一、认识主流浏览器 Chrome谷歌浏览器Safari苹果浏览器Firefox火狐浏览器Opera欧朋浏览器 二、浏览器内核是什么&#xff1f; 三、五大浏览器&#xff0c;四大内核 四、前端做网页开发用什么浏览器&#xff1f; Chrome谷歌浏览器。

JavaWeb笔记之WEB项目

一. 版本控制 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理&#xff0c;是软件配置管理的核心思想之一。 版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变&a…

leetCode算法—11. 盛最多水的容器

11.给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 难度&#xff1a;中等 ** 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#x…

[python]python实现对jenkins 的任务触发

目录 关键词平台说明背景一、安装 python-jenkins 库二、code三、运行 Python 脚本四、注意事项 关键词 python、excel、DBC、jenkins 平台说明 项目Valuepython版本3.6 背景 用python实现对jenkins 的任务触发。 一、安装 python-jenkins 库 pip install python-jenkin…

智能优化算法应用:基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鹈鹕算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鹈鹕算法4.实验参数设定5.算法结果6.参考文献7.MA…

天软特色因子看板 (2023.12 第13期)

该因子看板跟踪天软特色因子A06008聪明钱因子(beta)&#xff0c;该因子为以分钟行情价量信息为基础&#xff0c;识别聪明钱交易&#xff0c;用以刻画机构交易行为 值越大&#xff0c;越反映其悲观情绪&#xff0c;反之&#xff0c;反映其乐观情绪。 今日为该因子跟踪第13期&…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)Channel 模块的实现

在这篇文章中虽然实现了能够和多客户端建立连接&#xff0c;并且同时和多个客户端进行通信。 基于多反应堆的高并发服务器【C/C/Reactor】&#xff08;上&#xff09;-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/135141316?spm1001.2014.3001.5501但是有…

在线渗透盒子,集成了近百个常见的渗透渗透工具,类似软件商城的工具可以进行工具下载

现在简单统计了一下大概有80个渗透工具左右&#xff0c;3个运行环境&#xff0c;1个破解工具 工具包介绍 该工具是一个类似软件商城的工具可以进行工具下载&#xff0c;工具的卸载&#xff0c;工具的更新&#xff0c;工具编写了自动化的安装脚本&#xff0c;不用担心工具跑不起…

Log4net 教程

一、Log4net 教程 在CodeProject上找到一篇关于Log4net的教程&#xff1a;log4net Tutorial&#xff0c;这篇博客的作者是&#xff1a;Tim Corey &#xff0c;对应源代码地址为&#xff1a; https://github.com/TimCorey/Log4netTutorial&#xff0c;视频地址为&#xff1a;Ap…

CSS:浮动

CSS&#xff1a;浮动 浮动效果浮动方式 float浮动特性标准流脱标脱标的影响脱标的影响范围 清除浮动清除浮动原理 clear基于clear的清除浮动方式额外标签法:afert伪元素法双伪元素法 清除浮动原理 BFCBFC定义BFC布局规则创建一个BFC基于BFC的清除浮动方式父级添加overflow法 浮…

Linux网络编程——Socket编程步骤及常用API

Sockt服务器和客户端的开发步骤 TCP connect()最好建立在listen()后&#xff0c;一旦监听到就建立连接。 UDP 常用API 包含头文件 #include<sys/types.h> #include<sys/socket.h>创建套接字&#xff08;连接协议&#xff09; 作用 用于根据指定的地址族、数据…

最新AI创作系统ChatGPT系统源码+DALL-E3文生图+AI绘画+GPT语音对话功能

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

openai最新探索:超级对齐是否可行?

前言 今天来介绍一篇openai最新的paper&#xff1a;弱到强的对齐。 openai专门成立了一个团队来做大模型的超级对齐即superhuman model&#xff0c;之前chatgpt取得成功依赖RLHF即依赖人类反馈&#xff0c;但是作者期望的superhuman model将会是一个能够处理各种复杂问题的强…