【Linux C++】一篇文章搞懂线程同步的五种方式(锁、条件变量、信号量)

【Linux C++】一篇文章搞懂线程同步的五种方式(锁、条件变量、信号量),第1张

目录

一、什么是线程同步?

二、为什么要使用线程同步?

三、线程同步的五种方式

        1、互斥锁

        互斥锁 *** 作函数

        互斥锁的属性

        2、自旋锁

        自旋锁 *** 作函数

        自旋锁的属性

         3、读写锁

        读写锁的 *** 作函数

        读写锁的特点

        读写锁的属性

        读写锁的注意事项

        4、信号量

        信号量 *** 作函数

        5、条件变量

        条件变量 *** 作函数

        注意事项

        关于条件变量

总结

结语


前言:文章中的代码是我在vim编辑状态下拷贝过来的,由于我的vim使用插件的原因,导致拷贝过来的代码需要删删改改,如果有错误,还请大佬指出,谢谢! 

一、什么是线程同步?

        即当有一个线程在对内存进行 *** 作时,其他线程都不可以对这个内存地址进行 *** 作,直到该线程完成 *** 作, 其他线程才能对该内存地址进行 *** 作,而其他线程又处于等待状态,实现线程同步的方法有很多。

        听完上面的叙述,是不是很匹配互斥类型锁的特色。

二、为什么要使用线程同步?

        线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。

        当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。

        线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行 *** 作,而不是同时进行 *** 作。

三、线程同步的五种方式         1、互斥锁

        1)加锁和解锁,确保同一时间只有一个线程访问共享资源;

        2)访问共享资源之前加锁,访问完成后释放锁;

        3)如果某线程持有锁,其他线程形成等待队列;

        就比如一个套间里的人使用卫生间,当你上厕所的时候需要关锁,解决完事之后再进行解锁。其他人在外面只能排队等待,等你解决完事解开锁之后,第二个人进去之后又会加锁。

        互斥锁 *** 作函数

        pthread_mutex_t mutex;                                // 声明锁

        int pthread_mutex_init();                                // 初始化锁

        int pthread_mutex_lock();                              // 等待并加锁

        int pthread_mutex_trylock();                          // 尝试加锁不等待

        int pthread_mutex_timedlock();                     // 带超时机制的加锁

        int pthread_mutex_unlock();                          // 解锁

        int pthread_mutex_destroy();                         // 销毁锁

        demo04测试程序代码如下:

#include 
#include 
#include 
#include 
#include 

int var;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;   // 宏声明并初始化互斥锁互斥锁

void *thmain(void *arg);                             // 线程主函数

int main(int argc, char* argv[])
{
    pthread_t thid1,thid2;                // 线程id
    // 函数初始化锁
    // pthread_mutex_init(&mutex,NULL);   // 第一个参数锁的id,第二个锁的属性,下面说
    
    // 创建线程,参数1:线程id,参数2:线程属性,参数3:线程主函数,参数4:线程主函数参数
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待线程退出--第一个参数线程id,第二个参数线程退出状态,这里表示不关心
    pthread_join(thid1,NULL); pthread_join(thid2,NULL);    

    printf("var=%d\n",var);

    // 退出程序前销毁锁
    pthread_mutex_destroy(&mutex);                // 一个参数锁的id
    return 0;
}

// 两个线程共用这一个线程主函数
void *thmain(void *arg)
{
    for(int ii=0;ii<1000000;ii++)
    {
        pthread_mutex_lock(&mutex);        // 加锁
        var++;                             // 加锁之后操作变量
        pthread_mutex_unlock(&mutex);      // 解锁
    }
}

         makefile文件如下:

all: demo04

demo04: demo04.cpp
    g++ -g -o demo04 demo04.cpp -lpthread

