条件变量是什么变量(条件变量的使用)

条件变量是什么变量(条件变量的使用)资源保护 进程的资源保护 对于进程来说 由于每个进程空间是完全独立的 相互间不可能篡改对方进程空间的数据 所以进程空间内部的数据 资源 保护的非常到位 不需要加什么额外的保护机制 只有当它们共享操作第三方资源时才会涉及到资源保护问题 比如共享操作第三方文件 或者共享内存 的数据时 才会使用到进程信号量这样的资源保护机制 我们在讲进程 IPC 的时候就说过 虽然进程信号量被划到 IPC 中

大家好,我是讯享网,很高兴认识大家。



资源保护

进程的资源保护

对于进程来说,由于每个进程空间是完全独立的,相互间不可能篡改对方进程空间的数据,所以进程空间内部的数据(资源)保护的非常到位,不需要加什么额外的保护机制。只有当它们共享操作第三方资源时才会涉及到资源保护问题,比如共享操作第三方文件(或者共享内存)的数据时,才会使用到进程信号量这样的资源保护机制。我们在讲进程IPC的时候就说过,虽然进程信号量被划到“IPC”中,但是进程信号量的作用实际上是借助通信来实现资源(数据)保护。对于进程来说,因为进程空间的独立性,因此进程资源的保护很到位,反倒是进程间共享数据很困难,因此OS提供了管道、消息队列等进程间通信机制。

线程的资源保护

对于线程来说,由于进程内部的所有线程共享进程空间,因此线程间使用全局变量即可实现数据共享,数据通信的实现非常容易,不过数据共享越是容易,数据相互篡改的危险性就越高,因此对于线程来说,需要重点考虑如何保护资源(数据),防止相互篡改。

总结

进程:进程空间天然是独立的,因此进程间资源的保护是天然的(现成的),需要重点关心的进程间的通信

线程:多线程天然的共享进程空间,因此线程数据共享是天然的(现成的),需要重点关心的是资源的保护

线程的资源保护机制

C线程的资源保护机制有:互斥锁、信号量、条件变量

互斥锁

互斥锁的作用就是用来实现互斥的。原理同进程信号量那里的互斥。

互斥锁使用的步骤

①定义一个互斥锁(变量)

②初始化互斥锁:预设互斥锁的初始值

③加锁解锁

④进程退出时销毁互斥锁

初始化互斥锁的函数

原型 

:restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码

功能

初始化定义的互斥锁。所谓初始化,就是设置互斥锁所需要的值。

参数

mutex:互斥锁,需要我们自己定义。

比如:pthread_mutex_t mutex;

pthread_mutex_t是一个结构体类型,所以mutex实际上是一个结构体变量。

attr:互斥锁的属性

设置NULL表示使用默认属性,除非我们想要实现一些互斥锁的特殊功能,否则默认属性就够用了。

返回值

总是返回0,所以这个函数不需要进行出错处理。

加锁解锁函数

原型

功能

pthread_mutex_lock:阻塞加锁

如果锁没有解开时,当前线程尝试加锁时会阻塞,直到加锁成功为止。

兄弟函数:pthread_mutex_trylock(pthread_mutex_t *mutex)

非阻塞加锁,加锁成功是最好,如果不成功就错误返回,不会阻塞。

pthread_mutex_unlock:解锁,解锁不会阻塞

参数

mutex:需要加锁和解锁的互斥锁

返回值

成功返回0,失败返回错误号。

销毁互斥锁函数

原型

功能

销毁互斥锁。所谓销毁,说白了就是删除互斥锁相关的数据,释放互斥锁数据所占用的各种内存资源。

参数

mutex:需要被销毁的互斥锁

返回值

成功返回0,失败返回非零错误号

再说说互斥锁

初始化互斥锁有两种方法

第1种:使用pthread_mutex_init实现

第2种:定义互斥锁时直接初始化实现

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 与 pthread_mutex_init(&mutex, NULL);的功能是一样的,都是将互斥锁设置为快锁。

怎么理解pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ?

这句话的本来面目是:struct mutex* mutex = {,,,...};

这个是典型的结构体变量的初始化,pthread_mutex_t其实就是对struct mutex* typedef后的类型,PTHREAD_MUTEX_INITIALIZER的宏值为{,,,...}。

以下写法对不对

等价于

