线程标识符
Linux中,每个进程有一个pid,类型为pid_t,由getpid()
取得。Linux下的POSIX线程也有一个id,类型为pthread_t,由pthread_self()
取得,该id由线程库维护,其id空间是各个进程独立的(即不同进程中的线程可能有相同的id)
。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP:轻量级进程),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。
有时候我们可能需要知道线程的真实pid。比如进程P1要向另外一个进程P2中的某个线程发送信号时,既不能使用P2的pid,更不能使用线程的pthread id,而只能使用该线程的真实pid,称为tid。
函数gettid()
可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall
来获取
return syscall(SYS_gettid)
但因为使用线程调用开销很大,所以我们需要对所获取的tid做一个缓存,防止每次都使用系统调用,从而提高获取tid的效率
muduo库中线程类的实现,采用的是基于对象的方式来实现的
started_
表示线程是否已经启动
pthreadId_
:进程中线程id
tid_
真实线程id
func_
该线程要回调的函数
name_
线程名称
numCreated
已经创建的线程个数,是一个原子整数类
代码还是很清楚的, Thread.cc
其中CurrentThread
中实现了对线程真实tid的缓存
注意一下__thread
修饰的变量是线程局部存储的, 且只能修饰POD类型,不能修饰class类型,因为无法自动调用构造函数和析构函数
__thread, gcc内置的线程局部存储设施
POD类型 (plain old data),与C兼容的原始数据,例如结构和整型等C语言中的类型是POD类型,但带有用户定义的构造函数或虚函数的类则不是
__thread string t_obj1(“hello”); // 错误,不能调用对象的构造函数
__thread string* t_obj2 = new string; // 错误,指针类型是POD类型,但是初始化必须用编译期常量,不能是运行期的
__thread string* t_obj3 = NULL; // 正确,但是需要手工初始化并销毁对象
__thread变量在每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。用一个例子来理解它的用法。
#include <pthread.h>
#include <iostream>
#include <unistd.h>
using namespace std;
//__thread int var = 5;
int var = 5;
void *worker1(void* arg);
void *worker2(void* arg);
int main()
{
pthread_t p1, p2;
pthread_create(&p1, NULL, worker1, NULL);
pthread_create(&p2, NULL, worker2, NULL);
pthread_join(p1, NULL);
pthread_join(p2, NULL);
return 0;
}
void *worker1(void* arg)
{
cout << ++var << endl;
}
void *worker2(void* arg)
{
cout << ++var << endl;
}
/**
* 使用__thread关键字,输出为:
* 6
* 6 个线程的值互不干扰, 跟全局变量?
*
* 不使用__thread关键字,输出为:
* 6
* 7
* /
pthread_atfork()函数
#include <pthread.h>
int pthread_atfork(void (*prepare)(void),
void (*parent)(void),
void (*child)(void));
在调用fork时,内部创建子进程之前在父进程中会调用prepare
,内部创建子进程成功后,父进程会调用parent
,子进程会调用child
, 这就是pthread_atfork()的作用, 因为在fork之前可能有多个线程,有可能是在主线程中调用也有可能是在子线程中调用, 如果fork()是在子线程中调用得到一个新进程,新进程就只有一个执行序列,只有一个线程(调用fork的线倍继承下来),用pthread_atfork()来可以避免这种情况
对于编写多线程程序来说,最好不要调用fork(),即不要编写多线程多进程程序,因为Linux的fork()只克隆当前线程的thread of control, 不克隆其他线程。 fork()之后,除了当前线程之外,其他线程都消失了,也就是说,不能够一下子fork()和父进程一样的多线程子进程
fork()之后子进程中只有一个线程,其他线程都小时了,这就曹正一个危险的局面:其他线程可能正好位于临界区指内,持有了某个锁,而它忽然死亡,再也没有机会去解锁了。如果子进程试图再对同一个mutex加锁,就会死锁
那我们调用atfork()
的话就可以在fork之前,即创建子进程之前去解锁,那样拷贝下来的就是解锁的,就不会死锁了
∴ 父进程在创建一个子进程的时候,指挥复制当前线程的执行状态,其他线程不会复制,因此子进程会处于死锁的状态