clean:
    rm -rf demo04

        编译运行结果如下:

        我们程序创建了两个线程,他们使用同一个线程主函数,线程主函数中对全局变量进行100万次加一的 *** 做,所以理想状态下,我们的最终结果应该是200万,所以当结果是200万次的时候就证明了两个线程互相没有干扰,每一次加1都是原子 *** 作,实现了线程同步

        上面这段话如果有不理解的地方,可以查看我另外一篇文章《线程安全》

        互斥锁的属性

        PTHREAD_MUTEX_TIMED_NP,这个是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。

        PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁(递归锁),允许同一个线程对同一个锁成功获取多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。

        PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,解锁后,请求锁的线程重新竞争。

        2、自旋锁

        自旋锁的功能和互斥锁一样,但是互斥锁在等待锁的过程中会进行休眠,不消耗cpu,而自旋锁在等待锁的过程中会不断地循环检查锁是否可用。

        自旋锁和互斥锁不能说谁好谁不好,各有应用场景,因为自旋锁等待的时候会不断检查锁的可用状况,所以适合那种加锁时间极端的场景,互斥锁会休眠,所以适用于那些加锁时间可能会比较长的场景。

        另外,自旋锁没有带超时机制的加锁,因为一般自旋锁都是使用在加锁时间极短的场景。

        自旋锁 *** 作函数

        pthread_spinlock_t mutex;                                        // 声明锁

        int pthread_spin_init();                                              // 初始化锁

        int pthread_spin_lock();                                            // 加锁

        int pthread_spin_trylock();                                        // 尝试枷锁

        int pthread_spin_unlock();                                        // 解锁

        int pthread_spin_destroy();                                       // 销毁锁

        注意,自旋锁不能再使用宏进行初始化了,只能使用函数,并且自旋锁的初始化锁函数和互斥锁有点不同

        互斥锁:pthread_mutex_init(锁的id,锁的属性)

        自旋锁:pthread_spin_init(锁的id,锁的共享标志);

        自旋锁的属性

        初始化函数的第二个参数用于设置属性(共享标志)

        PTHREAD_PROCESS_SHARED                        // 共享

        PTHREAD_PROCESS_PRIVATE                        // 私有

        实际开发中可能会有多个进程中每个进程又创建了多个线程的场景,如果spin自旋锁的第二个参数,共享标志设置为共享SHARED,就代表多进程的其他进程可以使用这个进程的自旋锁。如果共享标志设置为PRIVATE私有的,就代表只有创建这个自旋锁的进程可以使用该自旋锁。

        一般情况下我们设置为私有PTHREAD_PROCCESS_PRIVATE

        demo05测试程序源码如下:

#include 
#include 
#include 
#include 
#include 
 
int var;

// 注意下面的初始化是spinlock不是spin                                                                
pthread_spinlock_t spin;                    // 自旋锁只能用函数初始化,不可以使用宏

void *thmain(void *arg);                    // 线程主函数

int main(int argc, char* argv[])
{
    // 设置为私有,其他进程不可共享当前进程的自旋锁
    pthread_spin_init(&spin,PTHREAD_PROCESS_PRIVATE);
    pthread_t thid1,thid2;                // 线程id
    
    // 创建线程,参数1:线程id,参数2:线程属性,参数3:线程主函数,参数4:线程主函数参数
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待线程退出--第一个参数线程id,第二个参数线程退出状态,这里表示不关心
    pthread_join(thid1,NULL); pthread_join(thid2,NULL);    

    printf("var=%d\n",var);

    // 退出程序前销毁锁
    pthread_spin_destroy(&spin);                // 一个参数锁的id
    return 0;
}