说白了这就是在尝试给结构体变量进行整体赋值,我们讲c时说过,结构体变量是不能够整体赋值的,所以写法是错误的。如果你想给结构体变量赋值的话,只能一个一个的给结构体成员赋值来实现。其实我们调用pthread_mutex_init函数来初始化互斥锁时,这个函数设置初始值的方式,就是给mutex这个结构体变量的成员一个一个的赋值来实现的。

所以说:

调用pthread_mutex_init函数来给mutex设置初始值时,实现的本质是结构体赋值。

使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER方式给mutex设置初始值时,实现的本质是结构体初始化。

代码演示

结合2种初始化互斥锁的方式。代码中我两种都用了,这种没问题,互斥锁大不了重复初始化一次,不影响

有关PTHREAD_MUTEX_INITIALIZER宏

实际上除了这个宏外,还有两个宏,分别是:

PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP

PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP

PTHREAD_MUTEX_INITIALIZER:快锁

快速互斥锁(或叫阻塞互斥锁),简称快锁。快锁的特点是:

①加锁不成功是会阻塞,如果不想阻塞必须使用pthread_mutex_trylock来加锁,而不是pthread_mutex_lock。

②对于同一把快锁来说,不能多次加锁,否者会出错

③已经解锁的快锁也不能再次解锁,否者会出错

PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:检错互斥锁

使用pthread_mutex_lock加锁时,如果加锁不成功不会阻塞,会直接出错返回。加锁不成功就直接错误返回,所以才被称为“检错互斥锁”。

PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:递归互斥锁。

特点:

①同一把锁可多次枷锁,每加一次锁,加锁次数就会加1

②解锁时,解锁的顺序刚好与加锁顺序相反,每解锁一次,加锁次数就会减1。

正是由于可以重复的加锁和解锁,所以才被称为递归加锁。

pthread_mutex_init(&mutex, NULL)设置是什么锁

当第二个参数为NULL时,默认设置的是快锁。如果你想通过pthread_mutex_init函数,将mutex初始化出“检错锁”和“递归锁”的话,我们必须通过第二个参数进行相应的属性设置来实现。这种实现方法比较麻烦。

如果你真想使用“检错锁”和“递归锁”,建议还是使用直接初始化的方式,这样会更方便。

pthread_mutex_t mutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

线程信号量

进程信号量与线程信号量

线程的信号量与进程的信号量几乎完全相同,只不过一个是给进程用的,另一个是给线程用的。我们使用进程信号量时,我们自己往往还需要二次封装,线程的信号量函数则不需要,直接就可以使用,所以线程的信号量使用起来更加容易,应该说使用难度非常低。

二值信号量和多值信号量

对于线程信号量来说,也分为二值信号量和多值信号量,同样的我们这里只讲二值信号量。使用二值信号量时,往往用来实现“互斥”和“同步”。如果想实现互斥的话,更多的还是使用前面讲的互斥锁来实现,因为线程互斥锁提供了更多可自供选择的功能,比如可以设置为“检错锁”、“递归锁”等。如果你只是想实现简单互斥的话,不管是使用线程互斥锁的“快锁”来实现,还是使用线程信号量来实现,最终所实现的互斥效果都是一样的。

信号量的使用步骤

①定义信号量集合

(a)用于互斥时,集合中只需要一个信号量。

(b)用于同步时,有几个线程需要同步,集合中就需要包含几个信号量

②初始化集合中的每个信号量

设置初始值,二值信号量的初始值要么是0、要么是1。

(a)如果是用于互斥,基本都是设置为1

(b)如果是用于同步,看具体情况

③p、v操作

p操作:信号量值-1

V操作:信号量值+1

④进程结束时,删除线程信号量集合


讯享网

初始化信号量的函数

原型

功能

初始化线程信号量集合中的某个信号量,给它设置一个初始值。

参数

sem:信号量集合中的某个信号量

信号量集合需要我们自己定义,比如:sem_t sem[3]

线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。

sem[0]:第一个信号量

sem[1]:第二个信号量

sem[2]:第三个信号量

sem_init(&sem[0], int pshared, unsigned int value);

线程信号量集合其实就是自定义的一个数组,而进程信号量集合则是通过semget函数创建的。我们只要把数组定义为全局变量,所有的线程即可共享使用,不像进程信号量,需要semid才能实现共享操作。

pshared

0:给线程使用

!0:可以给进程使用

不过对于进程来说,我们更多的还是使用进程信号量,因为线程信号量用到进程上时,存在一些不稳定的情况。

value:初始化值。对于二值信号量来说,要么是1,要么是0。

返回值

