目录
1.线程同步概念
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解
1.问题当我们解决了线程互斥,随着我们使用锁,出现了这样的问题:一个线程在解锁后仍然有极大的概率再次获取锁进行实现。那么出现了其他的线程无法进行同样的函数调用。那么这些挂起的线程就出现所谓的线程饥饿问题
2.分析与解决
1.问题的出现其实就是因为释放锁的线程离锁的资源太近了。一旦所谓的线程仍然要调用本函数时,那么它争夺到锁的概率就比所谓的其他的线程要高
2.根据上面的情况,我们追加一条设定,就是一旦释放锁后,那么该线程的等待的优先级一定要拍到其他的线程之后
3.线程饥饿之所以是一个问题。就是因为针对于申请到锁的线程一直在执行函数调用,而不断的释放锁和申请锁,似乎对于当前线程是一件十分浪费时间的事情。那针对于其他的线程,都被挂起,那么这些线程就没有进行任何函数调用,也浪费时间
2.生产消费者模型
1.模型介绍
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
1.生产者之间互斥(因为生产数据在仓库中保存是覆盖式的);消费者之间互斥(因为消费数据不能像之前抢票一样出现线程安全问题);消费者与生产者之间互斥同步(互斥:防止消费者消费一般生产者覆盖数据;同步:使得消费者与生产者之间不出现反复确认是否需要数据)
2.优点:
解耦(消费者与生产者之间有缓冲,不强相关联)
支持忙闲不均(生产者可以在消费者不消费时生产;同理消费者也可以)
提高效率(之后解释)
2.条件变量
1.线程的执行遵循: 加锁 -- 判断执行条件 -- 执行 -- 解锁
2.不过,光光是判断条件是不对的,因为某个执行流一定会出现不断加锁,判断条件,条件不满足,解锁,继续加锁如此循环往复的动作。那么我们是否存在解决的手段呢?答案是肯定的,只需要加入条件变量。条件变量就是一个类似于排队等待的数据结构,当线程进入执行时,一旦判断执行条件失败,此刻我们就可以通过所谓的条件变量使得线程解锁后进行阻塞等待,直到条件能使得线程继续进行执行。
初始化和销毁
等待
pthread_wait的第二个参数一定是被使用的锁,并且当等待时,该cond会将当前的锁释放,交给其他线程执行。一旦被重新唤起,线程将会重新在此处进行执行,并且同时得到锁
释放
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int tickets = 1000; void *start_routine(void *args) { std::string name = static_cast<const char *>(args); while (true) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); std::cout << name << "->" << tickets << std::endl; tickets--; pthread_mutex_unlock(&mutex); } } int main() { pthread_t t[5]; for (int i = 0; i < 5; i++) { char *name = new char[64]; snprintf(name, 64, "thread %d", i + 1); pthread_create(t + i, nullptr, start_routine, (void *)name); } while (true) { sleep(1); pthread_cond_signal(&cond); } for (int i = 0; i < 5; i++) { pthread_join(t[i], nullptr); } return 0; }
访问有明显的替换。
3.实现生产消费模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
const static int gmaxcap=500; template<class T> class BlockQueue { public: BlockQueue(const int &maxcap=gmaxcap) :_maxcap(gmaxcap) { pthread_mutex_init(&_mutex,nullptr); pthread_cond_init(&_pcond,nullptr); pthread_cond_init(&_ccond,nullptr); } void push(const T &in) //输入型参数 const& { pthread_mutex_lock(&_mutex); while(is_full()) //伪唤醒,使用while进行持续判断 { pthread_cond_wait(&_pcond,&_mutex); //生产条件不满足,生产者进行等待 } //在这里一定不满 _q.push(in); //此时一定有数据,所以唤醒消费者访问 pthread_cond_signal(&_ccond); pthread_mutex_unlock(&_mutex); sleep(1); } void pop(T* out) //输出型参数 * 输入输出型为 & { pthread_mutex_lock(&_mutex); while(is_empty()) { pthread_cond_wait(&_ccond,&_mutex); //生产条件不满足,生产者进行等待 } //在这里一定不为空 *out = _q.front(); _q.pop(); //此时一定有空间,所以唤醒生产者访问 pthread_cond_signal(&_pcond); pthread_mutex_unlock(&_mutex); } ~BlockQueue() { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_pcond); pthread_cond_destroy(&_ccond); } private: bool is_empty() { return _q.empty(); } bool is_full() { return _q.size()==_maxcap; } private: std::queue<T> _q; int _maxcap; //队列元素上限 pthread_mutex_t _mutex; pthread_cond_t _pcond; //生产者对应的条件变量 pthread_cond_t _ccond; //消费者对应的条件变量 };
需要注意的就是判断条件变量时是需要循环判断的,以免由于多线程出现公共数据访问的问题。
提高效率问题
1.如果消费者和生产者之间互相制约,使得其效率并没有起到什么作用,依然是串行执行的。但是所谓的生产与消费并不是只有这个模型下的工作。
2.生产者线程构建任务需要花费时间,同理消费者线程完成任务需要花费时间。我们需要花费的任务可能十分复杂。那么其实当线程拿到数据后,其他线程也可以拿其他数据,相互不影响,并发的执行提高了效率。