// 两个线程共用这一个线程主函数
void *thmain(void *arg)
{
    for(int ii=0;ii<1000000;ii++)
    {
        pthread_spin_lock(&spin);        // 加锁
        var++;                             // 加锁之后操作变量
        pthread_spin_unlock(&spin);      // 解锁
    }
}

        makefile不变

        运行结果如下:

         3、读写锁

        读写锁允许更高的并发性

        三种状态:读模式加锁(读锁)、写模式加锁(写锁)、不加锁

        读写锁的 *** 作函数

        pthread_rwlock_t mutex;                           // 声明读写锁rw--read-write

        int pthread_rwlock_init();                           // 初始化读写锁

        int pthread_rwlock_destroy();                   // 销毁锁

        pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; // 声明并初始化读写锁

        int pthread_rwlock_rdlock();                    // 申请读锁

        int pthread_rwlock_tryrdlock();               // 尝试申请读锁,不会产生阻塞

        int pthread_rwlock_timedrdlock();          // 带超时机制的申请读锁

        int pthread_rwlock_wrlock();                   // 申请写锁

        int pthread_rwlock_trywrlock();              // 尝试申请写锁,不产生阻塞

        int pthread_rwlock_timedwrlock();         // 申请写锁,带有超时机制 

        int pthread_rwlock_unlock();                  // 解锁函数,读锁写锁通用

        int pthread_rwlockattr_getpshared();    // 获取读写锁的属性

        int pthread_rwlockattr_setpshared();    // 设置读写锁的属性

        读写锁的特点

        只要没有线程持有写锁,任意线程都可以成功申请读锁

        只有在所有线程都没有锁的情况下,申请写锁才能成功

        读写锁的属性

        读写锁的属性也是共享标志,分为PTHREAD_PROCESS_PRIVATE(私有)和PTHREAD_PROCESS_SHARED(共享),和自旋锁设置属性的方式一样,参上,不过读写锁有专门设置锁的属性的函数

        测试程序demo06.cpp:

#include                                                                                    
#include 
#include 
#include 
#include 
#include 

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;     // 声明读写锁并初始化

void *thmain(void *arg);                    // 线程主函数

void handle(int sig);                       // 信号15的处理函数。