成功返回0,失败返回-1,errno被设置。注意信号量的错误号不是返回的,而是设置到errno中。

PV操作函数

原型

功能

sem_wait:

阻塞p操作集合中某个信号量,值-1。如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止。

sem_wait的兄弟函数

int sem_trywait(sem_t *sem):不阻塞

如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。

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

可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有p操作成功就是出错返回,不再阻塞。

sem_post

对某个信号量进行v操作,v操作不存在阻塞问题。v操作成功后,信号量的值会+1

参数

sem:p操作的某个信号量。比如:sem_wait(&sem[0]);

返回值

适用于2个函数,成功返回0,失败返回-1,errno被设置。

删除信号量函数

原型

功能

删除某个信号量,把所有信号量都删除后,信号量集合就被销毁。这与删除进程信号量集合有所不同,对于进程信号量集合来说,只要删除一个信号量,整个集合即被删除,但是对于线程信号量来说,需要一个一个的删除,当所有信号量都删除完后,集合才被删除完毕。

参数

sem:信号量集合中某个信号量

返回值

成功返回0,失败返回-1,errno被设置。

代码演示

使用信号量实现互斥

使用信号量实现同步

比如有三个线程(1主线程,2个次线程),分别打印、、,使用同步让他们顺序的打印、、。

使用进程信号量实现进程同步时,有多少个进程需要同步,集合中就需要包含几个信号量。同样的,使用线程信号量实现同步时,有几个线程需要同步,集合中就需要包含几个信号量。

条件变量

线程配合工作的例子

eg:主线程对va变量循环+1,次线程发现va==5时,打印va的值并将va清0,如果va的值!=5就什么都不做

采用最笨的实现方法:次线程循环检测va的值,然后做出相应的响应。代码如下

这种循环检测的方法虽然简单,但是存在很大的问题,那就是当va不满足时,次线程会一直在不停的循环检测,cpu执行次线程的while时其实是在空转,白白浪费cpu的资源。

最好的方式是,当va条件不满足时就应该让次线程休眠(阻塞),等主线程将va准备好时,主动通知次线程,将它唤醒,像这样的解决方式,我们就可以使用条件变量来实现。

条件变量的作用

多线程配合工作时,当线程检测到某条件不满足时就休眠,直到别的线程将条件准备好,然后通过条件变量将其唤醒。条件变量需要在互斥锁的配合下才能工作。

条件变量的使用步骤

①定义一个条件变量(全局变量)。由于条件变量需要互斥锁的配合,所以还需要定义一个线程互斥锁。

②初始化条件变量

③使用条件变量

④删除条件变量,也需要把互斥锁删除。

初始化条件变量函数

原型

功能

初始化条件变量,与互斥锁的初始化类似。

也可以直接初始化:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//与互斥锁的初始化的原理是一样的

参数

cond:条件变量

attr:用于设置条件变量的属性,设置为NULL,表示使用默认属性

返回值

成功返回0,失败返回非零错误号

等待条件变量函数

原型

功能

检测条件变量cond,如果cond没有被设置,表示条件还不满足,别人还没有对cond进行设置,此时pthread_cond_wait会休眠(阻塞),直到别的线程设置cond表示条件准备好后,才会被唤醒。

参数

cond:条件变量

mutex:和条件变量配合使用的互斥锁

返回值

成功返回0,失败返回非零错误号

兄弟函数

多了第三个参数,用于设置阻塞时间,如果条件不满足时休眠(阻塞),但是不会一直休眠,当时间超时后,如果cond还没有被设置,函数不再休眠。

设置条件变量函数

原型

功能

当线程将某个数据准备好时,就可以调用该函数去设置cond,表示条件准备好了,pthread_cond_wait检测到cond被设置后就不再休眠(被唤醒),线程继续运行,使用别的线程准备好的数据来做事。当调用pthread_cond_wait函数等待条件满足的线程只有一个时,就是用pthread_cond_signal来唤醒,如果说有好多线程都调用pthread_cond_wait在等待时,使用int pthread_cond_broadcast(pthread_cond_t *cond);它可以将所有调用pthread_cond_wait而休眠的线程都唤醒。

参数

cond:条件变量

返回值

成功返回0,失败返回非零错误号

删除条件变量函数

原型

功能

删除条件变量

参数

cond:条件变量

返回值

成功返回0,失败返回非零错误号

代码演示

 

 

 

小讯
上一篇 2025-06-02 09:12
下一篇 2025-06-15 10:53

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/195933.html