<Linux线程池、线程安全(单例模式、STL、智能指针)、读者写者问题及线程扩展与总结>——《Linux》

news2025/3/10 11:23:13

目录

1.线程池

1.1 线程池:

1.2 线程池的应用场景:

1.3 线程池的种类:

1.4 线程池示例:

1.5 线程池编程模拟实现:

 2.  线程安全的单例模式

2.1 什么是单例模式

2.2 什么是设计模式

2.3 单例模式的特点

2.3.1 饿汉实现方式和懒汉实现方式

2.3.2 饿汉方式实现单例模式

2.3.3 懒汉方式实现单例模式

2.3.4 懒汉方式实现单例模式(线程安全版本)

3.STL、智能指针、线程安全

3.1 STL中的容器是否是线程安全的?

3.2 智能指针是否是线程安全的?

4. 其他常见的各种锁

5.  读者写者问题

5.1 读写锁

5.2 读写锁接口

5.3 编程模拟实现读写锁案例:

后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知


1.线程池

/* 1.1 线程池:

*       一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

* 1.2 线程池的应用场景:

*       1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

*       2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

*       3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

* 1.3 线程池的种类:

* 1.4 线程池示例:

*  1. 创建固定数量线程池,循环从任务队列中获取任务对象,

*  2. 获取到任务对象后,执行任务对象中的任务接口

*/

1.5 线程池编程模拟实现:

version1:

 shell脚本:

ps -aL | grep -E 'master|follower'

ThreadPool.hpp:

 

 

#pragma once
#include <iostream>
#include <cassert>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "Log.hpp"
#include "Lock.hpp"
using namespace std;

int gThreadNum = 5;
template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum)
    :threadNum_(threadNum)
    ,isStart_(false)
    {
        assert(threadNum_ > 0);
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T>&) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance) //仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }
        return instance;
    }
    //类内成员, 成员函数,都有默认参数this
    //static函数无法访问类内成员,只能通过接口,这里通过传入的this指针
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        prctl(PR_SET_NAME, "follower");
        while (1)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            //这个任务就被拿到了线程的上下文中
            T t = tp->pop();
            tp->unlockQueue();

            // for debug
            int one, two;
            char oper;
            t.get(&one, &two, &oper);
            //规定,所有的任务都必须有一个run方法
            Log() << "新线程完成计算任务: " << one << oper << two << "=" << t.run() << "\n";
        }
    }
    void start()
    {
        assert(!isStart_);
        for (int i = 0; i < threadNum_; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);
        }
        isStart_ = true;
    }
    void push(const T &in)
    {
        lockQueue();
        taskQueue_.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }

private:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool haveTask() { return !taskQueue_.empty(); }
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
    T pop()
    {
        T temp = taskQueue_.front();
        taskQueue_.pop();
        return temp;
    }

private:
    bool isStart_;
    int threadNum_;
    queue<T> taskQueue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *instance;
    // const static int a = 100;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

ThreadPoolTest.cc:

 

#include "ThreadPool.hpp"
#include "Task.hpp"
#include <ctime>
#include <thread>

// 如何对一个线程进行封装, 线程需要一个回调函数(支持lambda)自行实现
// class tread{
// };

int main()
{
    prctl(PR_SET_NAME, "master");

    const string operators = "+/*/%";
    // unique_ptr<ThreadPool<Task> > tp(new ThreadPool<Task>());
    unique_ptr<ThreadPool<Task> > tp(ThreadPool<Task>::getInstance());
    tp->start();

    srand((unsigned long)time(nullptr) ^ getpid() ^ pthread_self());
    // 派发任务的线程
    while(true)
    {
        int one = rand()%50;
        int two = rand()%10;
        char oper = operators[rand()%operators.size()];
        Log() << "主线程派发计算任务: " << one << oper << two << "=?" << "\n";
        Task t(one, two, oper);
        tp->push(t);
        sleep(1);
    }
}

Lock.hpp:

#pragma once
#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock_, nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&lock_);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock_);
    }

private:
    pthread_mutex_t lock_;
};

class LockGuard
{
public:
    LockGuard(Mutex *mutex) : mutex_(mutex)
    {
        mutex_->lock();
        std::cout << "加锁成功..." << std::endl;
    }