int main(int argc, char* argv[])
{
    signal(15,handle);          // 当程序接收到15的信号会调用该函数。

    pthread_t thid1,thid2,thid3;

    // 创建线程,参数1:线程id,参数2:线程属性,参数3:线程主函数,参数4:线程主函数参数
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1);       // 线程之间sleep 1秒,这样能保证三个线程会一直拥有锁
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1);    
    if(pthread_create(&thid3,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待线程退出--第一个参数线程id,第二个参数线程退出状态,这里表示不关心
    pthread_join(thid1,NULL); pthread_join(thid2,NULL); pthread_join(thid3,NULL);    

    // 退出程序前销毁锁
    pthread_rwlock_destroy(&rwlock);                // 一个参数锁的id
    return 0;
}

// 三个线程共用这一个线程主函数
void *thmain(void *arg)
{
    for(int ii=0;ii<100;ii++)
    {
        printf("线程%lu开始申请读锁...\n",pthread_self());
        pthread_rwlock_rdlock(&rwlock);     // 第一个线程申请时肯定没有写锁,所以任意线程都能申请读锁
        printf("线程%lu申请读锁成功...\n\n",pthread_self());
sleep(5);
        pthread_rwlock_unlock(&rwlock);     // 解锁
        printf("线程%lu已经释放读锁...\n\n",pthread_self());
    }
}

void handle(int sig)
{
    printf("开始申请写锁...\n");
    pthread_rwlock_wrlock(&rwlock);         // 加锁
    printf("申请写锁成功...\n");
    sleep(10);
    pthread_rwlock_unlock(&rwlock);         // 解锁
    printf("写锁已经解除...\n\n");
}

        解释一下代码:

        上面代码中关于信号处理的方法以后我会讲,这里大家只需要知道15的信号宏名为SIGTERM,也就是我们的killall命令,他就是15的信号。当进程运行时,killall这个进程,就会调用信号处理函数,也就是你使用killall命令杀这个程序的时候,进程收到命令就会开始申请写锁。

        这里的代码和之前的略有不同

        首先就是创建线程之间会延迟一秒,这里可以先看一下线程主函数中的内容

        主函数首先申请了读写锁,因为三个线程中没有线程申请写锁,所以申请一定会成功,那么第一个创建的线程就会持有读锁,然后过了一秒第二个线程又持有了读锁,再过一秒第三个线程也持有了读锁,此时再过两秒之后第一个线程释放了锁,但是还有两个线程持有锁,等线程2释放锁的时候,线程1和线程3还会持有读锁,这就能保证程序运行的时候三个线程会一直持有读锁,所以申请写锁不会成功。

        假设我们不延时,三个线程同时(相差时间忽略不计)被创建,最后又同时释放锁,这个时候,因为你一旦发送15的信号,他开始申请写锁就会一直在申请,哪怕你只有几毫秒的时间没有持有锁,写锁也会申请成功。

        我们运行程序,发送killall命令,如果申请写锁一直不成功,就说明线程一直持有读锁。

        makefile不变

        运行如下:

        发送15的信号:

        结果:

        可以看到写锁一直没有申请成功,这足以证明两点:

        1、当线程一直持有锁的时候,写锁不会申请成功

        2、这个程序是不间断持有读锁的

        如果实际开发中这样让程序一直持有读锁,不就造成写入线程饿死的情况了吗?

        所以我们实际开发中肯定不能让线程一直持有读锁。

        下面的测试程序不是解决写入线程饿死的情况,只是证明下,如果所有线程不持有锁,那么写锁就会申请成功

        demo07.cpp:

#include                                                                                    
#include 
#include 
#include 
#include 
#include 

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;     // 声明读写锁并初始化

void *thmain(void *arg);                    // 线程主函数

void handle(int sig);                       // 信号15的处理函数。

int main(int argc, char* argv[])
{
    signal(15,handle);          // 当程序接收到15的信号会调用该函数。

    pthread_t thid1,thid2,thid3;

    // 创建线程,参数1:线程id,参数2:线程属性,参数3:线程主函数,参数4:线程主函数参数
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1);       // 线程之间sleep 1秒,这样能保证三个线程会一直拥有锁
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1);    
    if(pthread_create(&thid3,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待线程退出--第一个参数线程id,第二个参数线程退出状态,这里表示不关心
    pthread_join(thid1,NULL); pthread_join(thid2,NULL); pthread_join(thid3,NULL);    

    // 退出程序前销毁锁
    pthread_rwlock_destroy(&rwlock);                // 一个参数锁的id
    return 0;
}

// 三个线程共用这一个线程主函数
void *thmain(void *arg)
{
    for(int ii=0;ii<100;ii++)
    {
        printf("线程%lu开始申请读锁...\n",pthread_self());
        pthread_rwlock_rdlock(&rwlock);     // 第一个线程申请时肯定没有写锁,所以任意线程都能申请读锁
        printf("线程%lu申请读锁成功...\n\n",pthread_self());
sleep(5);
        pthread_rwlock_unlock(&rwlock);     // 解锁
        printf("线程%lu已经释放读锁...\n\n",pthread_self());
        
        if(ii==3) sleep(8);
    }
}

void handle(int sig)
{
    printf("开始申请写锁...\n");
    pthread_rwlock_wrlock(&rwlock);         // 加锁
    printf("申请写锁成功...\n");
    sleep(10);
    pthread_rwlock_unlock(&rwlock);         // 解锁
    printf("写锁已经解除...\n\n");
}

        只加了一行代码,在线程主函数中,当线程第三次释放了锁之后,延时8秒,这样的话有一段时间就能保证全部的线程都释放了锁,我们可以提前申请写锁,他会一直等待,当线程都没有锁的时候,就会立马申请成功,同时写锁申请成功后,在写锁没有释放之前,所有读锁也都不会申请成功。

        运行如下:

        通过结果得知:

        1、线程都没有锁的情况下写锁就会申请成功

        2、线程持有写锁的时候所有读锁申请都不会成功 

        

        读写锁的注意事项

        读写锁适用于对读的次数远大于写的情况

        Linux系统优先考虑读锁,这种实现方式可能会导致写入线程饿死的情况,所以一定不能让自己的线程一直都持有读锁,除非你使用读写锁就是为了读取信息不写入。

        上面说的是“当其他线程持有锁的时候,申请写锁不会成功”,不是“当其他线程持有读锁的时候,申请写锁不会成功”,可以在程序运行的时候多发几次15的信号申请写锁,自己测试一下吧。

        4、信号量

        信号量的知识,以后会单独拿出来讲,这里只需要知道他的函数怎么用就行了,以及如何用信号量实现互斥锁的功能。

        信号量 *** 作函数

        sem_t *sem;                        // 声明信号量

        int sem_init();                       // 初始化信号量

        int sem_destroy();                // 销毁信号量

        int sem_wait(sem_t *sem);        // 信号量的P *** 作

        int sem_trywait(sem_t *sem);    // 信号量的P *** 作,不阻塞

        int sem_timedwait();                  // 信号量的P *** 作,带有超时机制

        int sem_post(sem_t *sem);       // 信号量的V *** 作

        

        int sem_getvalue();                   // 获取信号量的值

        解释一下,我们这里使用的是二元信号量,也就是信号值只有0和1,0代表不可用,1代表可用,信号量的初始值我们可以通过init函数设置,一般设置为1。当我们使用P *** 作的时候,信号值就会减1,变为0--不可用(类似于加锁的 *** 作),然后当我们使用了V *** 作的时候,信号量就会加1,变为1--可用(类似于解锁的 *** 作)。

        以上解释可能不太标准,但大致就是这个意思,关于信号量的知识以后会发布相关文章。

        demo08.cpp:

#include                                                                                    
#include 
#include 
#include 
#include 
#include 

int var;

sem_t sem;              // 声明信号量,使用函数初始化

void *thmain(void *arg);                             // 线程主函数

int main(int argc, char* argv[])
{
    pthread_t thid1,thid2;              // 线程id
                                        
    sem_init(&sem,0,1);                 // 第一个参数,信号量id,第二个先固定填0,第三个信号量初始值
    
    // 创建线程,参数1:线程id,参数2:线程属性,参数3:线程主函数,参数4:线程主函数参数
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待线程退出--第一个参数线程id,第二个参数线程退出状态,这里表示不关心
    pthread_join(thid1,NULL); pthread_join(thid2,NULL);    

    printf("var=%d\n",var);

    // 退出程序前销毁锁
    sem_destroy(&sem);                // 一个参数锁的id
    return 0;
}

// 两个线程共用这一个线程主函数
void *thmain(void *arg)
{
    for(int ii=0;ii<1000000;ii++)
    {
        sem_wait(&sem);                    // 信号量的P操作(加锁)
        var++;                             // 加锁之后操作变量
        sem_post(&sem);                    // 信号量的V操作(解锁)
    } 
}

        makefile文件不变

        运行结果如下:

         这样就通过二元信号量实现了互斥锁的功能,信号量的知识就先讲这么多。

        5、条件变量         条件变量 *** 作函数

        pthread_cond_t cond;                                // 生命条件变量

        int pthread_cond_init();                              // 初始化条件变量

        int pthread_cond_destroy();                       // 销毁条件变量

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;        // 声明并初始化

        int pthread_cond_wait();                            // 等待被唤醒

        int pthread_cond_timedwait();                   // 等待被唤醒,带超时机制

        int pthread_cond_signal();                         // 唤醒一个等待中的线程

        int pthread_cond_broadcast();                   // 唤醒全部等待中的线程

                 

        // 设置条件变量的共享属性,也就是在多个进程的线程之间是否共享条件变量

        int pthread_condattr_getpshared();            // 获取共享属性

        int pthread_condattr_setpshared();            // 设置共享属性

        // 设置条件变量的时钟属性,一般用不到

        int pthread_condattr_getclock();                 // 获取时钟属性

        int pthread_condattr_setclock();                 // 设置时钟属性

        注意事项

        首先大家不要被条件变量的名称给误导了,大家就把它当作是一种特殊的锁就行了

        另外条件变量必须搭配互斥锁使用,至于为什么以后会讲,大家也可以先猜测猜测是为什么

        条件变量的wait()函数会产生阻塞

        测试程序demo10.cpp(这里使用激活一个线程的函数)

#include                                                                                    
#include 
#include 
#include 
#include 
#include 
 
// 条件变量需要搭配互斥锁使用
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;     // 声明并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 声明并初始化互斥锁

void* thmain(void *arg);            // 线程主函数

void handle(int sig);               // 信号处理函数

int main(int argc, char* argv[])
{
    signal(15,handle);              // 设置信号15的处理函数
 
    pthread_t thid1,thid2,thid3;

    // 创建线程
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid3,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待子线程的退出
    pthread_join(thid1,NULL); pthread_join(thid2,NULL); pthread_join(thid3,NULL);

    // 销毁锁和条件变量
    pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex);

    return 0;
}

