2024年C C++最新POSIX线程详解_posix 线程 mutex condition,震撼发布

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

#include <stdio.h>
#include <unistd.h>
int myglobal;
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;

void *thread_function(void *arg) {
int i,j;
for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
j=myglobal;
j=j+1;
printf(“.”);
fflush(stdout);
sleep(1);
myglobal=j;
pthread_mutex_unlock(&mymutex);
}
return NULL;
}
int main(void) {
pthread_t mythread;
int i;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
printf(“error creating thread.”);
abort();
}
for ( i=0; i<20; i++) {
pthread_mutex_lock(&mymutex);
myglobal=myglobal+1;
pthread_mutex_unlock(&mymutex);
printf(“o”);
fflush(stdout);
sleep(1);
}
if ( pthread_join ( mythread, NULL ) ) {
printf(“error joining thread.”);
abort();
}
printf(“\nmyglobal equals %d\n”,myglobal);
exit(0);
}


此时pthread\_mutex\_lock() 和 pthread\_mutex\_unlock() 函数调用,如同“在施工中”标志一样,将正在修改和读取的某一特定共享数据包围起来。其他线程访问时继续睡眠,直到该线程完成对其的操作。


![在这里插入图片描述](https://img-blog.csdnimg.cn/20190413221832138.png)


###### 等待条件之POSIX条件变量


互斥对象是线程程序必需的工具,但它们并非万能的。例如,如果线程正在等待共享数据内某个条件出现,那会发生什么呢?  
 1)使用忙查询的方法非常浪费时间和资源,效率非常低。代码可以反复对互斥对象锁定和解锁,以检查值的任何变化。同时,还要快速将互斥对象解锁,以便其它线程能够进行任何必需的更改。这是一种非常可怕的方法,因为线程需要在合理的时间范围内频繁地循环检测变化。  
 2)解决这个问题的最好方法是使用pthread\_cond\_wait() 调用来等待特殊条件发生。当线程在等待满足某些条件时使线程进入睡眠状态。一旦条件满足,还需要一种方法以唤醒因等待满足特定条件而睡眠的线程。如果能够做到这一点,线程代码将是非常高效的,并且不会占用宝贵的互斥对象锁。这正是 POSIX 条件变量能做的事!  
 了解 pthread\_cond\_wait() 的作用非常重要 – 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。


###### 条件变量的概念


通常在程序里,我们使用条件变量来表示等待”某一条件”的发生。虽然名叫”条件变量”,但是它本身并不保存条件状态,本质上条件变量仅仅是一种通讯机制:当有一个线程在等待(pthread\_cond\_wait)某一条件变量的时候,会将当前的线程挂起,直到另外的线程发送信号(pthread\_cond\_signal)通知其解除阻塞状态。


由于要用额外的共享变量保存条件状态(这个变量可以是任何类型比如bool),由于这个变量会同时被不同的线程访问,因此需要一个额外的mutex保护它。


《Linux系统编程手册》也有这个问题的介绍:



> 
> A condition variable is always used in conjunction with a mutex. The mutex provides mutual exclusion for accessing the shared variable, while the condition variable is used to signal changes in the variable’s state.
> 
> 
> 


条件变量总是结合mutex使用,条件变量就共享变量的状态改变发出通知,mutex就是用来保护这个共享变量的。