    ~LockGuard()
    {
        mutex_->unlock();
        std::cout << "解锁成功...." << std::endl;
    }

private:
    Mutex *mutex_;
};

Task.hpp:

#pragma once
#include <iostream>
#include <string>

class Task
{
public:
    Task() : elemOne_(0), elemTwo_(0), operator_('0')
    {
    }
    Task(int one, int two, char op) : elemOne_(one), elemTwo_(two), operator_(op)
    {
    }
    int operator() ()
    {
        return run();
    }
    int run()
    {
        int result = 0;
        switch (operator_)
        {
        case '+':
            result = elemOne_ + elemTwo_;
            break;
        case '-':
            result = elemOne_ - elemTwo_;
            break;
        case '*':
            result = elemOne_ * elemTwo_;
            break;
        case '/':
        {
            if (elemTwo_ == 0)
            {
                std::cout << "div zero, abort" << std::endl;
                result = -1;
            }
            else
            {
                result = elemOne_ / elemTwo_;
            }
        }

        break;
        case '%':
        {
            if (elemTwo_ == 0)
            {
                std::cout << "mod zero, abort" << std::endl;
                result = -1;
            }
            else
            {
                result = elemOne_ % elemTwo_;
            }
        }
        break;
        default:
            std::cout << "非法操作: " << operator_ << std::endl;
            break;
        }
        return result;
    }
    int get(int *e1, int *e2, char *op)
    {
        *e1 = elemOne_;
        *e2 = elemTwo_;
        *op = operator_;
    }
private:
    int elemOne_;
    int elemTwo_;
    char operator_;
};

 Log.hpp:

 

#pragma once
#include <iostream>
#include <ctime>
#include <pthread.h>

std::ostream &Log()
{
    std::cout << "Fot Debug |" << " timestamp: " << (uint64_t)time(nullptr) << " | " << " Thread[" << pthread_self() << "] | ";
    return std::cout;
}

makefile:

 

CC=g++
FLAGS=-std=c++11
LD=-lpthread
bin=threadpool
src=ThreadPoolTest.cc

$(bin):$(src)
	$(CC) -o $@ $^ $(LD) $(FLAGS)
.PHONY:clean
clean:
	rm -f $(bin)

 version 2:

  

 g++  threadpool.cpp -o threadpool -pthread -lrt -std=c++0x
/*threadpool.h*/
/* 线程池:

*       一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

* 线程池的应用场景:

*       1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

*       2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

*       3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

* 线程池的种类:

* 线程池示例:

*  1. 创建固定数量线程池,循环从任务队列中获取任务对象,

*  2. 获取到任务对象后,执行任务对象中的任务接口

*/
/*threadpool.hpp*/