void *thmain(void* arg)
{
    while(true)
    {
        printf("线程%lu开始等待条件信号...\n",pthread_self());  // 获取当前线程号
        pthread_cond_wait(&cond,&mutex);        // 等待条件信号,注意参数 
        printf("线程%lu等待条件信号成功...\n\n",pthread_self());
    }
}

void handle(int sig)
{
    printf("发送条件信号...\n");
    pthread_cond_signal(&cond);         // 唤醒等待条件信号的一个线程。
    /* pthread_cond_broadcast(&cond);      // 唤醒等待条件信号的全部线程。 */
}

        解释一下代码:

        首先创建了三个线程,然后设置了15(killall命令)的信号处理函数。在线程主函数中,每一个线程都是一直在等待条件信号,如果使用一次killall就会调用一次handle()函数,handle函数每调用一次就会激活一个等待中的线程

        运行

        发送四次15的信号

        查看运行结果

        我们可以看到,我们发送了四次15的信号,每一次都会调用一次信号处理函数,然而调用一次信号处理函数就会激活一个等待中的线程,但是这里激活线程并不是随机的,而是按照等待的顺序去激活的,仔细观看线程编号就可以发现。 

        测试程序demo10.cpp(这里使用激活全部线程的函数)

#include                                                                                    
#include 
#include 
#include 
#include 
#include 
 