[cpp官网描述](https://bbs.csdn.net/topics/618668825)


###### pthread\_cond\_wait实现步骤


首先,我们使用条件变量的接口实现一个简单的生产者-消费者模型,avail就是保存条件状态的共享变量,它对生产者线程、消费者线程均可见。不考虑错误处理,先看生产者实现:



pthread_mutex_lock(&mutex);
avail++;
pthread_mutex_unlock(&mutex);

pthread_cond_signal(&cond); /* Wake sleeping consumer */


因为avail对两个线程都可见,因此对其修改均应该在mutex的保护之下,再来看消费者线程实现:



for (;😉
{
pthread_mutex_lock(&mutex);
while (avail == 0)
pthread_cond_wait(&cond, &mutex);

while (avail > 0)
{
    /\* Do something \*/
    avail--;
}
pthread\_mutex\_unlock(&mutex);

}


当”avail==0”时,消费者线程会阻塞在pthread\_cond\_wait()函数上。如果pthread\_cond\_wait()仅需要一个pthread\_cond\_t参数的话,此时mutex已经被锁,要是不先将mutex变量解锁,那么其他线程(如生产者线程)永远无法访问avail变量,也就无法继续生产,消费者线程会一直阻塞下去。  
 因此pthread\_cond\_wait()对mutex解锁,然后进入睡眠状态,等待cond以接收POSIX 线程“信号”。一旦接收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自pthread\_cond\_signal() 或 pthread\_cond\_broadcast() 调用的信号),它就会苏醒。但 pthread\_cond\_wait() 没有立即返回 – 它还要做一件事:重新锁定 mutex。



> 
> 理解后提问:调用 pthread\_cond\_wait() 之 前,互斥对象必须处于什么状态?pthread\_cond\_wait() 调用返回之后,互斥对象处于什么状态?这两个问题的答案都是“锁定”。
> 
> 
> 


综上,pthread\_cond\_wait()函数大致会分为3个部分:


1.解锁互斥量mutex  
 2.阻塞调用线程,直到当前的条件变量收到通知  
 3.重新锁定互斥量mutex


其中1和2是原子操作,也就是说在pthread\_cond\_wait()调用线程陷入阻塞之前其他的线程无法获取当前的mutex,也就不能就该条件变量发出通知。


###### 虚假唤醒


前面判断条件状态的时候avail > 0放在了while循环中,而不是if中,这是因为pthread\_cond\_wait()阻塞在条件变量上的时候,即使其他的线程没有就该条件变量发出通知(pthread\_cond\_signal()/pthread\_cond\_broadcast()),条件变量也有可能会自己醒来(pthread\_cond\_wait()函数返回),因此需要重新检查一下共享变量上的条件成不成立,确保条件变量是真的收到了通知,否则继续阻塞等待。关于虚假唤醒的相关介绍,可以戳这里查看维基百科下面的几个引用:[https://en.wikipedia.org/wiki/Spurious\_wakeup。](https://bbs.csdn.net/topics/618668825)


##### 三、工作队列的实现



> 
> 这里摘抄[IBM第三部分应用实现代码](https://bbs.csdn.net/topics/618668825):
> 
> 
> 


在这个方案中,我们创建了许多工作程序线程。每个线程都会检查 wq(“工作队列”),查看是否有需要完成的工作。如果有需要完成的工作,那么线程将从队列中除去一个节点,执行这些特定工作,然后等待新的工作到达。


与此同时,主线程负责创建这些工作程序线程、将工作添加到队列,然后在它退出时收集所有工作程序线程。您将会遇到许多 C 代码,好好准备吧!  
 **队列**  
 需要队列是出于两个原因。首先,需要队列来保存工作作业。还需要可用于跟踪已终止线程的数据结构。还记得前几篇文章(请参阅本文结尾处的 参考资料)中,我曾提到过需要使用带有特定进程标识的 pthread\_join 吗?使用“清除队列”(称作 “cq”)可以解决无法等待 任何已终止线程的问题(稍后将详细讨论这个问题)。以下是标准队列代码。将此代码保存到文件 queue.h 和 queue.c:


queue.h



/* queue.h
** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
** Author: Daniel Robbins
** Date: 16 Jun 2000
*/
typedef struct node {
struct node *next;
} node;
typedef struct queue {
node *head, *tail;
} queue;
void queue_init(queue *myroot);
void queue_put(queue *myroot, node *mynode);
node *queue_get(queue *myroot);


queue.c



/* queue.c
** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
** Author: Daniel Robbins
** Date: 16 Jun 2000
**
** This set of queue functions was originally thread-aware. I
** redesigned the code to make this set of queue routines
** thread-ignorant (just a generic, boring yet very fast set of queue
** routines). Why the change? Because it makes more sense to have
** the thread support as an optional add-on. Consider a situation
** where you want to add 5 nodes to the queue. With the
** thread-enabled version, each call to queue_put() would
** automatically lock and unlock the queue mutex 5 times – that’s a
** lot of unnecessary overhead. However, by moving the thread stuff
** out of the queue routines, the caller can lock the mutex once at
** the beginning, then insert 5 items, and then unlock at the end.
** Moving the lock/unlock code out of the queue functions allows for
** optimizations that aren’t possible otherwise. It also makes this
** code useful for non-threaded applications.
**
** We can easily thread-enable this data structure by using the
** data_control type defined in control.c and control.h. */

#include <stdio.h>
#include “queue.h”

void queue_init(queue *myroot) {
myroot->head=NULL;
myroot->tail=NULL;
}

void queue_put(queue *myroot,node *mynode) {
mynode->next=NULL;
if (myroot->tail!=NULL)
myroot->tail->next=mynode;
myroot->tail=mynode;
if (myroot->head==NULL)
myroot->head=mynode;
}

node *queue_get(queue *myroot) {
//get from root
node *mynode;
mynode=myroot->head;
if (myroot->head!=NULL)
myroot->head=myroot->head->next;
return mynode;
}


data\_control 代码  
 我编写的并不是线程安全的队列例程,事实上我创建了一个“数据包装”或“控制”结构,它可以是任何线程支持的数据结构。看一下 control.h:



#include <pthread.h>

typedef struct data_control {
pthread_mutex_t mutex;
pthread_cond_t cond;
int active;
} data_control;


现在您看到了 data\_control 结构定义,以下是它的视觉表示:


所使用的 data\_control 结构  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190413224529738.png)  
 图像中的锁代表互斥对象,它允许对数据结构进行互斥访问。黄色的星代表条件变量,它可以睡眠,直到所讨论的数据结构改变为止。on/off 开关表示整数 “active”,它告诉线程此数据是否是活动的。在代码中,我使用整数 active 作为标志,告诉工作队列何时应该关闭。以下是 control.c:  
 control.c



/* control.c
** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
** Author: Daniel Robbins
** Date: 16 Jun 2000
**
** These routines provide an easy way to make any type of
** data-structure thread-aware. Simply associate a data_control
** structure with the data structure (by creating a new struct, for
** example). Then, simply lock and unlock the mutex, or
** wait/signal/broadcast on the condition variable in the data_control
** structure as needed.
**
** data_control structs contain an int called “active”. This int is
** intended to be used for a specific kind of multithreaded design,
** where each thread checks the state of “active” every time it locks
** the mutex. If active is 0, the thread knows that instead of doing
** its normal routine, it should stop itself. If active is 1, it
** should continue as normal. So, by setting active to 0, a
** controlling thread can easily inform a thread work crew to shut
** down instead of processing new jobs. Use the control_activate()
** and control_deactivate() functions, which will also broadcast on
** the data_control struct’s condition variable, so that all threads
** stuck in pthread_cond_wait() will wake up, have an opportunity to
** notice the change, and then terminate.
*/

#include “control.h”

int control_init(data_control *mycontrol) {
int mystatus;
if (pthread_mutex_init(&(mycontrol->mutex),NULL))
return 1;
if (pthread_cond_init(&(mycontrol->cond),NULL))
return 1;
mycontrol->active=0;
return 0;
}

int control_destroy(data_control *mycontrol) {
int mystatus;
if (pthread_cond_destroy(&(mycontrol->cond)))
return 1;
if (pthread_cond_destroy(&(mycontrol->cond)))
return 1;
mycontrol->active=0;
return 0;
}
int control_activate(data_control *mycontrol) {
int mystatus;
if (pthread_mutex_lock(&(mycontrol->mutex)))
return 0;
mycontrol->active=1;
pthread_mutex_unlock(&(mycontrol->mutex));
pthread_cond_broadcast(&(mycontrol->cond));
return 1;
}

int control_deactivate(data_control *mycontrol) {
int mystatus;
if (pthread_mutex_lock(&(mycontrol->mutex)))
return 0;
mycontrol->active=0;
pthread_mutex_unlock(&(mycontrol->mutex));
pthread_cond_broadcast(&(mycontrol->cond));
return 1;
}


**调试时间**  
 在开始调试之前,还需要一个文件。以下是 dbug.h:



#define dabort()
{ printf(“Aborting at line %d in source file %s\n”,__LINE__,__FILE__); abort(); }


此代码用于处理工作组代码中的不可纠正错误。


**工作组代码**  
 说到工作组代码,以下就是:  
 workcrew.c



#include <stdio.h>
#include <stdlib.h>
#include “control.h”
#include “queue.h”
#include “dbug.h”

/* the work_queue holds tasks for the various threads to complete. */

struct work_queue {
data_control control;
queue work;
} wq;

/* I added a job number to the work node. Normally, the work node
would contain additional data that needed to be processed. */

typedef struct work_node {
struct node *next;
int jobnum;
} wnode;

/* the cleanup queue holds stopped threads. Before a thread
terminates, it adds itself to this list. Since the main thread is
waiting for changes in this list, it will then wake up and clean up
the newly terminated thread. */

struct cleanup_queue {
data_control control;
queue cleanup;
} cq;

/* I added a thread number (for debugging/instructional purposes) and
a thread id to the cleanup node. The cleanup node gets passed to
the new thread on startup, and just before the thread stops, it
attaches the cleanup node to the cleanup queue. The main thread
monitors the cleanup queue and is the one that performs the
necessary cleanup. */

typedef struct cleanup_node {
struct node *next;
int threadnum;
pthread_t tid;
} cnode;

void *threadfunc(void *myarg) {

wnode *mywork;
cnode *mynode;

mynode=(cnode *) myarg;

pthread_mutex_lock(&wq.control.mutex);

while (wq.control.active) {
while (wq.work.head==NULL && wq.control.active) {
pthread_cond_wait(&wq.control.cond, &wq.control.mutex);
}
if (!wq.control.active)
break;
//we got something!
mywork=(wnode *) queue_get(&wq.work);
pthread_mutex_unlock(&wq.control.mutex);
//perform processing…
printf(“Thread number %d processing job %d\n”,mynode->threadnum,mywork->jobnum);
free(mywork);
pthread_mutex_lock(&wq.control.mutex);
}

pthread_mutex_unlock(&wq.control.mutex);

pthread_mutex_lock(&cq.control.mutex);
queue_put(&cq.cleanup,(node *) mynode);
pthread_mutex_unlock(&cq.control.mutex);
pthread_cond_signal(&cq.control.cond);
printf(“thread %d shutting down…\n”,mynode->threadnum);
return NULL;

}

#define NUM_WORKERS 4

int numthreads;

void join_threads(void) {
cnode *curnode;

printf(“joining threads…\n”);

while (numthreads) {
pthread_mutex_lock(&cq.control.mutex);

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

int numthreads;

void join_threads(void) {
cnode *curnode;

printf(“joining threads…\n”);

while (numthreads) {
pthread_mutex_lock(&cq.control.mutex);

[外链图片转存中…(img-SJE5ExxC-1715553045604)]
[外链图片转存中…(img-xVHV1pD5-1715553045604)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值