当前位置: 首页> 游戏> 评测 > 网络规划设计师教程电子版2023_广州疫情最新情况通报_微信小程序开发流程_seo快速排名软件方案

网络规划设计师教程电子版2023_广州疫情最新情况通报_微信小程序开发流程_seo快速排名软件方案

时间:2025/7/10 18:09:31来源:https://blog.csdn.net/2301_79881188/article/details/143086804 浏览次数:0次
网络规划设计师教程电子版2023_广州疫情最新情况通报_微信小程序开发流程_seo快速排名软件方案

1.背景概念

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
using namespace std;
struct customer
{pthread_t tid;string name;
};
int g_tickets = 1000;
//定义一个全局变量的锁,保证所有线程都用同一个锁
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
void *GetTicket(void *args)
{customer *c = (customer *)args;while (true){pthread_mutex_lock(&g_mutex); //加锁if (g_tickets > 0){usleep(1000);cout << c->name << " is getting a ticket " << g_tickets << endl;g_tickets--;pthread_mutex_unlock(&g_mutex); //解锁}else{pthread_mutex_unlock(&g_mutex); //解锁break;}}return nullptr;
}int main()
{vector<customer> custs(5);for (int i = 0; i < 5; i++){custs[i].name = "customer-" + to_string(i + 1);pthread_create(&custs[i].tid, nullptr, GetTicket, &custs[i]);}for (int i = 0; i < 5; i++){pthread_join(custs[i].tid, nullptr);}return 0;
}

例如上诉的抢票系统中,我们有5个线程来共享临界资源 g_tickets,为了保证临界资源的原子性,我们对其采用了锁。执行之后出现如下情况:

我们发现都是一个线程抢到票,即都是同一个线程占用了锁,这是因为线程在占用锁的时候,是不受控制的,这就有可能导致一个竞争能力强的线程,从头到尾都占用一个锁。为了总是避免一个线程占用锁,于是出现了线程同步的概念。

在Linux中,线程同步是指多个线程在执行过程中,通过一些机制来协调它们对数据和资源的访问,让线程能够按照某种特定的顺序访问临界资源,以避免竞态条件、数据不一致和饥饿等问题。线程同步可以通过多种方式实现,包括互斥锁、条件变量、读写锁和信号量等。

2.条件变量

条件变量是一种同步机制,它允许一个或多个线程在某个条件不满足时阻塞,并在条件满足时被唤醒。条件变量通常与互斥锁一起使用,以确保线程安全。在多线程编程中,条件变量用于协调线程之间的执行顺序,特别是在生产者-消费者问题、读者-写者问题等场景中非常有用。

条件变量的类型为 pthread_cond_t,与互斥量类型 pthread_mutex_t 相似。

创建条件变量

方法一,宏定义全局条件变量

pthread_cond_t xxx = PTHREAD_COND_INITIALIZER;

全局的条件变量必须用宏PTHREAD_COND_INITIALIZER进行初始化,并且不需要手动销毁。

方法二,使用 pthread_cond_init 函数

int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t * attr);

参数:

  • cond:指向条件变量的指针,该变量在函数调用后将被初始化为条件变量。
  • attr:指向条件变量属性的指针,用于设置条件变量的属性。如果传递NULL,则使用默认属性。

返回值:函数成功返回0,失败则返回错误码

等待条件变量

我们要想让线程按一定顺序访问临界资源,就需要对这些线程进行排队,而这时我们就用到了队列来一个一个排队访问。

如上图有三个线程thread-1thread-2thread-3,这三个线程争夺一个临界资源。

thread-1申请到了锁mutex,但是由于我们给这个临界资源添加了条件变量,此时thread-1不能直接访问临界资源,而是进入等待队列,并且释放持有的锁:

由于锁被释放,后续线程可以继续申请这个锁。于是thread-2thread-3也分别申请到了mutex,通过相同的方式进入了等待队列:

现在所有线程都在等待队列中,这些线程因为没有满足特定条件,所以不能访问临界资源。但是为什么要进等待队列呢?

因为要保证线程同步,也就是说,一开始谁先申请到锁访问临界资源,那么后续条件满足时,就让谁先来访问这个资源。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

