POSIX线程(英语:POSIX Threads,常被缩写为Pthreads)是POSIX 的线程标准,定义了创建和操纵线程的一套API。
实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-likePOSIX 系统,如Linux、Solaris。它以pthread.h头文件和一个线程库实现。Pthreads API中大致共有100个函数调用,全都以"pthread_"开头。
Thread即有pthread_t 也有pid_t ,它们各有用处,pthread_t 给pthread_XXX函数使用,而pid_t
作为线程标识。
pthread_join
函数定义: int pthread_join(pthread_t thread, void **retval);
参数 :thread: 线程标识符,retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。
代码中如果没有pthread_join 主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
int pthread_join(pthread_t th, void **thread_return)
pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,需要注意的是一个线程仅允许唯一的一个线程使用 pthread_join()等待它的终止。
pthread_create
pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。
pthread_create的返回值 表示成功,返回0;表示出错,返回-1。
#include
int pthread_create(
pthread_t *restrict tidp, //新创建的线程ID指向的内存单元。
const pthread_attr_t *restrict attr, //线程属性,默认为NULL
void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行
void *restrict arg //默认为NULL。若上述函数需要参数,将参数放入结构中并将地址作为arg传入。
);
参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
pthread_atfork()
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
pthread_atfork()在fork()之前调用,当调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child。
实际上对于编写多线程程序来说最好不要调用fork() 即不要编写多线程多进程程序 要么用多进程要么用多进程 因为多线程fork()之后容易出现死锁。
fork 可能是在主线程中调用,也可能是在子线程中调用,fork得到一个新进程,新进程只有一个执行序列,只有一个线程(也就是新进程中的主线程,所以需要把线程名改为main)(调用fork的线程被继承下来)
snprintf()函数:
int snprintf ( char * str, size_t size, const char * format, ... );
基于对象和面向对象
面向对象依靠的是虚函数+继承,用户通过重写基类的虚函数,实现自己的逻辑。
基于对象,依赖类的组合,使用function和bind实现委托机制,更加依赖于回调函数传入逻辑。
muduo库使用的是基于对象的编程思想,只暴露具体类不暴露抽象类,这在Thread类的设计中就得到体现。
如果用面向对象的编程风格封装 Thread 类,则是利用虚函数+继承的方式,通过重写Thread基类的run方法,传入自己的用户逻辑。
在muduo库中采用 C++11的function,将函数作为Thread类的成员,用户只需要将function对象传入线程即可,所以Thread的声明中,应该含有一个function成员变量。
将其当做std::string 、std::vector<>、这样的类型就可以了。只不过其值为函数指针,但比函数指针更灵活。
因为std::function 是一种模板,所以要传入类型,就如std::vector
不过,std::function传入的是函数类型 返回值 (参数类型) 如:std::function
explicit关键字
C++中,explicit关键字只能用于修饰只有一个参数的类构造函数 (或者除了第一个参数外其余参数都有默认值的多参构造函数), 它的作用是表明该构造函数是显示的, 而非隐式的。如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了。
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
PID与TID,Linux的进程和线程
线程之间虽然共享地址空间,但是操作系统仍然赋予了她们一部分专属于自己的空间,用于区分它们,比如线程的tid,这里明确一下操作系统中和进程线程有关的几个id变量。
为什么使用pid_t而不使用pthread_t来标识线程id呢?
pthread_t的值很大,无法作为一些容器的key值。 glibc的Pthreads实现实际上把pthread_t作为一个结构体指针,指向一块动态分配的内存,但是这块内存是可以反复使用的,也就是说很容易造成pthread_t的重复。也就是说pthreads只能保证同一进程内,同一时刻的各个线程不同;不能保证同一个进程全程时段每个线程具有不同的id,不能保证线程id的唯一性。
在LINUX系统中,建议使用gettid()系统调用的返回值作为线程id,这么做的原因:
返回值是一个pid_t,其值是一个很小的整数,方便输出。
在linux系统中,它直接标识内核任务调度id,可通过/proc文件系统中找到对应项:/proc/tid 或者 /proc/pid/task/tid,方便定位到具体线程。任何时刻都是唯一的,并且由于linux分配新的pid采用递增轮回办法,短时间内启动多个线程也会具有不同的id。
关键字:__thread
线程共享进程的数据,如果想要每个线程都有一份独立的数据,那么可以使用__thread关键字修饰数据。
通过使用__thread类型的变量,上面的数据在每个线程中就都有一份而且没有相互覆盖的情况,这个地方如果不知道这种做法的只能通过一个全局的数组来记录了,操作的时候还需上锁,既影响效率还麻烦容易出错。
extern 关键字
extern也可用来进行链接指定;也可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中
,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
说人话,即有以下两个作用:
C的规则
去翻译只是一个声明
而不是定义,具体的定义要在具体的模块中完成举个例子,我们想要声明一个long int runtime_minute变量供多个模块使用:
//time.h
#ifndef TIME_H
#define TIME_H
//首先我们在time.h中进行以下声明:
extern long int runtime_minute;
#endif //TIME_H
在头文件中通过extern声明的变量并没有定义,所以要在time.cpp中给出定义:
//time.cpp
#include "time.h"
//在time.cpp中给出runtime_minute变量的具体定义:
long int runtime_minute = 0;
这时,如果想要在time2.cpp中使用这个定义在time.cpp中的runtime_minute变量,只需要包含time.h这个头文件即可:
//time2.cpp
#include "time.h"
#include
//在time.cpp中给出runtime_minute变量的具体定义:
int main(){
runtime_minute++;
std::cout<
C++ assert()函数用法
其作用是如果它的条件返回错误,则终止程序执行。
原型定义:
#include
void assert( int expression );
assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
muduo库中线程的封装是采用基于对象实现的,首先,为了获得最干净的语义,Thread应该是不可复制的,所以需要继承NonCopyable。
Tid表示线程的真实id。 POSIX的thread库提供了pthread_self获取当前线程的标识符,类型为pthread_t,这是一个结构体,用起来不便。线程的真实id是一个整数,可以通过系统调用syscall(SYS_gettid)获得,在muduo中封装为gettid()函数。调用系统调用开销比较大,因此可以使用一个变量t_cachedTid来存储,在线程第一次使用tid时通过系统调用获得,存储在t_cacheTid中,以后使用时不再需要系统调用了。
muduo库thread.cc代码分为两部分,detail命名空间部分是实现是用的一些全局函数和数据结构,Thread::部分是线程成员函数的实现,
为什么不能直接在创建线程的时候执行某个类的成员函数?
因为pthread_create 的线程函数定义为void *func(void*)
,无法将 non-staic 成员函数传递给pthread_create 。
startThread 是一个外部的函数,用来调用 ThreadData 的 runInThread ,因为 pthread_create 只接受静态函数,所以我们需要一个跳板,这便是 startThread 存在的意义。runInThread 里我们修改线程名字,获取线程 tid 的值,运行 fun 。
这是一个命名空间,不是类,主要是线程类和异常类中需要用到这个命名空间。thread的封装和一个命名空间muduo::CurrentThread紧密相关,在这个空间中定义了和线程相关的属性
以下所有变量都是__thread修饰的,这样的变量是线程局部存储的,也就是说每个线程都有个独立的变量
t_cachedTid 是线程真实pid的缓存,为什么要做缓存呢?因为调用系统调用开销比较大,因此可以使用一个变量t_cachedTid来存储,在线程第一次使用tid时通过系统调用获得,存储在t_cacheTid中,以后使用时不再需要系统调用了。
1. 内联
tid()
如过是第一次调用,调用cacheTid(),再调用detail::gettid(),再调用系统的SYS_gettid,获得线程tid赋值给t_cacheTid,也就是获得当前线程id并缓存
返回t_cacheTid的值
tidString()
返回t_tidString
tidStringLength()
返回t_tidStringLength
name()
返回t_threadName