#ifndef __M_TP_H__
#define __M_TP_H__
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#define MAX_THREAD 5
typedef bool (*handler_t)(int);
class ThreadTask
{
private:
    int _data;
    handler_t _handler;

public:
    ThreadTask() : _data(-1), _handler(NULL) {}
    ThreadTask(int data, handler_t handler)
    {
        _data = data;
        _handler = handler;
    }
    void SetTask(int data, handler_t handler)
    {
        _data = data;
        _handler = handler;
    }
    void Run()
    {
        _handler(_data);
    }
};
class ThreadPool
{
private:
    int _thread_max;
    int _thread_cur;
    bool _tp_quit;
    std::queue<ThreadTask *> _task_queue;
    pthread_mutex_t _lock;
    pthread_cond_t _cond;

private:
    void LockQueue()
    {
        pthread_mutex_lock(&_lock);
    }
    void UnLockQueue()
    {
        pthread_mutex_unlock(&_lock);
    }
    void WakeUpOne()
    {
        pthread_cond_signal(&_cond);
    }
    void WakeUpAll()
    {
        pthread_cond_broadcast(&_cond);
    }
    void ThreadQuit()
    {
        _thread_cur--;
        UnLockQueue();
        pthread_exit(NULL);
    }
    void ThreadWait()
    {
        if(_tp_quit)
        {
            ThreadQuit();
        }
        pthread_cond_wait(&_cond, &_lock);
    }
    bool IsEmpty()
    {
        return _task_queue.empty();
    }
    static void *thr_start(void *arg)
    {
        ThreadPool *tp = (ThreadPool *)arg;
        while (1)
        {
            tp->LockQueue();
            while (tp->IsEmpty())
            {
                tp->ThreadWait();
            }
            ThreadTask *tt;
            tp->PopTask(&tt);
            tp->UnLockQueue();
            tt->Run();
            delete tt;
        }
        return NULL;
    }

public:
    ThreadPool(int max = MAX_THREAD) : _thread_max(max), _thread_cur(max),
                                       _tp_quit(false)
    {
        pthread_mutex_init(&_lock, NULL);
        pthread_cond_init(&_cond, NULL);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }
    bool PoolInit()
    {
        pthread_t tid;
        for (int i = 0; i < _thread_max; i++)
        {
            int ret = pthread_create(&tid, NULL, thr_start, this);
            if (ret != 0)
            {
                std::cout << "create pool thread error\n";
                return false;
            }
        }
        return true;
    }
    bool PushTask(ThreadTask *tt)
    {
        LockQueue();
        if (_tp_quit)
        {
            UnLockQueue();
            return false;
        }
        _task_queue.push(tt);
        WakeUpOne();
        UnLockQueue();
        return true;
    }
    bool PopTask(ThreadTask **tt)
    {
        *tt = _task_queue.front();
        _task_queue.pop();
        return true;
    }
    bool PoolQuit()
    {
        LockQueue();
        _tp_quit = true;
        UnLockQueue();
        while (_thread_cur > 0)
        {
            WakeUpAll();
            usleep(1000);
        }
        return true;
    }
};
#endif
/*main.cpp*/
bool handler(int data)
{
    srand(time(NULL));
    int n = rand() % 5;
    printf("Thread: %p Run Tast: %d--sleep %d sec\n", pthread_self(), data, n);
    sleep(n);
    return true;
}
int main()
{
    int i;
    ThreadPool pool;
    pool.PoolInit();
    for (i = 0; i < 10; i++)
    {
        ThreadTask *tt = new ThreadTask(i, handler);
        pool.PushTask(tt);
    }
    pool.PoolQuit();
    return 0;
}

 2.  线程安全的单例模式

2.1 什么是单例模式

单例模式是一种 "经典的, 常用的, 常考的" 设计模式.

2.2 什么是设计模式

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

2.3 单例模式的特点

某些类, 只应该具有一个对象(实例), 就称之为单例.
例如一个男人只能有一个媳妇.
在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

2.3.1 饿汉实现方式和懒汉实现方式

[洗完的例子]
吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.
懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.

2.3.2 饿汉方式实现单例模式

template <typename T>
class Singleton
{
    static T data;

public:
    static T *GetInstance()
    {
        return &data;
    }
};
只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.

2.3.3 懒汉方式实现单例模式

template <typename T>
class Singleton
{
    static T *inst;

public:
    static T *GetInstance()
    {
        if (inst == NULL)
        {
            inst = new T();
        }
        return inst;
    }
};
存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
但是后续再次调用, 就没有问题了.

2.3.4 懒汉方式实现单例模式(线程安全版本)

 

注意事项:
1. 加锁解锁的位置
2. 双重 if 判定, 避免不必要的锁竞争
3. volatile关键字防止过度优化

3.STL、智能指针、线程安全

3.1 STL中的容器是否是线程安全的?

不是.
原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

3.2 智能指针是否是线程安全的?

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

4. 其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。(乐观锁是由程序员实现的)
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁?

5.  读者写者问题

5.1 读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。
注意:写独占,读共享,读锁优先级高

5.2 读写锁接口

设置读写优先
读写锁的行为
当前锁状态读锁请求写锁请求
无锁

可以

