1.
int
pthread_attr_setstacksize(pthread_attr_t *attr,
size_t
stacksize);
2.
3.
int
pthread_create(pthread_t *
thread
,
const
pthread_attr_t *attr,
4.
void
*(*start_routine) (
void
*),
void
*arg);
线程栈如上图所示,共享进程(或者称之为线程组)的虚拟地址空间。既然多个线程聚集在一起,我怎么知道我要操作的那个线程栈的地址呢。要解决这个问题,必须要领会线程和进程以及线程组的概念。我不想写一堆片汤话,下面我运行我的测试程序,然后结合现象分析原因:
www.it165.net
001.
#include
002.
#include
003.
#include
004.
#include
005.
006.
#define gettid() syscall(__NR_gettid)
007.
008.
pthread_key_t key;
009.
__thread
int
count = 2222;
010.
__thread unsigned
long
long
count2 ;
011.
static
__thread
int
count3;
012.
void
echomsg(
int
t)
013.
{
014.
printf
(
"destructor excuted in thread %x,param=%x\n"
,pthread_self(),t);
015.
}
016.
017.
void
* child1(
void
*arg)
018.
{
019.
int
b;
020.
int
tid=pthread_self();
021.
022.
printf
(
"I am the child1 pthread_self return %p gettid return %d\n"
,tid,gettid());
023.
024.
char
* key_content =
malloc
(8);
025.
if
(key_content != NULL)
026.
{
027.
strcpy
(key_content,
"ACACACA"
);
028.
}
029.
pthread_setspecific(key,(
void
*)key_content);
030.
031.
count=666666;
032.
count2=1023;
033.
count3=2048;
034.
printf
(
"I am child1 , tid=%x ,count (%p) = %10d,count2(%p) = %10llu,count3(%p) = %6d\n"
,tid,&count,count,&count2,count2,&count3,count3);
035.
asm
volatile
(
"movl %%gs:0, %0;"
036.
:
"=r"
(b)
/* output */
037.
);
038.
039.
printf
(
"I am child1 , GS address %x\n"
,b);
040.
041.
sleep(2);
042.
printf
(
"thread %x returns %x\n"
,tid,pthread_getspecific(key));
043.
sleep(50);
044.
}
045.
046.
void
* child2(
void
*arg)
047.
{
048.
int
b;
049.
int
tid=pthread_self();
050.
051.
printf
(
"I am the child2 pthread_self return %p gettid return %d\n"
,tid,gettid());
052.
053.
char
* key_content =
malloc
(8);
054.
if
(key_content != NULL)
055.
{
056.
strcpy
(key_content,
"ABCDEFG"
);
057.
}
058.
pthread_setspecific(key,(
void
*)key_content);
059.
count=88888888;
060.
count2=1024;
061.
count3=2047;
062.
printf
(
"I am child2 , tid=%x ,count (%p) = %10d,count2(%p) = %10llu,count3(%p) = %6d\n"
,tid,&count,count,&count2,count2,&count3,count3);
063.
064.
065.
asm
volatile
(
"movl %%gs:0, %0;"
066.
:
"=r"
(b)
/* output */
067.
);
068.
069.
printf
(
"I am child2 , GS address %x\n"
,b);
070.
071.
sleep(1);
072.
printf
(
"thread %x returns %x\n"
,tid,pthread_getspecific(key));
073.
sleep(50);
074.
}
075.
076.
077.
int
main(
void
)
078.
{
079.
int
b;
080.
pthread_t tid1,tid2;
081.
printf
(
"hello\n"
);
082.
083.
084.
pthread_key_create(&key,echomsg);
085.
086.
asm
volatile
(
"movl %%gs:0, %0;"
087.
:
"=r"
(b)
/* output */
088.
);
089.
090.
printf
(
"I am the main , GS address %x\n"
,b);
091.
092.
pthread_create(&tid1,NULL,child1,NULL);
093.
pthread_create(&tid2,NULL,child2,NULL);
094.
095.
printf
(
"pthread_create tid1 = %p\n"
,tid1);
096.
printf
(
"pthread_create tid2 = %p\n"
,tid2);
097.
098.
sleep(60);
099.
pthread_key_delete(key);
100.
printf
(
"main thread exit\n"
);
101.
return
0;
102.
}
这是一个比较综合的程序,因为我下面要多次从不同的侧面分析。对于现在,我们要展示的是进程 线程 线程组的关系。在一个终端运行编译出来的test2程序,显示的信息如下:
另一个终端看ps信息,ps显示的信息如下:
直接ps,是看不到我们创建的线程的。只有3658一个进程。当我们采用ps -eLf的时候,我们看到了三个线程3658/3659/3660,或者称之为轻量级进程(LWP)。Linux到底是怎么看待这三者的关系的呢:
Linux下多线程程序,一般都是有一个主进程通过调用pthread_create创建了一个或者多个子线程,如同我们的程序,主进程在main中创建了两个子进程。那么Linux到底是怎么看待这些事情的呢?
1.
pid_t pid;
2.
pid_t tgid;
3.
...
4.
struct
task_struct *group_leader;
/* threadgroup leader */
上面三个变量是进程描述符的三个成员变量。pid字面意思是process id,其实叫thread id会更合适。tgid 字面含义是thread group ID。对于存在多个线程的程序而言,每个线程都有自己的pid,没错pid,如同我们例子中的3658/3659/3660,但是都有个共同的线程组ID (TGID):3658 。
好吧,我们再重新说一遍,对于普通进程而言,我们可以称之为只有一个LWP的线程组,pid是它自己的pid,tgid还是它自己,线程组里面只有他自己一个光杆司令,自然group_leader也是它自己。但是多线程的进程(线程组更恰当)则不然。开天辟地的main函数所在的进程会有自己的PID,也会有也TGID,group_leader,都是他自己。注意,它自己也是LWP。后面他使用ptherad_create创建了2个线程,或者LWP,这两个新创建的线程会有自己的PID,但是TGID会沿用创建自己的那个进程的TGID,group_leader也会尊创建自己的进程的进程描述符(task_struct)为自己的group_leader。copy_process函数中有如下代码:
01.
p->pid = pid_nr(pid);
02.
p->tgid = p->pid;
//普通进程
03.
if
(clone_flags & CLONE_THREAD)
04.
p->tgid = current->tgid;
//线程选择叫起它的进程的tgid作为自己的tgid
05.
....
06.
p->group_leader = p;
//普通进程
07.
INIT_LIST_HEAD(&p->thread_group);
08.
...
09.
if
(clone_flags & CLONE_THREAD) {
10.
current->
signal
->nr_threads++;
11.
atomic_inc(¤t->
signal
->live);
12.
atomic_inc(¤t->
signal
->sigcnt);
13.
p->group_leader = current->group_leader;
//线程选择叫起它的进程作为它的group_leader
14.
list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
15.
}
OK,ps -eLf中有个字段叫NLWP,就是线程组中LWP的个数,对于我们的例子,main函数所在LWP+两个线程 = 3.
我们传说的getpid函数,本质取得是进程描述符的TGID,而gettid系统调用,取得才是每个LWP各自的PID。请看上面的图片输出,上面连个线程gettid返回的是3873和3874,是自己的PID。稍微有点毁三观
除此外,需要指出的是用户态pthread_create出来的线程,在内核态,也拥有自己的进程描述符task_struct(copy_process里面调用dup_task_struct创建)。这是什么意思呢。意思是我们用户态所说的线程,一样是内核进程调度的实体。进程调度,严格意义上说应该叫LWP调度,进程调度,不是以前面提到的线程组为单位调度的,本质是以LWP为单位调度的。这个结论乍一看惊世骇俗,细细一想,其是很合理。我们为什么多线程?因为多CPU,多核,我们要充分利用多核,同一个线程组的不同LWP是可以同时跑在不同的CPU之上的,因为这个并发,所以我们有线程锁的设计,这从侧面证明了,LWP是调度的实体。