实验内容
-
编译运行Lecture 14示例代码:
alg.14-1
~alg.14-7
,指出你认为不合适的地方并加以改进 -
对
clone()
的flags采用不同的配置,设计测试程序讨论其调用结果。- 配置包括CLONE_PARENT,CLONE_VFORK,CLONE_FILES,CLONE_SIGHAND,CLONE_THREAD
实验环境
-
架构:Intel x86_64(双系统)
-
操作系统:Ubuntu 20.04
-
汇编器:gas(GNU Assembler) in AT&T mode
-
编译器:gcc
技术日志
alg.14-1-tls-thread
分析
执行命令
gcc -o alg.14-1-tls-thread.o alg.14-1-tls-thread.c -l pthread
p.s:由于pthread
不是C库中的内容,而是POSIX提供的线程集成操作集,因此在生成可执行文件时需将其加入编译的路径中。
执行结果
代码及原理分析
代码原理
-
pthread
主要函数功能分析Pthread函数 pthread_create create a new thread pthread_exit terminate the calling thread pthread_join wait for a specific thread to exit pthread_yield release the CPU to let another thread run pthread_attr_init create and initialize a thread's attribute structure pthread_attr_destroy remove a thread's attribute structure -
pthread_create
函数分析函数原型
int pthread_create(pthread_t &restict ptid,const pthread_attr_t *restrict_attr,void*(start_rtn)(void*),void*restrict arg)
第一个参数
ptid
用来存储该函数申请到的线程号(不唯一);第二个参数pthread_attr_t
表示该申请的子线程需要拥有的性质,没有特殊要求则传入(NULL);第三个参数表明生成的子线程在完成创建之后将要执行的函数,因此传入该函数对应的首地址(对应线程代码段的首地址),注意:此处所使用函数的首地址与父线程中该函数的地址相同。最后一个参数,表明需要传入前一个函数的参数列表。
宏定义解析
#define __NR_gettid 186 /* 186 for x86-64, 224 for i386-32 */ #define gettid() syscall(__NR_gettid) /* long int */ __thread int tlsvar = 0; /* tlsvar for each thread; interpreted by language compiler */
首先此处定义了一个调用system call
查询当前线程线程号的宏定义,此处的__NR_gettid
由主机的架构决定x86-64
体系使用186,i386-32
体系则使用224.在调用该宏定义后则会在终端自动打印出当前线程的线程号。随后,定义一个用于之后实现tls
的全局变量tlsvar
,值得注意的是该全局变量会由编译器进行特殊的处理,其结果是所有生成的子进程都会拥有一个自己的tlsvar
(通过这个变量作为标识符,可以让每个线程在共有的全局变量区内使用某块特定的内存存储自己私有的变量)
子线程执行的函数(其拥有的代码段)
void printf_tlsvar(char *param) { /* sharing the TLS tlsvar with thread_worker */ printf("%s%ld, tlsvar = %d\n", param, gettid(), tlsvar); return; } static void* thread_worker(void* arg) { char *param = (char *)arg; int randomcount; for (int i = 0; i < 5; ++i) { randomcount = rand() % 100000; for (int k = 0; k < randomcount; k++) ; /* delay for a random time */ printf_tlsvar(param); tlsvar++; /* each thread has its local tlsvar */ } pthread_exit(0); }
在printf_tlsvar(char *param)
中,会自动打印出传入的参数(此处使用不同长度\t
来区分不同线程),以及当前线程号及其对应的tls_var
的数值。为了使得不同tls_var
的出现拥有随机性,此处使用randomcount = rand() % 100000;for (int k = 0; k < randomcount; k++) ;
让任意线程延迟随机长的时间段。当执行完这段函数后,则自动退出成功pthread_exit(0);
。
生成子进程的main函数分析
int main(void) { pthread_t ptid1, ptid2; char para1[] = "\t\t\t"; char para2[] = "\t\t\t\t\t\t"; int randomcount; pthread_create(&ptid1, NULL, &thread_worker, para1); pthread_create(&ptid2, NULL, &thread_worker, para2); printf("parent tid1 tid2\n"); printf("================ ================ ================\n"); for (int i = 0; i < 5; ++i) { randomcount = rand() % 100000; for (int k = 0; k < randomcount; k++) ; printf("%ld, tlsvar = %d\n", gettid(), tlsvar); tlsvar++; /* main- thread has its local tlsvar */ } sleep(1); pthread_join(ptid1, NULL); pthread_join(ptid2, NULL); return 0; }
在主线程中,首先使用para1
与para2
用来更明显的区分不同的子线程。随后,调用pthread_create()
函数创建两个子进程,并将para1
与para2
作为参数传入函数中(此时,二者的代码段首地址相同,但是堆栈空间不同)。然后主线程与两个子线程同时执行打印线程号以及tlsvar
的相似代码段,从执行结果可以看到,三个子线程的tlsvar
完全隔离。最后,在完成打印任务休眠一秒后,在等待两个子线程成功退出后(调用join
函数),结束自身进程。
alg.14-2-tls-pthread-key-1.c分析
执行命令
gcc -o alg.14-2-tls-pthread-key-1.o alg.14-2tls-pthread-key-1.c -l pthread
执行结果
代码原理与分析
代码原理:
$$
\begin{aligned} impleamentation\_of\_TLS: \_\_thread&:tlsvar\,for\,each\,thread,gcc\,extension\,of\,C,interpreted\,by compiler,a\,language\,level\,solutional\,of\,TLS\\ pthread\,API&:\ \begin{aligned} &pthread\_key\_create\\&pthread\_get\_spcific\\&pthread\_set\_sepcific \end{aligned} \end{aligned}
$$
pthread_key_create
创建该线程独有的关键字变量(创建时需传入call back函数,以关闭申请的资源),随后调用pthread_get_specific()
并将key