背景知识
a.重谈地址空间
b.理解代码数据划分的本质
1.理解文件缓冲区
2.虚拟地址的本质是什么,是一种资源
Linux线程概念
什么是线程
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
- 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质是在进程地址空间内运行
- 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
回忆一下进程
进程的定义---内核观点
进程=内核数据结构+进程代码和数据
进程:承担分配系统资源的基本实体
以前我们使用进程的时候,内部只有一个执行流的进程
task_struct<=进程
CPU不做区分task_struct是进程还是线程,都是执行流
CPU看到的执行流<=进程
Linux中的执行流:轻量级进程 (Linux用进程模拟的线程)
Linux中是轻量级进程与其他操作系统还是有区别的
Linux中只有一个轻量级进程只有一个PCB,用这个轻量级进程可以模拟多线程
其他操作系统有tcb和多个pcb线程结构体
Linux将轻量级线程封装成其他操作系统多线程一样的接口
线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
- 性能损失
- 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
- 健壮性降低
- 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制
- 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高
- 编写与调试一个多线程程序比单线程程序困难得多
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
线程用途
进程和线程
- 进程是资源分配的基本单位
- 线程是调度的基本单位
- 线程共享进程数据,但也拥有自己的一部分数据:
- 线程ID
- 一组寄存器(硬件上下文数据--线性可以动态运行)
- 栈(线程运行时生成的各种临时变量,临时变量会被每个线程保存在自己的栈区)
- errno
- 信号屏蔽字
- 调度优先级
- 文件描述符表
- 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
- 当前工作目录
- 用户id和组id
Linux线程控制
POSIX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>
- 链接这些线程函数库时要使用编译器命令的“-lpthread”选项
创建线程
功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
#include <iostream>
#include <unistd.h>
#include <ctime>
int gval = 100;
void fun()
{
}
// 新线程
// 0x1111~0x2222
void *threadStart(void *args)
{
while (true)
{
sleep(1);
// int x = rand() % 5;
std::cout << "new thread running..." << ", pid: " << getpid()
<< ", gval: " << gval << ", &gval: " << &gval << std::endl;
fun();
// if(x == 0)
// {
// int *p = nullptr;
// *p = 100; // 野指针
// }
}
}
// 0x3333~0x4444
int main()
{
srand(time(nullptr));
pthread_t tid1;
pthread_create(&tid1, nullptr, threadStart, (void *)"thread-new");
pthread_t tid2;
pthread_create(&tid2, nullptr, threadStart, (void *)"thread-new");
pthread_t tid3;
pthread_create(&tid3, nullptr, threadStart, (void *)"thread-new");
// 主线程
while (true)
{
std::cout << "main thread running..." << ", pid: " << getpid()
<< ", gval: " << gval << ", &gval: " << &gval << std::endl;
gval++; // 修改!
sleep(1);
}
return 0;
}
查看线程的指令
ps - aL
PID是进程号,LWP是线程号
编译时要带库
等待线程
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
pthread_t thread: 被连接线程的线程号
void **retval : 二级指针,输出型参数,指向一个指向被连接线程的返回码的一级指针
返回值 : 0代表成功。 失败,返回的则是错误号。
pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源
当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:
- 被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间
- 一个线程只能被一个线程所连接。
- 被连接的线程必须是非分离的,否则连接会出错。
所以可以看出pthread_join()有两种作用:
- 用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。
- 对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。
退出线程
#include <pthread.h>
void pthread_exit(void *retval);
参数:retval表示线程退出状态,通常传NULL
作用:将单个线程退出。
注意几点:
- return的作用是返回到函数的调用点,如果是main函数中的return,则代表该进程结束,并释放进程地址空间,所有线程都终止。对于其它函数的return,则直接返回到函数的调用点。exit和_exit函数会直接终止整个进程,导致所有线程结束。pthread_exit函数则会导致调用该函数的线程结束。所以,多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit导致进程退出,其他线程也结束,主控线程退出时不能return或exit。
- 另注意,pthread_exit或者return返回的指针(线程执行的函数用return或者pthread_exit结束线程时)所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,此时退出的这个线程函数所占据的栈空间可能又会被重新分配出去,因此其他线程再次使用这个返回的地址没有意义。
杀死线程
#include <pthread.h>
pthread_cancel(pthread_t thread);
杀死一个线程,对应进程中的kill函数;成功返回0,失败返回错误码;
注意:线程的取消并不是实时的,需要一个取消点,而这个取消点一般是一个系统调用函数,如果线程之中没有这个取消点,可以调用pthread_testcancel函数自行设置一个取消点
分离线程
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:即主线程与子线程分离,子线程结束后,资源自动回收
pthread_join()函数的替代函数,可回收创建时detachstate属性设置为PTHREAD_CREATE_JOINABLE的线程的存储空间。该函数不会阻塞父线程。pthread_join()函数用于只是应用程序在线程tid终止时回收其存储空间。如果tid尚未终止,pthread_detach()不会终止该线程。当然pthread_detach(pthread_self())也是可以的。
返回值:pthread_detach() 在调用成功完成之后返回零。其他任何返回值都表示出现了错误。如果检测到以下任一情况,pthread_detach()将失败并返回相应的值。
- EINVAL:tid是分离线程
- ESRCH:tid不是当前进程中有效的为分离线程
#include <pthread.h>
pthread_t pthread_self(void);
线程可以通过调用pthread_self函数获得自身线程标识。
pthread_detach(pthread_self());
使线成分离出来。当这个线程执行完成任务后释放释放资源。不然它会保留退出状态,等待别人来取。
pthread_detach(threadid)函数的功能是使线程ID为threadid的线程处于分离状态,一旦线程处于分离状态,该线程终止时底 层资源立即被回收;否则终止子线程的状态会一直保存(占用系统资源)直到主线程调用pthread_join(threadid,NULL)获取线程的退出状态。通常是主线程使用pthread_create()创建子线程以后,一般可以调用pthread_detach(threadid)分离刚刚创建的子线程,这里的threadid是指子线程的threadid;如此以来,该子线程止时底层资源立即被回收;被创建的子线程也可以自己分离自己,子线程调用pthread_detach(pthread_self())就是分离自己,因为pthread_self()这个函数返回的就是自己本身的线程ID;
相关问题探究
问题1: main 主线程和 新线程谁先运行?不确定
问题2: 我们期望谁最后退出? main thread,你如何保证呢?
用pthread_join主线程等待回收来保证。 不join呢?会造成类似僵尸进程的问题
问题3: tid 是什么样子的?是什么呢?虚拟地址! 为什么?
问题4:全面看待线程函数传参: 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!
问题5: 全面看待线程函数返回:
a. 只考虑正确的返回,不考虑异常,因为异常了,整个进程就崩溃了,包括主线程
b. 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!
问题6: 如何创建多线程呢?
问题7: 新线程如何终止?
1. 线程函数 return
2. pthread_exit
3. main thread call pthread_cancel, 新线程退出结果是-1
问题8: 可以不可以不join线程,让他执行完就退出呢??可以!
a. 一个线程被创建,默认是joinable的,必须要被join的
b. 如果一个线程被分离,线程的工作状态分离状态,不需要/不能被join的. 依旧属于进程内部,但是不需要被等待了,该线程崩溃时整个进程也会崩溃。
主线程结束,子线程是否也强制结束?
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
// // 可以给线程传递多个参数,甚至方法了
// // class ThreadData
// // {
// // public:
// // int Excute()
// // {
// // return x + y;
// // }
// // public:
// // std::string name;
// // int x;
// // int y;
// // // other
// // };
// // class ThreadResult
// // {
// // public:
// // std::string print()
// // {
// // return std::to_string(x) + "+" + std::to_string(y) + "=" + std::to_string(result);
// // }
// // public:
// // int x;
// // int y;
// // int result;
// // };
// // // 问题5: 全面看待线程函数返回:
// // // a. 只考虑正确的返回,不考虑异常,因为异常了,整个进程就崩溃了,包括主线程
// // // b. 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!
// // void *threadRun(void *args)
// // {
// // //std::string name = (const char*)args;
// // //int a = *(int*)args; // warning
// // ThreadData *td = static_cast<ThreadData*>(args); // (ThreadData*)args
// // ThreadResult *result = new ThreadResult();
// // int cnt = 10;
// // while(cnt)
// // {
// // sleep(3);
// // std::cout << td->name << " run ..." << ", cnt: " << cnt-- << std::endl;
// // result->result = td->Excute();
// // result->x = td->x;
// // result->y = td->y;
// // break;
// // // int *p = nullptr;
// // // *p = 100; // 故意野指针
// // }
// // delete td;
// // return (void*)result;
// // }
// std::string PrintToHex(pthread_t &tid)
// {
// char buffer[64];
// snprintf(buffer, sizeof(buffer), "0x%lx", tid);
// return buffer;
// }
// const int num = 10;
// void *threadrun(void *args)
// {
// // pthread_detach(pthread_self());
// std::string name = static_cast<const char*>(args);
// while(true)
// {
// std::cout << name << " is running" << std::endl;
// sleep(3);
// // int *p = nullptr;
// // *p = 100;
// break;
// }
// // return args;
// // exit(1); // 进程: 专门用来终止进程的,不能用来终止线程!
// pthread_exit(args); // 专门终止一个线程的!
// }
// // main函数结束,main thread结束,表示进程结束!
// int main()
// {
// // 问题7: 新线程如何终止?
// // 1. 线程函数 return
// // 2. pthread_exit
// // 3. main thread call pthread_cancel, 新线程退出结果是-1
// // 问题8: 可以不可以不join线程,让他执行完就退出呢??可以!
// // a. 一个线程被创建,默认是joinable的,必须要被join的.
// // b. 如果一个线程被分离,线程的工作状态分离状态,不须要/不能被join的. 依旧属于进程内部,但是不需要被等待了
// // std::vector<pthread_t> tids;
// // // 问题6: 如何创建多线程呢?
// // for(int i = 0; i < num; i++)
// // {
// // // 1. 有线程的id
// // pthread_t tid;
// // // 2. 线程的名字
// // char *name = new char[128];
// // snprintf(name, 128, "thread-%d", i+1);
// // pthread_create(&tid, nullptr, threadrun, /*线程的名字*/name);
// // // 3. 保存所有线程的id信息 --- base
// // // tids.push_back(tid);
// // tids.emplace_back(tid);
// // }
// // for (auto tid : tids)
// // {
// // pthread_detach(tid); // 主线程分离新线程,新线程必须存在
// // }
// // // //做我的事情
// // // while(true)
// // // {
// // // sleep(1);
// // // }
// // // // join todo
// // for(auto tid : tids)
// // {
// // // pthread_cancel(tid); // 取消
// // // std::cout << "cancel: " << PrintToHex(tid) << std::endl;
// // void *result = nullptr; // 线程被取消线程的退出结果是:-1 #define PTHREAD_CANCELED ((void *) -1)
// // int n = pthread_join(tid, &result);
// // // std::cout << PrintToHex(tid) << " quit... "<< std::endl;
// // std::cout << (long long int)result << " quit..., n : " << n << std::endl;
// // // delete (const char*)name;
// // }
// // sleep(100);
// // pthread_t tid; // unsigned long int
// // // 问题1: main 和 new 线程谁先运行?不确定
// // // 问题4:全面看待线程函数传参: 我们可以传递任意类型,但你一定要能想得起来,也可以传递类对象的地址!!!
// // ThreadData *td = new ThreadData();
// // td->name = "thread-1";
// // td->x = 10;
// // td->y = 20;
// // int n = pthread_create(&tid, nullptr, threadRun, td);
// // // td.name = "thread-2";
// // // td.num = 2;
// // // int n = pthread_create(&tid, nullptr, threadRun, (void*)&td);
// // if(n != 0) // 后面我们暂时就关心了
// // {
// // std::cerr << "create thread error" << std::endl;
// // return 1;
// // }
// // // 问题3: tid 是什么样子的?是什么呢?虚拟地址! 为什么? TODO
// // std::string tid_str = PrintToHex(tid); // 我们按照16进行打印出来
// // std::cout << "tid : " << tid_str << std::endl;
// // std::cout << "main thread join begin..." << std::endl;
// // // 问题2:我们期望谁最后退出? main thread,你如何保证呢?
// // ThreadResult *result = nullptr; // 开辟了空间的!!!
// // n = pthread_join(tid, (void**)&result); // join来保证。 不join呢?会造成类似僵尸进程的问题
// // if(n == 0)
// // {
// // std::cout << "main thread wait success, new thread exit code: " << result->print() << std::endl;
// // }
// // sleep(100);
// return 0;
// }
C++11多线程调用
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void threadrun(std::string name, int num)
{
while(num)
{
std::cout << name << " num : " << num<< std::endl;
num--;
sleep(1);
}
}
int main()
{
std::string name = "thread-1";
std::thread mythread(threadrun, std::move(name), 10);
while(true)
{
std::cout << "main thhread..." << std::endl;
sleep(1);
}
mythread.join();
return 0;
}
C++11的多线程本质:就是对原生线程库接口的封装
底层就是各操作系统的接口函数
编译时依旧需要加后缀 -pthread
相关理解
tid是什么,(虚拟地址),为什么?
LWPtid,tid是一个地址,是库中自己维护的一个值
我们接下来创建线程,前提是把库加载到内存,映射到我进程的地址空间。
库如何做到对线程进行管理的呢,先描述,再组织。
描述:库中创建描述线程的相关结构体字段属性
组织:通过“数组”组织
__thread修饰内置类型
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <pthread.h>
// Linux有效, __thread只能修饰内置类型
__thread int gval = 100;
std::string ToHex(pthread_t tid)
{
char id[128];
snprintf(id, sizeof(id), "0x%lx", tid);
return id;
}
void *threadrun(void *args)
{
std::string name = static_cast<const char *>(args);
while (true)
{
std::string id = ToHex(pthread_self());
std::cout << name << " is running, tid: " << id << ", gval: " << gval << ", &gval: " << &gval << std::endl;
sleep(1);
gval++;
}
}
int main()
{
pthread_t tid; // 线程属性集合的起始虚拟地址--- 在pthread库中维护
pthread_create(&tid, nullptr, threadrun, (void *)"thread-1");
while (true)
{
std::cout << "main thread, gval: " << gval << ", &gval: " << &gval << std::endl;
sleep(1);
}
pthread_join(tid, nullptr);
return 0;
}
C++11多线程封装
#include <iostream>
#include <string>
#include <pthread.h>
namespace ThreadMoudle
{
// 线程要执行的方法,后面我们随时调整
typedef void (*func_t)(const std::string &name); // 函数指针类型
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running" << std::endl;
_isrunning = true;
_func(_name);
_isrunning = false;
}
public:
Thread(const std::string &name, func_t func):_name(name), _func(func)
{
std::cout << "create " << name << " done" << std::endl;
}
static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
{
Thread *self = static_cast<Thread*>(args); // 获得了当前对象
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
if(n != 0) return false;
return true;
}
std::string Status()
{
if(_isrunning) return "running";
else return "sleep";
}
void Stop()
{
if(_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop" << std::endl;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined" << std::endl;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 线程要执行的回调函数
};
}
// void Print(const std::string &name)
// {
// int cnt = 1;
// while (true)
// {
// std::cout << name << "is running, cnt: " << cnt++ << std::endl;
// sleep(1);
// }
// }
// const int gnum = 10;
// int main()
// {
// // 我在管理原生线程, 先描述,在组织
// // 构建线程对象
// std::vector<Thread> threads;
// for (int i = 0; i < gnum; i++)
// {
// std::string name = "thread-" + std::to_string(i + 1);
// threads.emplace_back(name, Print);
// sleep(1);
// }
// // 统一启动
// for (auto &thread : threads)
// {
// thread.Start();
// }
// sleep(10);
// // 统一结束
// for (auto &thread : threads)
// {
// thread.Stop();
// }
// // 等待线程等待
// for (auto &thread : threads)
// {
// thread.Join();
// }
// // Thread t("thread-1", Print);
// // t.Start();
// // std::cout << t.Name() << ", status: " << t.Status() << std::endl;
// // sleep(10);
// // std::cout << t.Name() << ", status: " << t.Status() << std::endl;
// // t.Stop();
// // sleep(1);
// // std::cout << t.Name() << ", status: " << t.Status() << std::endl;
// // t.Join();
// // std::cout << "join done" << std::endl;
// return 0;
// }
线程互斥
多个线程能够看到的资源---共享资源-->我们需要对这部分资源进行保护-->互斥、同步
抢票代码
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
using namespace ThreadMoudle;
int tickets = 10000;
void route(const std::string &name)
{
while(true)
{
if(tickets > 0)
{
// 抢票过程
usleep(1000); // 1ms -> 抢票花费的时间
printf("who: %s, get a ticket: %d\n", name.c_str(), tickets);
tickets--;
}
else
{
break;
}
}
}
int main()
{
Thread t1("thread-1", route);
Thread t2("thread-2", route);
Thread t3("thread-3", route);
Thread t4("thread-4", route);
t1.Start();
t2.Start();
t3.Start();
t4.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();
}
进程1,2,3,4都读取的票数是1,都经过CPU一一逻辑判断,都是可以作减法。
算术运算作减法时会重新读取数据,然后减减,最后再一一写回数据。
逻辑运算和算术运算是分开单独一一运行的。
如何解决呢?
互斥量的接口
初始化互斥量
方法 1 ,静态分配 :(使用全局或静态的锁,这样初始化就行了)#include <pthread.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
方法 2 ,动态分配 :(局部的锁可以这样初始化)#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参数: mutex:要初始化的互斥量 attr:NULL
- 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
- 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值:成功返回0,失败返回错误号(<0)
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
- 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
- 多个线程并发的操作共享变量,会带来一些问题。
所谓对临界资源的保护,本质是对临界区代码进行保护
我们对所有资源进行访问,本质是通过代码进行访问的,保护资源,本质是想办法把访问资源的代码保护起来。
原理角度理解这个锁
实现角度理解这个锁
钥匙只有一把,A线程拿走了钥匙(1)实现加锁,执行后面锁住的代码,其他线程BCD来时,只能得到(0)。从而挂起等待A执行完。这就实现了互斥。
线程A执行完时,会将钥匙(1)归还给lock。
Thread.hpp
#include <iostream>
#include <string>
#include <pthread.h>
namespace ThreadMoudle
{
class ThreadData
{
public:
ThreadData(const std::string &name, pthread_mutex_t *lock):_name(name), _lock(lock)
{}
public:
std::string _name;
pthread_mutex_t *_lock;
};
// 线程要执行的方法,后面我们随时调整
typedef void (*func_t)(ThreadData *td); // 函数指针类型
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running" << std::endl;
_isrunning = true;
_func(_td);
_isrunning = false;
}
public:
Thread(const std::string &name, func_t func, ThreadData *td):_name(name), _func(func), _td(td)
{
std::cout << "create " << name << " done" << std::endl;
}
static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
{
Thread *self = static_cast<Thread*>(args); // 获得了当前对象
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
if(n != 0) return false;
return true;
}
std::string Status()
{
if(_isrunning) return "running";
else return "sleep";
}
void Stop()
{
if(_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop" << std::endl;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined" << std::endl;
delete _td;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 线程要执行的回调函数
ThreadData *_td;
};
}
main.c
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace ThreadMoudle;
int tickets = 10000; // 共享资源,造成了数据不一致的问题
void route(ThreadData *td)
{
while (true)
{
LockGuard lockguard(td->_lock); // RAII风格的锁
if (tickets > 0)
{
// 抢票过程
usleep(1000); // 1ms -> 抢票花费的时间
printf("who: %s, get a ticket: %d\n", td->_name.c_str(), tickets);
tickets--;
}
else
{
break;
}
}
}
// pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
// void route(ThreadData *td)
// {
// // std::cout <<td->_name << ": " << "mutex address: " << td->_lock << std::endl;
// // sleep(1);
// while (true)
// {
// pthread_mutex_lock(td->_lock);
// if (tickets > 0)
// {
// // 抢票过程
// usleep(1000); // 1ms -> 抢票花费的时间
// printf("who: %s, get a ticket: %d\n", td->_name.c_str(), tickets);
// tickets--;
// pthread_mutex_unlock(td->_lock);
// }
// else
// {
// pthread_mutex_unlock(td->_lock);
// break;
// }
// }
// }
static int threadnum = 4;
int main()
{
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, nullptr);
std::vector<Thread> threads;
for(int i = 0; i < threadnum; i++)
{
std::string name = "thread-" + std::to_string(i+1);
ThreadData *td = new ThreadData(name, &mutex);
threads.emplace_back(name, route, td);
}
for(auto &thread : threads)
{
thread.Start();
}
for(auto &thread : threads)
{
thread.Join();
}
pthread_mutex_destroy(&mutex);
}
LockGuard.hpp
#include <pthread.h>
class LockGuard
{
public:
LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t *_mutex;
};
由于加锁的线程执行完还完钥匙解锁后,再次抢钥匙加锁时,会出现抢钥匙成功率大的情况
这意味着,该线程会一直霸占这把锁,一直执行程序,而其他线程很难有机会。
这就需要下面的线程同步,防止这种不公平情况的产生
线程同步
条件变量
- 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
- 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解
条件变量函数
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
cond是局部变量用这个初始化
如果是全局或者静态的,
#include <pthread.h> pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
即可
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond)
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有线程
int pthread_cond_signal(pthread_cond_t *cond);//随机唤醒一个线程
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
const int num = 5;
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;
void *Wait(void *args)
{
std::string name = static_cast<const char *>(args);
while (true)
{
pthread_mutex_lock(&gmutex);
pthread_cond_wait(&gcond, &gmutex /*?*/); // 这里就是线程等待的位置
usleep(10000);
std::cout << "I am : " << name << std::endl;
pthread_mutex_unlock(&gmutex);
// usleep(100000);
}
}
int main()
{
pthread_t threads[num];
for (int i = 0; i < num; i++)
{
char *name = new char[1024];
snprintf(name, 1024, "thread-%d", i + 1);
pthread_create(threads + i, nullptr, Wait, (void *)name);
usleep(10000);
}
sleep(1);
// 唤醒其他线程
while (true)
{
// pthread_cond_signal(&gcond);
pthread_cond_broadcast(&gcond);
std::cout << "唤醒一个线程...." << std::endl;
sleep(2);
}
for (int i = 0; i < num; i++)
{
pthread_join(threads[i], nullptr);
}
return 0;
}
- 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
- 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
条件变量需要配合互斥量去使用,先上锁,发现条件不满足,解锁,然后等待在条件变量上
条件变量是一个队列,允许多个线程排队,等待时会自动解锁,直到被唤醒时需要重新竞争锁然后才能继续在等待的代码处向后执行
生产者消费者模型
- 解耦
- 支持并发
- 支持忙闲不均
实现生产消费模式,本质就是:
通过代码实现,实现321原则,用锁和条件变量(或者其他方式)来实现三种关系
基于BlockingQueue的生产者消费者模型
BlockingQueue
BlockQueue.hpp
#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>
const static int defaultcap = 5;
template <typename T>
class BlockQueue
{
private:
bool IsFull()
{
return _block_queue.size() == _max_cap;
}
bool IsEmpty()
{
return _block_queue.empty();
}
public:
BlockQueue(int cap = defaultcap) : _max_cap(cap)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_p_cond, nullptr);
pthread_cond_init(&_c_cond, nullptr);
}
// 假设:2个消费者
void Pop(T *out)
{
pthread_mutex_lock(&_mutex);
while (IsEmpty()) // while可以保证代码的鲁棒性(健壮性)
{
// 添加尚未满足,但是线程被异常唤醒的情况,叫做伪唤醒!
pthread_cond_wait(&_c_cond, &_mutex); // 两个消费者都在这里等待了
}
// 1. 没有空 || 2. 被唤醒了
*out = _block_queue.front();
_block_queue.pop();
// if(_block_queue.size() > hight_water)
// pthread_cond_signal(&_p_cond);
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_p_cond);
}
// 一个生产者
void Equeue(const T &in)
{
pthread_mutex_lock(&_mutex);
while (IsFull()) // if ?
{
// 满了,生产者不能生产,必须等待
// 可是在临界区里面啊!!!pthread_cond_wait
// 被调用的时候:除了让自己继续排队等待,还会自己释放传入的锁
// 函数返回的时候,不就还在临界区了!
// 返回时:必须先参与锁的竞争,重新加上锁,该函数才会返回!
pthread_cond_wait(&_p_cond, &_mutex);
}
// 1. 没有满 || 2. 被唤醒了
_block_queue.push(in); // 生产到阻塞队列
pthread_mutex_unlock(&_mutex);
// 让消费者消费
pthread_cond_signal(&_c_cond); // pthread_cond_broadcast : 一种场景
}
~BlockQueue()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_p_cond);
pthread_cond_destroy(&_c_cond);
}
private:
std::queue<T> _block_queue; // 临界资源
int _max_cap;
pthread_mutex_t _mutex;
pthread_cond_t _p_cond; // 生产者条件变量
pthread_cond_t _c_cond; // 消费者条件变量
// int low_water = _max_cap/3
// int hight_water _max_cap/3*2
};
Task.hpp
#include<iostream>
#include<functional>
// typedef std::function<void()> task_t;
using task_t = std::function<void()>;
void Download()
{
std::cout << "我是一个下载的任务" << std::endl;
}
// // 要做加法
// class Task
// {
// public:
// Task()
// {
// }
// Task(int x, int y) : _x(x), _y(y)
// {
// }
// void Excute()
// {
// _result = _x + _y;
// }
// void operator ()()
// {
// Excute();
// }
// std::string debug()
// {
// std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";
// return msg;
// }
// std::string result()
// {
// std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
// return msg;
// }
// private:
// int _x;
// int _y;
// int _result;
// };
main.cc
#include "BlockQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <unistd.h>
void *Consumer(void *args)
{
BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);
while(true)
{
// 1. 获取数据
task_t t;
bq->Pop(&t);
// 2. 处理数据
// t.Excute();
t();
// std::cout << "Consumer -> " << t.result() << std::endl;
}
}
void *Productor(void *args)
{
srand(time(nullptr) ^ getpid());
BlockQueue<task_t> *bq = static_cast<BlockQueue<task_t> *>(args);
while(true)
{
// 1. 构建数据/任务
// int x = rand() % 10 + 1; // [1, 10]
// usleep(x * 1000);
// int y = rand() % 10 + 1; // [1, 10]
// Task t(x, y);
// 2. 生产数据
bq->Equeue(Download);
std::cout << "Productor -> Download" << std::endl;
sleep(1);
}
}
int main()
{
BlockQueue<task_t> *bq = new BlockQueue<task_t>();
pthread_t c1,c2, p1,p2,p3;
pthread_create(&c1, nullptr, Consumer, bq);
pthread_create(&c2, nullptr, Consumer, bq);
pthread_create(&p1, nullptr, Productor, bq);
pthread_create(&p2, nullptr, Productor, bq);
pthread_create(&p3, nullptr, Productor, bq);
pthread_join(c1, nullptr);
pthread_join(c2, nullptr);
pthread_join(p1, nullptr);
pthread_join(p2, nullptr);
pthread_join(p3, nullptr);
return 0;
}
POSIX信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值
int sem_destroy(sem_t *sem);
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
原理:由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行;V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
- 环形队列采用数组模拟,用模运算来模拟环状特性
- 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态
- 但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
Task.hpp
#include<iostream>
#include<functional>
// typedef std::function<void()> task_t;
// using task_t = std::function<void()>;
// void Download()
// {
// std::cout << "我是一个下载的任务" << std::endl;
// }
// 要做加法
class Task
{
public:
Task()
{
}
Task(int x, int y) : _x(x), _y(y)
{
}
void Excute()
{
_result = _x + _y;
}
void operator ()()
{
Excute();
}
std::string debug()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";
return msg;
}
std::string result()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
return msg;
}
private:
int _x;
int _y;
int _result;
};
RingQueue.hpp
#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <semaphore.h>
template <typename T>
class RingQueue
{
private:
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t &s)
{
sem_post(&s);
}
public:
RingQueue(int max_cap)
: _ringqueue(max_cap), _max_cap(max_cap), _c_step(0), _p_step(0)
{
sem_init(&_data_sem, 0, 0);
sem_init(&_space_sem, 0, max_cap);
pthread_mutex_init(&_c_mutex, nullptr);
pthread_mutex_init(&_p_mutex, nullptr);
}
void Push(const T &in) //生产者
{
// 信号量:是一个计数器,是资源的预订机制。预订:在外部,可以不判断资源是否满足,就可以知道内部资源的情况!
P(_space_sem); // 信号量这里,对资源进行使用,申请,为什么不判断一下条件是否满足???信号量本身就是判断条件!
pthread_mutex_lock(&_p_mutex); //?
_ringqueue[_p_step] = in;
_p_step++;
_p_step %= _max_cap;
pthread_mutex_unlock(&_p_mutex);
V(_data_sem);
}
void Pop(T *out) // 消费
{
P(_data_sem);
pthread_mutex_lock(&_c_mutex); //?
*out = _ringqueue[_c_step];
_c_step++;
_c_step %= _max_cap;
pthread_mutex_unlock(&_c_mutex);
V(_space_sem);
}
~RingQueue()
{
sem_destroy(&_data_sem);
sem_destroy(&_space_sem);
pthread_mutex_destroy(&_c_mutex);
pthread_mutex_destroy(&_p_mutex);
}
private:
std::vector<T> _ringqueue;
int _max_cap;
int _c_step;
int _p_step;
sem_t _data_sem; // 消费者关心
sem_t _space_sem; // 生产者关心
pthread_mutex_t _c_mutex;
pthread_mutex_t _p_mutex;
};
main.cc
#include "RingQueue.hpp"
#include "Task.hpp"
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
void *Consumer(void*args)
{
RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
while(true)
{
Task t;
// 1. 消费
rq->Pop(&t);
// 2. 处理数据
t();
std::cout << "Consumer-> " << t.result() << std::endl;
}
}
void *Productor(void*args)
{
RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);
while(true)
{
sleep(1);
// 1. 构造数据
int x = rand() % 10 + 1; //[1, 10]
usleep(x*1000);
int y = rand() % 10 + 1;
Task t(x, y);
// 2. 生产
rq->Push(t);
std::cout << "Productor -> " << t.debug() << std::endl;
}
}
int main()
{
srand(time(nullptr) ^ getpid());
RingQueue<Task> *rq = new RingQueue<Task>(5);
// 单单
pthread_t c1, c2, p1, p2, p3;
pthread_create(&c1, nullptr, Consumer, rq);
pthread_create(&c2, nullptr, Consumer, rq);
pthread_create(&p1, nullptr, Productor, rq);
pthread_create(&p2, nullptr, Productor, rq);
pthread_create(&p3, nullptr, Productor, rq);
pthread_join(c1, nullptr);
pthread_join(c2, nullptr);
pthread_join(p1, nullptr);
pthread_join(p2, nullptr);
pthread_join(p3, nullptr);
return 0;
}
线程池
Task.hpp
#include<iostream>
#include<functional>
// typedef std::function<void()> task_t;
// using task_t = std::function<void()>;
// void Download()
// {
// std::cout << "我是一个下载的任务" << std::endl;
// }
// 要做加法
class Task
{
public:
Task()
{
}
Task(int x, int y) : _x(x), _y(y)
{
}
void Excute()
{
_result = _x + _y;
}
void operator ()()
{
Excute();
}
std::string debug()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=?";
return msg;
}
std::string result()
{
std::string msg = std::to_string(_x) + "+" + std::to_string(_y) + "=" + std::to_string(_result);
return msg;
}
private:
int _x;
int _y;
int _result;
};
Thread.hpp
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
namespace ThreadMoudle
{
// 线程要执行的方法,后面我们随时调整
// typedef void (*func_t)(ThreadData *td); // 函数指针类型
// typedef std::function<void()> func_t;
using func_t = std::function<void(const std::string&)>;
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running" << std::endl;
_isrunning = true;
_func(_name);
_isrunning = false;
}
public:
Thread(const std::string &name, func_t func):_name(name), _func(func)
{
std::cout << "create " << name << " done" << std::endl;
}
static void *ThreadRoutine(void *args) // 新线程都会执行该方法!
{
Thread *self = static_cast<Thread*>(args); // 获得了当前对象
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRoutine, this);
if(n != 0) return false;
return true;
}
std::string Status()
{
if(_isrunning) return "running";
else return "sleep";
}
void Stop()
{
if(_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop" << std::endl;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined" << std::endl;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 线程要执行的回调函数
};
ThreadPool.hpp
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include "Thread.hpp"
using namespace ThreadMoudle;
static const int gdefaultnum = 5;
void test()
{
while (true)
{
std::cout << "hello world" << std::endl;
sleep(1);
}
}
template <typename T>
class ThreadPool
{
private:
void LockQueue()
{
pthread_mutex_lock(&_mutex);
}
void UnlockQueue()
{
pthread_mutex_unlock(&_mutex);
}
void Wakeup()
{
pthread_cond_signal(&_cond);
}
void WakeupAll()
{
pthread_cond_broadcast(&_cond);
}
void Sleep()
{
pthread_cond_wait(&_cond, &_mutex);
}
bool IsEmpty()
{
return _task_queue.empty();
}
void HandlerTask(const std::string &name) // this
{
while (true)
{
// 取任务
LockQueue();
while (IsEmpty() && _isrunning)
{
_sleep_thread_num++;
Sleep();
_sleep_thread_num--;
}
// 判定一种情况
if (IsEmpty() && !_isrunning)
{
std::cout << name << " quit" << std::endl;
UnlockQueue();
break;
}
// 有任务
T t = _task_queue.front();
_task_queue.pop();
UnlockQueue();
// 处理任务
t(); // 处理任务,此处不用/不能在临界区中处理
std::cout << name << ": " << t.result() << std::endl;
}
}
public:
ThreadPool(int thread_num = gdefaultnum) : _thread_num(thread_num), _isrunning(false), _sleep_thread_num(0)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
void Init()
{
func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
for (int i = 0; i < _thread_num; i++)
{
std::string threadname = "thread-" + std::to_string(i + 1);
_threads.emplace_back(threadname, func);
}
}
void Start()
{
_isrunning = true;
for (auto &thread : _threads)
{
thread.Start();
}
}
void Stop()
{
LockQueue();
_isrunning = false;
WakeupAll();
UnlockQueue();
}
void Equeue(const T &in)
{
LockQueue();
if (_isrunning)
{
_task_queue.push(in);
if (_sleep_thread_num > 0)
Wakeup();
}
UnlockQueue();
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
private:
int _thread_num;
std::vector<Thread> _threads;
std::queue<T> _task_queue;
bool _isrunning;
int _sleep_thread_num;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
};
Main.cc
#include "ThreadPool.hpp"
#include "Task.hpp"
// #include <memory>
int main()
{
// std::unique_ptr<ThreadPool> tp = std::make_unique<ThreadPool>(); //c++14
ThreadPool<Task> *tp = new ThreadPool<Task>();
tp->Init();
tp->Start();
int cnt = 10;
while(cnt)
{
// 不断地向线程池推送任务
sleep(1);
Task t(1,1);
tp->Equeue(t);
sleep(1);
std::cout << "cnt: " << cnt-- << std::endl;
}
tp->Stop();
std::cout << "thread pool stop" << std::endl;
sleep(10);
return 0;
}