Linux之线程同步

目录

一、问题引入

二、实现线程同步的方案——条件变量

1、常用接口:

2、使用示例


一、问题引入

我们再次看看上次讲到的多线程抢票的代码:这次我们让一个线程抢完票之后不去做任何事。

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <time.h>
#include <pthread.h>

using namespace std;
#define THREAD_NUM 5

class threaddata
{
public:
    threaddata(const string &s, pthread_mutex_t *m)
        : name(s), mtx(m)
    {}

public:
    string name;
    pthread_mutex_t *mtx;
};

int ticket = 100;

void *getticket(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        pthread_mutex_lock(td->mtx); 
        if (ticket > 0)              
        {
            usleep(rand() % 10000);
            cout << td->name << ":"
                 << " " << ticket << endl;
            ticket--;
            pthread_mutex_unlock(td->mtx); 
        }
        else
        {
            pthread_mutex_unlock(td->mtx); 
            break;
        }
    }
    delete td;
    return nullptr;
}

int main()
{
    pthread_mutex_t mtx;
    pthread_mutex_init(&mtx, nullptr);

    pthread_t t[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; i++)
    {
        string name = "thread ";
        name += to_string(i + 1);
        threaddata *td = new threaddata(name, &mtx);
        pthread_create(t + i, nullptr, getticket, (void *)td);
    }

    for (int i = 0; i < THREAD_NUM; i++)
        pthread_join(t[i], nullptr);

    pthread_mutex_destroy(&mtx);

    return 0;
}

运行结果:

我们这就发现了一个问题,对于抢票系统,我们看到的是只有一个线程5在一直连续抢票,没有其他的线程。这很不合理。

这是因为如果个别线程的竞争力特别强,每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,那么它就可以一直抢票。这就可能导致其他线程长时间竞争不到锁,造成了其他线程的饥饿问题(无法抢票)。虽然,你是拿到锁后再去访问临界资源,并且最后还释放了锁,由于竞争能力太强,可以一直拿到锁,这没有错,但这不合理。

为了解决这个问题,我们增加一个限制:当一个线程释放锁后,这个线程不能立马再次申请锁,该线程必须排到这个锁的资源等待队列的最后。这样,我们就有了线程同步:我们在保证数据安全的情况下让这些线程按照一定的顺序进行临界资源的访问,这就是线程同步。

竞态条件:因为时序问题,而导致程序异常,我们称为竞态条件。

二、实现线程同步的方案——条件变量

当一个线程互斥地访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了

例如一个线程访问队列时,发现队列为空,它只能等待,直到其他线程将一个节点添加到队列中。这种情况就需要用到条件变量。

1、常用接口:

1、条件变量的定义和初始化

​
NAME
       pthread_cond_destroy, pthread_cond_init - destroy and initialize condition variables

SYNOPSIS
       #include <pthread.h>

       //销毁
       int pthread_cond_destroy(pthread_cond_t *cond);
       //初始化
       int pthread_cond_init(pthread_cond_t *restrict cond,
              const pthread_condattr_t *restrict attr);
       //全局和静态变量初始化
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2、线程等待临界资源: 

pthread_cond_wait 功能:一就是让线程在特定的条件变量下等待,二就是让线程在等待时释放对应的互斥锁。当线程被唤醒时,该函数会帮助我们线程获取锁。

#include <pthread.h>
//特定时间阻塞等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abstime);
//等待
int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);

 3、唤醒线程去访问临界资源

#include <pthread.h>
// 唤醒所有等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

// 唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);

注:1、条件变量通常需要配合互斥锁一起使用。

2、条件变量的使用:一个线程等待条件变量的条件成立而被挂起;另一个线程使条件成立后唤醒等待的线程。

3、等待的时候往往是在临界区内等待的。(加锁与解锁之间的区域进行等待)

4、线程被唤醒,是在之前进行等待的地方被唤醒。

2、使用示例

有了线程同步,我们就可以改进我们之前的抢票系统的代码:

#include <iostream>
#include <string>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;
#define THREADNUM 3
typedef void *(*func)(void *argc);

class threaddata
{
public:
    threaddata(pthread_mutex_t *mtx, pthread_cond_t *cond, const string &name)
        : mtx_(mtx), cond_(cond), name_(name)
    {}

public:
    pthread_mutex_t *mtx_;
    pthread_cond_t *cond_;
    string name_;
};

int ticket = 100;

void *getticket(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        pthread_mutex_lock(td->mtx_);
        pthread_cond_wait(td->cond_, td->mtx_); // 在加锁和解锁之间进行等待
        if (ticket > 0)
        {
            usleep(rand() % 10000);
            cout << td->name_ << ":"
                 << " " << ticket << endl;
            ticket--;
            pthread_mutex_unlock(td->mtx_);
        }
        else
        {
            pthread_mutex_unlock(td->mtx_);
            break;
        }
    }
    delete td;
    return nullptr;
}