// 条件变量需要搭配互斥锁使用
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;     // 声明并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 声明并初始化互斥锁

void* thmain(void *arg);            // 线程主函数

void handle(int sig);               // 信号处理函数

int main(int argc, char* argv[])
{
    signal(15,handle);              // 设置信号15的处理函数
 
    pthread_t thid1,thid2,thid3;

    // 创建线程
    if(pthread_create(&thid1,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid2,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }
    if(pthread_create(&thid3,0,thmain,0)!=0) { printf("create failed.\n"); return -1; }

    // 等待子线程的退出
    pthread_join(thid1,NULL); pthread_join(thid2,NULL); pthread_join(thid3,NULL);

    // 销毁锁和条件变量
    pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex);

    return 0;
}

void *thmain(void* arg)
{
    while(true)
    {
        printf("线程%lu开始等待条件信号...\n",pthread_self());  // 获取当前线程号
        pthread_cond_wait(&cond,&mutex);        // 等待条件信号,注意参数 
        printf("线程%lu等待条件信号成功...\n\n",pthread_self());
    }
}

void handle(int sig)
{
    printf("发送条件信号...\n");
    /* pthread_cond_signal(&cond);         // 唤醒等待条件信号的一个线程。*/
    pthread_cond_broadcast(&cond);      // 唤醒等待条件信号的全部线程。
}

        运行

        发送信号

        结果如下:

 

        可以看到,全部的线程都已经被激活。

        关于条件变量

        关于条件变量,这节课主要讲了条件变量的一些基本函数的 *** 作,以及条件变量必须搭配互斥锁使用,关于条件变量更深入的知识,以后还会继续发文讲解。

总结

        通过上面的文章,我们大致可以知道,线程同步的方式无非就是加锁。但是,需要注意,锁虽然好用,但是不能说随便加,想加多少加多少,这样的想法是不行的,太多的锁很可能会产生死锁。另外,太多的锁也会降低程序的性能。

        所以,在做项目中我们尽量保证不要使用过多的锁,加锁的时间也是越短越好。

        此外,关于信号量的知识、条件变量讲解与用途,以后都会继续发文,感谢大家的观看!

结语

        由于这几天考试的问题,导致这篇文章都是零零散散凑时间写出来的,很多时候写下一个锁的知识的时候就已经忘了上一个锁写的什么了。所以如果文章有错误,请大佬一定指出来,非常感谢大家!如果有看不懂的地方也可以指出来,我会再做更改,尽量通俗易懂。这两天断断续续的写这篇文章脑子难免有些懵懵的,请见谅。另外,大家觉得文章不错的话可以关注一下我,以后看到我发文章,没准会激励起你学习的心,哈哈一起加油吧!!!

        凑个封面

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/2991613.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存