可以
读锁可以阻塞
写锁阻塞阻塞

 

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref); 
/* 
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/

 

初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);

销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

5.3 编程模拟实现读写锁案例:

version 1:

 

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

int board = 0;
pthread_rwlock_t rw;
void *reader(void* args)
{
    const char *name = static_cast<const char *>(args);
    cout << "run..." << endl;
    while(true)
    {
        pthread_rwlock_rdlock(&rw);
        cout << "reader read : " << board << "tid: " << pthread_self() << endl;
        sleep(1);
        pthread_rwlock_unlock(&rw);
    }
}

void *writer(void *args)
{
    const char *name = static_cast<const char *>(args);
    sleep(1);
    while(true)
    {
        pthread_rwlock_wrlock(&rw);
        board++;
        cout << "I am writer" << endl;
        sleep(1);
        pthread_rwlock_unlock(&rw);
    }
}

int main()
{
    pthread_rwlock_init(&rw, nullptr);
    pthread_t r1,r2,r3,r4,r5,r6, w;
    pthread_create(&r1, nullptr, reader, (void*)"reader");
    pthread_create(&r2, nullptr, reader, (void*)"reader");
    pthread_create(&r3, nullptr, reader, (void*)"reader");
    pthread_create(&r4, nullptr, reader, (void*)"reader");
    pthread_create(&r5, nullptr, reader, (void*)"reader");
    pthread_create(&r6, nullptr, reader, (void*)"reader");
    pthread_create(&w, nullptr, writer, (void*)"writer");


    pthread_join(r1, nullptr);
    pthread_join(r2, nullptr);
    pthread_join(r3, nullptr);
    pthread_join(r4, nullptr);
    pthread_join(r5, nullptr);
    pthread_join(r6, nullptr);
    pthread_join(w, nullptr);

    pthread_rwlock_destroy(&rw);
    return 0;
}

 

version 2:

 

#include <vector>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <pthread.h>

volatile int ticket = 1000;
pthread_rwlock_t rwlock;
void *reader(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        pthread_rwlock_rdlock(&rwlock);
        if (ticket <= 0)
        {
            pthread_rwlock_unlock(&rwlock);
            break;
        }
        printf("%s: %d\n", id, ticket);
        pthread_rwlock_unlock(&rwlock);
        usleep(1);
    }
    return nullptr;
}
void *writer(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        pthread_rwlock_wrlock(&rwlock);
        if (ticket <= 0)
        {
            pthread_rwlock_unlock(&rwlock);
            break;
        }
        printf("%s: %d\n", id, --ticket);
        pthread_rwlock_unlock(&rwlock);
        usleep(1);
    }
    return nullptr;
}
struct ThreadAttr
{
    pthread_t tid;
    std::string id;
};
std::string create_reader_id(std::size_t i)
{
    // 利用 ostringstream 进行 string 拼接
    std::ostringstream oss("thread reader ", std::ios_base::ate);
    oss << i;
    return oss.str();
}
std::string create_writer_id(std::size_t i)
{
    // 利用 ostringstream 进行 string 拼接
    std::ostringstream oss("thread writer ", std::ios_base::ate);
    oss << i;
    return oss.str();
}
void init_readers(std::vector<ThreadAttr> &vec)
{
    for (std::size_t i = 0; i < vec.size(); ++i)
    {
        vec[i].id = create_reader_id(i);
        pthread_create(&vec[i].tid, nullptr, reader, (void *)vec[i].id.c_str());
    }
}
void init_writers(std::vector<ThreadAttr> &vec)
{
    for (std::size_t i = 0; i < vec.size(); ++i)
    {
        vec[i].id = create_writer_id(i);
        pthread_create(&vec[i].tid, nullptr, writer, (void *)vec[i].id.c_str());
    }
}
void join_threads(std::vector<ThreadAttr> const &vec)
{
    // 我们按创建的 逆序 来进行线程的回收
    for (std::vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin(); it != vec.rend(); ++it)
    {
        pthread_t const &tid = it->tid;
        pthread_join(tid, nullptr);
    }
}
void init_rwlock()
{
#if 0   // 写优先
 pthread_rwlockattr_t attr;
 pthread_rwlockattr_init(&attr);
 pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
 pthread_rwlock_init(&rwlock, &attr);
 pthread_rwlockattr_destroy(&attr);
#else   // 读优先,会造成写饥饿
    pthread_rwlock_init(&rwlock, nullptr);
#endif
}
int main()
{
    // 测试效果不明显的情况下,可以加大 reader_nr
    // 但也不能太大,超过一定阈值后系统就调度不了主线程了
    const std::size_t reader_nr = 1000;
    const std::size_t writer_nr = 2;
    std::vector<ThreadAttr> readers(reader_nr);
    std::vector<ThreadAttr> writers(writer_nr);
    init_rwlock();
    init_readers(readers);
    init_writers(writers);
    join_threads(writers);
    join_threads(readers);
    pthread_rwlock_destroy(&rwlock);
}

g++ -std=c++11 readwrite.cc -o readwrite -lpthread

 

后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/116927.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驱动相关基础

1.程序分类 1.1 裸机程序&#xff1a;直接运行在对应硬件的的程序 1.2 应用程序&#xff1a;只能运行在对应操作系统上的程序 2. 计算机系统的层次结构 2.1 无操作系统的简单的两层结构 2.2 有操作系统的四层层次结构 3. 操作系统 狭义&#xff1a;给应用程序提供运行环…

Python图像处理【7】采样、卷积与离散傅里叶变换

采样、卷积与离散傅里叶变换0. 前言1. 图像傅里叶变换1.1 傅里叶变换基础1.2 傅里叶变换应用1.3 逆傅里叶变换应用2. 利用采样改变图像分辨率2.1 上采样2.2 下采样小结系列链接0. 前言 采样 (Sampling) 是用于选择/丢弃图像像素的空间操作&#xff0c;通常用于增加/减小图像大…

(byte)1658385462>>16=-40,怎么算的?

正文 在 Github 项目mongo-java-driver有一个类ObjectId.java&#xff0c;它的作用是生成唯一 id 的&#xff0c;它的核心实现是下面这样一段代码 [1]&#xff1a; public void putToByteBuffer(final ByteBuffer buffer) {notNull("buffer", buffer);isTrueArgume…

【数据结构Java版】树与二叉树的相关知识全解

目录 一、树型结构 &#xff08;1&#xff09;树的定义 &#xff08;2&#xff09;树的基本术语 &#xff08;3&#xff09;树的存储结构 二、二叉树 &#xff08;1&#xff09;二叉树的定义 &#xff08;2&#xff09;两种特殊二叉树 1.满二叉树 2.完全二叉树 &…

CSS中你可能不知道的Selectors特性

CSS中你可能不知道的Selectors特性本文作者为奇舞团前端开发工程师引言最近看了2022年全球CSS调查报告&#xff0c;发现了一些不常见的伪类和伪元素。伪类:has()html<div><h1>H1</h1><h2>H2</h2><p>h1{margin: 0 0 0.25rem 0}</p> &…

设置访问SSH为密钥访问

1.制作密钥对 ssh-keygen输入会问两个问题 设置公私钥名称&#xff08;可以留白&#xff0c;直接回车&#xff09;设置公私钥密码&#xff08;可以留白&#xff0c;直接回车&#xff09; 第一次输入第二次确认 留空确认的话&#xff0c;生成公私钥。共有两个文件 # 私钥 id…

Rxjava源码分析实践(三)【RxJava基本原理分析之订阅流】

本节,我们从Rxjava使用代码入手,去结合自己已有的知识体系,加查阅部分源码验证的方式,来一起探索一下Rxjava实现的基本原理。 为了本文原理分析环节,可以被更多的人理解、学习,所以小编从初学者的角度,从使用入手,一点点的分析了其中的源码细节、思想,建议大家随着本文…

NCMMSC论文介绍 | 探索语音自监督模型的高效融合算法

本文介绍了清华大学语音与音频技术实验室&#xff08;SATLab&#xff09;与上海交通大学跨媒体语言智能实验室&#xff08;X-LANCE&#xff09;合作的NCMMSC录用论文&#xff1a;Exploring Effective Fusion Algorithms for Speech Based Self-Supervised Learning Models。该论…

动态列合并更新

【问题】 I have one query, would be great if anyone can help me out on this. In SQL, I have two tables with same column names. Want to query if there is any difference in the column values and if yes will update the values(in the first table) else if the…

【工具类】后台Mock工具类

目录一、介绍二、使用方法1. Controller层定义接口2. 编写json文件3. 开启AOP4. 调用接口验证三、源码一、介绍 Controller层定义完接口后&#xff0c;不需要写业务逻辑。编写Json文件&#xff0c;调用接口时返回json文件的数据。 优点&#xff1a; 设计阶段即可定义好接口&…

Centos 图形化yum管理工具 - yum Extender

文章目录背景安装开启yum-GUI工具 - yumexyum list installed列出软件包的依赖yum cleam背景 作为一个yum工程师&#xff0c;长期备受yum 命令的煎熬。 难道yum就乜有一个GUI管理界面吗&#xff1f; yum Extender (简称 yumex ) , 是 yum 的图形化操作界面。可以通过 yumex 方…

ActiveMQ高级特性和大厂面试常考重点

目录 一、引入消息队列之后该如何保证其高可用性 二、异步投递Async Sends 三、延迟投递和定时投递 四、ActiveMQ消费重试机制 五、死信队列 六、如何保证消息不被重复消费呢?幂等性问题你谈谈 一、引入消息队列之后该如何保证其高可用性 ActiveMQ集群模式_zoeil的博客-…

【机器学习】KNN 算法介绍

文章目录一、KNN 简介二、KNN 核心思想实例分析&#xff1a;K 值的影响三、KNN 的关键1. 距离计算1. 闵可夫斯基距离2. 曼哈顿距离3. 欧氏距离4. 切比雪夫距离5. 余弦距离总结2. K值选择四、KNN 的改进&#xff1a;KDTree五、KNN 回归算法参考链接一、KNN 简介 KNN 算法&#…

想在微信上使用chatGPT?小程序?公众号?企业微信,最终还是选择了企业微信版本的chatgpt

chatgpt的接口现在都可以正常用了&#xff0c;但是怎么把这个功能放在手机上随用随开呢&#xff1f;微信个人聊天版本小程序版本公众号版本企业微信版本逻辑实现方式微信个人聊天版本 网上很多微信机器人版本的&#xff0c;但是原理是网页版微信&#xff0c;很多账号都不能登陆…

golang指针

指针 区别于C/C中的指针&#xff0c;Go语言中的指针不能进行偏移和运算&#xff0c;是安全指针。 要搞明白Go语言中的指针需要先知道3个概念&#xff1a;指针地址、指针类型和指针取值。 1.1. Go语言中的指针 Go语言中的函数传参都是值拷贝&#xff0c;当我们想要修改某个变…

Linux中如何理解线程?线程ID到底是什么?

朋友们好&#xff0c;这里简要介绍了进程和线程的区别以及对LINUX中线程ID的理解&#xff0c;本人目前理解尚浅&#xff0c;若文中有表述不当的地方还望理解并指正&#xff0c;谢谢大家&#xff01; 文章目录一&#xff1a;进程和线程二&#xff1a;线程ID和进程地址空间布局一…

5 项目部署

5.1 Linux-项目部署 5.1.1 环境 5.1.1.1 开发环境&#xff08;dev&#xff09; 外部用户无法访问&#xff0c;开发人员使用&#xff0c;版本变动很大 平时大家大多是在Windows或者Mac操作系统下去编写代码进行开发. 在开发环境中安装大量的软件&#xff0c;这样会导致环境的稳…

2022 年度盘点 | 更成熟的 AI,更破圈的技术狂欢

By 超神经内容一览&#xff1a;2022 年 AI 领域发展不断提速&#xff0c;新技术成果纷纷落地&#xff0c;模型迭代加速升级。本文总结了 2022 年 AI 领域各大公司的技术成就。关键词&#xff1a;年终盘点 大厂 技术创新2022 年在此起彼伏的咳嗽声中接近尾声&#xff0c;这一…

onCreate、onSaveInstanceState、onRestoreInstance一个参数和两个参数

Android Studio移动应用开发——onCreate、onSaveInstanceState、onRestoreInstance一个参数和两个参数_dear_jing的博客-程序员宅基地 - 程序员宅基地 在做Android生命周期实验过程中&#xff0c;把 Log.i(TAG, "(1) onCreate()") 写到了含有两个参数的函数 onSave…

HTML5 元素拖放

文章目录HTML5 元素拖放概述触发事件实现元素拖放功能dataTransfer元素拖动效果垃圾箱效果HTML5 元素拖放 概述 在HTML5中&#xff0c;我们只需要给元素添加一个draggable属性&#xff0c;然后设置该属性值为true&#xff0c;就能实现元素的拖放。 拖放&#xff0c;指的是“…