void *fun1(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        getticket((void *)td);
        sleep(1);
    }
}

void *fun2(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        getticket((void *)td);
        sleep(1);
    }
}

void *fun3(void *arg)
{
    threaddata *td = (threaddata *)arg;
    while (true)
    {
        getticket((void *)td);
        sleep(1);
    }
}

int main()
{
    srand((unsigned long)time(nullptr) ^ getpid() ^ 433);
    pthread_mutex_t mtx;
    pthread_cond_t cond;
    pthread_mutex_init(&mtx, nullptr);
    pthread_cond_init(&cond, nullptr);
    pthread_t t[THREADNUM];
    func fun[THREADNUM] = {fun1, fun2, fun3};

    for (int i = 0; i < THREADNUM; i++)
    {
        string name = "thread ";
        name += to_string(i + 1);
        threaddata *td = new threaddata(&mtx, &cond, name);
        pthread_create(t + i, nullptr, fun[i], (void *)td);
    }

    sleep(5);

    while (true)
    {
        pthread_cond_signal(&cond);
        sleep(1);
    }

    for (int i = 0; i < THREADNUM; i++)
        pthread_join(t[i], nullptr);

    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);

    return 0;
}

如果我们每次都想将在该条件变量下等待的所有线程进行唤醒,可以将代码中的pthread_cond_signal函数改为pthread_cond_broadcast函数。 

此时我们会发现唤醒这三个线程时具有明显的顺序性,因为这些线程启动时默认都会在该条件变量下去等待,而我们每次都唤醒的是在当前条件变量下等待的头部线程,当该线程执行完代码后会继续排到等待队列的尾部进行等待。

  • 93
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 67
    评论
### 回答1: Linux线程同步一头歌 线程同步Linux中非常重要的一个概念,它可以保证多个线程之间的数据访问不会出现冲突,从而保证程序的正确性和稳定性。线程同步的实现方式有很多种,比如互斥锁、条件变量、信号量等等。在使用线程同步的时候,需要注意一些细节问题,比如死锁、优先级反转等等。只有掌握了线程同步的原理和技巧,才能写出高效、稳定的多线程程序。 ### 回答2: Linux是一种广泛使用的操作系统,能够支持多任务处理。在这个操作系统中,线程同步是非常重要的概念。当多个线程被同时执行时,它们之间的相互影响可能会导致许多问题。为了避免这些问题,我们需要使用线程同步来确保线程之间的协调和合作。 线程同步的目的是为了让多个线程协调工作,而不是相互干扰。它的实现方式有很多种,例如信号量、互斥锁、条件变量等。这些线程同步机制都有自己的特点和适用情况,根据实际应用场景选择合适的机制是非常重要的。 在Linux系统中,常用的线程同步机制之一是互斥锁。使用互斥锁可以让一个线程独占一个资源,其他线程需要等待锁被释放才能访问这个资源。这样可以实现线程之间的互斥,并且避免了资源竞争问题。 另外,Linux还提供了条件变量机制。条件变量可以让线程之间在某些条件满足时进行通信,从而实现线程的协作。比如,一个线程可以在某个条件满足时向另一个线程发送信号,让它开始工作。这样可以避免线程之间的忙等待问题,并且能够提高程序的效率。 无论是使用互斥锁还是条件变量,线程同步都是Linux系统中非常重要的一个概念。它可以帮助我们解决多线程并发问题,提高程序的性能和可靠性。因此,学习和掌握线程同步机制是Linux开发者必备的技能之一。 ### 回答3: Linux操作系统中,线程同步是非常重要的一部分,它能确保多个线程之间的执行顺序、数据的读写顺序以及互斥关系等问题,进而保证程序的正确性和稳定性。线程同步避免了多个线程同时读写同一个共享变量时出现的问题,如数据混乱、竞争条件、死锁等。 Linux中常用的线程同步技术包括互斥锁、条件变量、读写锁、信号量等。互斥锁是最基础的线程同步技术,通过给临界区加锁的方式保证同一时刻只有一个线程进入。条件变量用于在多线程之间传递信息,例如线程A需要等待线程B的信号才能继续执行。读写锁用于在读操作和写操作之间进行区分,实现读写分离。信号量则可以保证同一时刻只有指定的数量的线程可以访问共享资源。 线程同步不但在Linux操作系统中有广泛的应用,而且在其他操作系统和软件中也起到了非常重要的作用。因此,熟练掌握各种线程同步技术是非常重要的。在应用中,我们需要根据不同的情况选择合适的线程同步技术,在多个线程之间合理地分配资源和控制执行顺序,从而为程序的正确性和性能提供保障。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值