参数:

  • cond:指向条件变量的指针。
  • mutex:指向互斥锁的指针,该互斥锁在调用pthread_cond_wait之前必须由当前线程持有的,而后线程释放该锁,从而让其他线程也可以申请锁,进入等待队列。

返回值:函数成功返回0,失败则返回错误码。

pthread_cond_wait函数的工作原理是将当前线程放入条件变量cond的等待队列中,并自动释放mutex互斥锁。

唤醒等待线程

线程进入等待队列进行排队后需要被唤醒,于是我们会使用如下函数

int pthread_cond_signal(pthread_cond_t *cond);
  • cond: 指向条件变量的指针,说明要唤醒哪一个条件变量下等待的一个线程。
  • 返回值: 函数返回 0 表示成功,非零值表示出错。

当一个线程调用 pthread_cond_wait 并因此进入等待状态时,它会释放与其关联的互斥锁,并等待条件变量被信号化。pthread_cond_signal 的作用是唤醒一个等待该条件变量的线程,这些线程随后可以尝试重新获取互斥锁,并继续执行。

比如刚刚三个线程都进入了等待队列:

当使用pthread_cond_signal唤醒一个线程时:线程重新获得之前释放的锁mutex,随后访问临界区代码。

当后续条件再次满足,thread-2thread-3也会依次再次获得锁,从而访问到临时资源。

假设现在thread-1访问完毕临界资源后,立马再次申请了锁:

由于条件变量的存在,therad-1不能直接访问资源,要去等待队列等待,此时thread-1进入等待队列尾部:

这样就可以避免一个线程一直占用临界资源,从而完成线程同步了。

int pthread_cond_broadcast(pthread_cond_t *cond);
  • cond: 指向条件变量的指针,说明要唤醒哪一个条件变量下等待的所有线程。
  • 返回值: 函数返回 0 表示成功,非零值表示出错。

还是刚才的三个线程都处于等待队列的情况:


当用pthread_cond_broadcast唤醒所有线程,此时所有被唤醒的线程再次竞争同一把锁,竞争到锁的线程才访问临界资源。当前一个线程访问完毕后,剩下的线程继续竞争,再访问临界资源。

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);
  • cond: 指向条件变量的指针。
  • 返回值: 如果函数成功销毁条件变量,返回 0;如果条件变量正在使用或已被销毁,返回非零值。

pthread_cond_destroy 函数用于销毁一个条件变量,释放与该条件变量相关联的资源。在调用 pthread_cond_destroy 之前,需要确保没有任何线程正在等待或正在使用该条件变量。如果条件变量正在被使用,或者有线程等待在该条件变量上,函数调用将失败,并返回错误码 EBUSY

3.使用示例

我们对一开始所用的抢票代码进行优化,保证不同线程按一定顺序访问临界资源,防止出现饥饿问题。

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
using namespace std;
struct customer
{pthread_t tid;string name;
};
int g_tickets = 1000;
//定义一个全局变量的锁,保证所有线程都用同一个锁
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
//定义一个全局条件变量
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;void *GetTicket(void *args)
{customer *c = (customer *)args;while (true){pthread_mutex_lock(&g_mutex); //加锁if (g_tickets > 0){usleep(1000);cout << c->name << " is getting a ticket " << g_tickets << endl;g_tickets--;pthread_cond_wait(&g_cond,&g_mutex); //进入等待对列等待条件变量pthread_mutex_unlock(&g_mutex); //解锁}else{pthread_mutex_unlock(&g_mutex); //解锁break;}}return nullptr;
}int main()
{vector<customer> custs(5);for (int i = 0; i < 5; i++){custs[i].name = "customer-" + to_string(i + 1);pthread_create(&custs[i].tid, nullptr, GetTicket, &custs[i]);}//唤醒线程while(g_tickets > 0){usleep(1000);pthread_cond_signal(&g_cond);}for (int i = 0; i < 5; i++){pthread_join(custs[i].tid, nullptr);}return 0;
}

这样就避免了都是同一个线程占用临界资源。

关键字:网络规划设计师教程电子版2023_广州疫情最新情况通报_微信小程序开发流程_seo快速排名软件方案

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: