在上一篇文章里提高,Linux 并没有像 win 那样真正意义上的线程,而是用进程去模拟线程的,所以 Linux 中的线程创建等一系列的操作由 NPTL POSIX线程库实现。
接口介绍
代码演示
线程ID及进程地址空间布局
注意:在线程中调用 exit 也是终止该进程所在的进程。想要单独终止进程有3种方式
pthread_exit介绍
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel
为什么需要线程等待呢?
已经退出的线程,其空间没有被释放,仍然在该进程的地址空间内。
创建的新进程不会复用新进程的地址空间,造成资源泄露。
函数接口介绍 pthread_join
默认情况下,新创建的线程是需要被等待的,新线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄露。
如果不关心线程的返回值,join是一种负担,这个时候我们可以告诉系统,当先线程退出的时候,自动释放线程的资源,需要进行线程分离。
接口 int pthread_detach(pthread_t thread)
1、所有线程都必须遵守:对临界区进行保护
2、lock(加锁) ===> 访问临界区 ==> unlock(解锁)
3、所有的线程都必须先看到同一把锁,锁本身也是临界资源,申请锁的过程也是两态的,即lock具有原子性,unlock 也具有原子性。
4、lock > 访问临界区(占用一定的时间处理)=> unlock ,在特定线程或者进程拥有锁的时候,期间有新线程来申请锁,一定是申请不到的!那个新线程将阻塞,将新线程/进程 对应的 PCB 投入到等待队列中,特定的 线程或者进程 unlock 之后,进行线程或进程的唤醒操作!
5、一次保证只有一个线程进入临界区,访问临界资源,就叫做互斥。
我们看一个简单的抢票代码:4个线程抢票
结果却出现了,车票代码为负数的情况
解析原因
大部分情况线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个进程,其他线程无法获得这种变量。
但是有时候许多变量需要在线程间共享,这样的变量称为共享变量,通过数据的共享完成线程之间的交互。
多个线程并发的操作共享变量,会带来一些问题,比如上面的抢票代码。
那么具体到这个抢票代码的问题,我们来分析一下:
if 语句判断条件为真以后,代码可以并发的切换到其他线程
usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
–ticket 操作本身就不是一个原子操作
要解决以上问题需要做到以下3点
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
用 mutex 互斥量优化上面的抢票代码
联系
区别
避免死锁的算法
例如,A 线程访问队列时,发现队列为空,它只能等待,直到 B 线程将一个节点添加到队列中,此时需要线程同步。需要条件变量。那么线程同步的定义:在保证数据安全的前提下(加锁),让多个执行流(线程)按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
需要多线程协同高效的完成任务
1、如果条件不满足,等待,释放锁。
2、通知机制
使用一组接口
通过本章,我们要知道,什么是生产者消费者模型,为什么会存在这种模型,这种模型该如何设计并编码,并通过一个基于阻塞队列的生产者消费者模型,阐述 pthread_cond_wait,为何需要互斥量,和条件变量的规范使用。
BlockQueue.hpp
#pragma once
#include
#include
#include
//封装的任务
class Task {
public:
int _x;
int _y;
public:
Task(){
}
Task(int x, int y):_x(x),_y(y)
{
}
int Run()
{
return _x + _y;
}
};
template<class T>
class BlockQueue {
private:
std::queue<T> _q;
size_t _cap;
pthread_mutex_t lock;
pthread_cond_t p_cond;
pthread_cond_t c_cond;
private:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
void ProductorWait()
{
std::cout << "productor wait ..." << std::endl;
pthread_cond_wait(&p_cond, &lock);
}
void ConsumerWait()
{
std::cout << "consumer wait ..." << std::endl;
pthread_cond_wait(&c_cond, &lock);
}
void WakeupProductor()
{
std::cout << "wake up productor ..." << std::endl;
pthread_cond_signal(&p_cond);
}
void WakeupConsumer()
{
std::cout <<"wake up consumer ..." << std::endl;
pthread_cond_signal(&c_cond);
}
bool IsFull()
{
return _q.size() >= _cap;
}
bool IsEmpty()
{
return _q.empty();
}
public:
BlockQueue(size_t cap = 5)
:_cap(cap)
{
pthread_mutex_init(&lock, nullptr);
pthread_cond_init(&p_cond, nullptr);
pthread_cond_init(&c_cond, nullptr);
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&p_cond);
pthread_cond_destroy(&c_cond);
}
void Put(const T& t)
{
// 生产者
LockQueue();
while (IsFull())
{
WakeupConsumer();
ProductorWait();
}
_q.push(t);
UnlockQueue();
}
void Take(T& t)
{
// 消费者
LockQueue();
while (IsEmpty())
{
WakeupProductor();
ConsumerWait();
}
t = _q.front();
_q.pop();
UnlockQueue();
}
};
main.cc
#include "BlockQueue.hpp"
#include
using namespace std;
pthread_mutex_t c_lock;
pthread_mutex_t p_lock;
void* consumer_run(void* arg)
{
BlockQueue<Task>* pbq = (BlockQueue<Task>*)arg;
while (true)
{
//int t = 0;
pthread_mutex_lock(&c_lock);
Task t;
pbq->Take(t);
sleep(1);
//cout << "consume data :" << t << endl;
cout<<"编号 "<< pthread_self()<<" 消费者" <<" consume task is " << t._x << " + " << t._y << " = " << t.Run() << endl;
pthread_mutex_unlock(&c_lock);
}
}
void* productor_run(void* arg)
{
sleep(1);
BlockQueue<Task>* pbq = (BlockQueue<Task>*)arg;
while (true)
{
pthread_mutex_lock(&p_lock);
int x = rand()%10 + 1;
int y = rand()%100 +1;
Task t(x,y);
pbq->Put(t);
// cout << "product data :" << t << endl;
cout <<"编号 " <<pthread_self()<< " 生产者" <<" product Task is : " << x << " + " << y << " = ?" << endl;
pthread_mutex_unlock(&p_lock);
sleep(1);
}
}
int main()
{
BlockQueue<Task> bq;
// 多消费者、多生产者
pthread_t c1,c2,c3,p1,p2,p3;
pthread_mutex_init(&c_lock, nullptr);
pthread_mutex_init(&p_lock,nullptr);
pthread_create(&c1, nullptr, consumer_run, (void*)&bq);
pthread_create(&c2, nullptr, consumer_run, (void*)&bq);
pthread_create(&c3, nullptr, consumer_run, (void*)&bq);
pthread_create(&p1, nullptr, productor_run, (void*)&bq);
pthread_create(&p2, nullptr, productor_run, (void*)&bq);
pthread_create(&p3, nullptr, productor_run, (void*)&bq);
pthread_join(c1,nullptr);
pthread_join(c2,nullptr);
pthread_join(c3,nullptr);
pthread_join(p1,nullptr);
pthread_join(p2,nullptr);
pthread_join(p3,nullptr);
pthread_mutex_destroy(&c_lock);
pthread_mutex_destroy(&p_lock);
return 0;
}
那么之前提到的问题:为什么 pthread_cond_wait需要互斥量(锁)===>在等待条件变量被其他线程通过访问临界资源打破等待的条件时,那么其他线程必须要有锁才可以,所以wait时候,必须释放锁,即该函数做了如下工作:自动释放lock ,当函数被返回的时候,返回到了临界区,则会让该线程重新持有锁。
struct sem {
int count;
mutex lock;
wait_queue *head;
}
// P() 操作的伪代码如下
P(){
lock();
if (count > 0) count--;
else
wait
unlock();
}
V(){
lock();
if (count == 原值) wait;
else
count++;
unlock();
}
1 初始化信号量
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
//参数:pshared:0表示线程间共享,非零表示进程间共享 value:信号量初始值
2 销毁信号量
int sem_destroy(sem_t *sem);
3 等待信号量
// 功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
4 发布信号量
//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
代码如下
Makefile
main:main.cc
g++ $^ -o $@ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f main
RingQueue.hpp
#pragma once
#include
#include
#include
template <class T>
class RingQueue
{
private:
std::vector<T> _v;
int _cap;
// 两个信号量
sem_t c_sem_data;
sem_t p_sem_blank;
// 两个下标索引
int c_index;
int p_index;
private:
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t &s)
{
sem_post(&s);
}
public:
RingQueue(int cap)
:_cap(cap),_v(cap)
{
sem_init(&c_sem_data, 0, 0);
sem_init(&p_sem_blank, 0, cap);
c_index = p_index = 0;
}
~RingQueue()
{
sem_destroy(&c_sem_data);
sem_destroy(&p_sem_blank);
c_index = p_index = 0;
}
void Put(T &in)
{
P(p_sem_blank);
_v[p_index] = in;
p_index++;
p_index %= _cap;
V(c_sem_data);
}
void Get(T &out)
{
// out 为输出型参数,由调用者传入引用获取内容
P(c_sem_data);
out = _v[c_index];
c_index++;
c_index %= _cap;
V(p_sem_blank);
}
};
main.cc
#include "RingQueue.hpp"
#include
using namespace std;
void *consumer(void *arg)
{
RingQueue<int>* rq = (RingQueue<int>*)arg;
while (true)
{
sleep(1);
int t;
rq->Get(t);
cout << "consumer done ..." << t << endl;
}
}
void *productor(void* arg)
{
RingQueue<int>* rq = (RingQueue<int>*)arg;
int count = 100;
while (true)
{
rq->Put(count);
count++;
if (count > 110)
{
count = 100;
}
cout << "productor done" << endl;
}
}
int main()
{
pthread_t c,p;
RingQueue<int> rq(5);
pthread_create(&c, nullptr, consumer, &rq);
pthread_create(&p, nullptr, productor,&rq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
return 0;
}
我们让主线程充当从网络中接受客户端请求的角色,每个请求是一个任务被送进任务队列,等待线程池里的线程来处理任务,我们的任务暂时简单的描述为:求一个数字的平方,等待后面更新计算机网络的时候可以完成一个小型的项目。
Makefile
testThreadPool:main.cc
g++ $^ -o $@ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f testThreadPool
ThreadPool.hpp
#pragma once
#include
#include
#include
#include
#define NUM 5
class Task{
public:
int base;
public:
Task(){
}
Task(int _b):base(_b){
}
void Run()
{
std::cout <<"thread is[" << pthread_self() << "] task run ... done: base# "<< base << " pow is# "<< pow(base,2) << std::endl;
}
~Task(){
}
};
class ThreadPool{
private:
std::queue<Task*> q;
int max_num;
pthread_mutex_t lock;
pthread_cond_t cond; //only consumer, thread pool thread;
bool quit;
public:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);
}
bool IsEmpty()
{
return q.size() == 0;
}
void ThreadWait()
{
pthread_cond_wait(&cond, &lock);
}
void ThreadWakeup()
{
//if(low_water > 30){
// pthread_cond_broadcast(&cond);
//}
pthread_cond_signal(&cond);
}
void ThreadsWakeup()
{
pthread_cond_broadcast(&cond);
}
public:
// 构造函数里尽量不要做有风险的事情
ThreadPool(int _max=NUM):max_num(_max),quit(false)
{
}
static void* Routine(void *arg) //
{
//线程分离
pthread_detach(pthread_self());
ThreadPool *this_p = (ThreadPool*)arg;
while(!quit){
this_p->LockQueue();
while(!quit && this_p->IsEmpty()){
this_p->ThreadWait();
}
Task t;
if(!quit && !this_p->IsEmpty){
this_p->Get(t);
}
this_p->UnlockQueue();
//t.Run();
}
}
void ThreadPoolInit()
{
pthread_mutex_init(&lock, nullptr);
pthread_cond_init(&cond, nullptr);
pthread_t t;
for(int i = 0; i < max_num; i++){
pthread_create(&t, nullptr, Routine, this);
}
}
//server
void Put(Task &in)
{
LockQueue();
q.push(&in);
UnlockQueue();
ThreadWakeup();
}
//Thread pool t;
void Get(Task &out)
{
Task*t = q.front();
q.pop();
out = *t;
}
void ThreadQuit()
{
if(!IsEmpty()){
std::cout << "task queue is not empty" << std::endl;
return;
}
quit = true;
ThreadsWakeup();
}
~ThreadPool()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
};
main.cc
#include "ThreadPool.hpp"
int main()
{
ThreadPool *tp = new ThreadPool();
tp->ThreadPoolInit();
//server
int count = 20;
while(count){
int x = rand()%10+1;
Task t(x);
tp->Put(t);
sleep(1);
count--;
}
tp->ThreadQuit(); //
return 0;
}