Linux多线程编程实例解析

举报
沧浪之水 发表于 2020/08/18 20:50:11 2020/08/18
【摘要】 Linux没有真正意义上的线程,它的实现是由进程来模拟,所以属于用户级线程,位于libpthread共享库(所以线程的ID只在库中有效),遵循POSIX标准。 Windows下有一个真正的数据结构TCB来描述线程。 Linux上两个最有名的线程库LinuxThreads和NPTL。

1.背景知识

             Linux没有真正意义上的线程,它的实现是由进程来模拟,所以属于用户级线程,位于libpthread共享库(所以线程的ID只在库中有效),遵循POSIX标准。

 

      Windows下有一个真正的数据结构TCB来描述线程。

 

      Linux上两个最有名的线程库LinuxThreadsNPTL

 

Linux两个线程模型的比较:

 

Linux线程模型的比较

 

 

 

Linux下多线程虚拟地址空间的映射类似于用vfork创建多个子进程。


 

2.进程和线程的区别

      进程:程序的一个动态运行实例,承担分配系统资源的实例。(Linux实现进程的主要目的是资源独占)

    线程:在进程的内部运行(进程的地址空间)运行的一个分支,也是调度的基本单位(调度按LWP调度)。(Linux实现线程的主要目的是资源共享)

 

    线程所有的资源由进程提供。

 

    单进程:只有一个进程的线程(LWP=PID)。

 

    LWP:轻量级进程。

 

    由于同一进程的多个线程共享同一地址空间, Text SegmentData Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境

 

 

1. 文件描述符表

 

2. 每种信号的处理方式(SIG_IGNSIG_DFL或者自定义的信号处理函数)  

 

3. 当前工作目录

  

4. 用户id和组id

 

 但有些资源是每个线程各有一份的:

 

1.线程ID

 

2. 上下文信息,包括各种寄存器的值、程序计数器和栈指针

 

3. 栈空间

 

4. errno变量

 

5. 信号屏蔽字

 

6. 调度优先级

 

多线程程序的优点(相对进程比较而言):

 

1. 多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间,创建销毁速度快。

 

2.是线程间方便的通信机制。由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

 

3.进程控制

Linux系统下,与线程相关的函数都定义在pthread.h头文件中。

创建线程函数———pthread_create函数

#include <pthread.h>

int pthread_create(pthread_t * thread, const pthread_arrt_t* attr,void*(*start_routine)(void *), void* arg);

 

1thread参数是新线程的标识符,为一个整型。

 

2attr参数用于设置新线程的属性。给传递NULL表示设置为默认线程属性。

 

3start_routinearg参数分别指定新线程将运行的函数和参数。start_routine返回时,这个线程就退出了

 

4)返回值:成功返回0,失败返回错误号。

 

        线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,调用pthread_self()可以获得当前线程的id

 

        进程id的类型时pid_t,每个进程的id在整个系统中是唯一的,调用getpid()可以获得当前进程的id,是一个正整数值。

 

终止线程———pthread_cancel函数和pthread_exit函数

终止某个线程而不终止整个进程,可以有三种方法:

 

1. 从线程函数return。这种方法对主线程不适应,main函数return相当于调用exit

 

2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

 

3. 线程可以调用pthread_exit终止自己。

 

#include <pthread.h>

int pthread_cancel(pthread_t thread);

 

1thread参数是目标线程的标识符。

 

2)该函数成功返回0,失败返回错误码。

 

#include <pthread.h>

void pthread_exit(void * retval);

 

1retvalvoid *类型,其它线程可以调用pthread_join获得这个指针。需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是由malloc 配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

 

2pthread_exit函数通过retval参数向线程的回收者传递其退出信息。它执行之后不会返回到调用者,且永远不会失败。

 

线程等待———pthread_join

#include <pthread.h>

void pthread_join(pthread_t thread,void ** retval);

 

1)调用该函数的线程将挂起等待,直到idthread的线程终止。

 

2thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,

 

总结如下:

 

1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。

 

2. 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED

 

3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。 如果对thread线程的终止状态不感兴趣,可以传NULLvalue_ptr参数。

 

3)成功返回0,失败返回错误码。可能出现的错误码:


 

4.分离线程

         1.在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。

 

         2.一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源

(例如栈)是不释放的。(默认情况下线程的创建都是可结合的)

 

         3.一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终止时由系统自动释放。

 

         4. 如果一个可结合线程结束运行但没有被join,会导致部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。 

          

          调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞。如何解决这种情况呢?

          

          例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码 pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。

 

 

 

Linux系统下的多线程遵循POSIX线程接口,称为 pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux pthread的实现是通过系统调用clone()来实现的。clone()是 Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的 多线程程序 pthread_create.c

 

 

一个重要的线程创建函数原型:

#include <pthread.h>

int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg);

 

返回值:若是成功建立线程返回0,否则返回错误的编号

形式参数:

                pthread_t *restrict tidp 要创建的线程的线程id指针

                const pthread_attr_t *restrict attr 创建线程时的线程属性

                void* (start_rtn)(void) 返回值是void类型的指针函数

                void *restrict arg   start_rtn的行参

                

例程1                               

    功能:创建一个简单的线程

    程序名称:pthread_create.c   

 

代码如下:

 

#include <stdio.h>

#include <pthread.h>

 

void *mythread1(void)

{

int i;

for(i = 0; i < 10; i++)

{

printf("This is the 1st pthread,created by xiaoqiang!\n");

sleep(1);

}

}

 

void *mythread2(void)

{

int i;

for(i = 0; i < 10; i++)

{

printf("This is the 2st pthread,created by xiaoqiang!\n");

sleep(1);

}

}

 

int main(int argc, const char *argv[])

{

int i = 0;

int ret = 0;

pthread_t id1,id2;

 

ret = pthread_create(&id1, NULL, (void *)mythread1,NULL);

if(ret)

{

printf("Create pthread error!\n");

return 1;

}

 

ret = pthread_create(&id2, NULL, (void *)mythread2,NULL);

if(ret)

{

printf("Create pthread error!\n");

return 1;

}

 

pthread_join(id1,NULL);

pthread_join(id2,NULL);

 

return 0;

}

执行结果如下:

 

fs@ubuntu:~/qiang/thread$ vi thread1.c

fs@ubuntu:~/qiang/thread$ gcc -o thread1 thread1.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread1

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 2st pthread,created by xiaoqiang!

This is the 1st pthread,created by xiaoqiang!

fs@ubuntu:~/qiang/thread$

两个线程交替执行。

另外,因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread,否则编译不过,会出现下面错误

thread_test.c: 在函数 ‘create’ 中:

thread_test.c:7: 警告: 在有返回值的函数中,程序流程到达函数尾

/tmp/ccOBJmuD.o: In function `main':thread_test.c:(.text+0x4f):对‘pthread_create’未定义的引用

collect2: ld 返回 1

此例子介绍了创建线程的方法

 

 

 

下面例子介绍向线程传递参数。

例程2

功能:向新的线程传递整形值

程序名称:pthread_int.c

 

代码如下:

 

#include <stdio.h>

#include <pthread.h>

 

void *create(void *arg)

{

int *num;

num = (int *)arg;

printf("Create parameter is %d\n",*num);

return (void *)0;

}

 

int main(int argc, const char *argv[])

{

pthread_t id1;

int error;

 

int test = 4;

int *attr = &test;

 

error = pthread_create(&id1,NULL,create,(void *)attr);

 

if(error)

{

printf("Pthread_create is not created!\n");

return -1;

}

sleep(1);

 

printf("Pthread_create is created..\n");

return 0;

}

执行结果如下:

 

fs@ubuntu:~/qiang/thread$ vi thread2.c

fs@ubuntu:~/qiang/thread$ gcc -o thread2 thread2.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread2

Create parameter is 4

Pthread_create is created..

fs@ubuntu:~/qiang/thread$

例程总结:

    可以看出来,我们在main函数中传递的整行指针,传递到我们新建的线程函数中。

 

在上面的例子可以看出来我们向新的线程传入了另一个线程的int数据,线程之间还可以传递字符串或是更复杂的数据结构。

例程3

程序功能:向新建的线程传递字符串

程序名称:pthread_string.c

代码如下:

 

#include <stdio.h>

#include <pthread.h>

 

void *create(char *arg)

{

char *str;

str = arg;

printf("The parameter passed from main is %s\n",str);

 

return (void *)0;

}

 

int main()

{

int error;

pthread_t id1;

char *str1 = "Hello ,xiaoqiang!";

char *attr = str1;

error = pthread_create(&id1, NULL, create, (void *)attr);

 

if(error != 0)

{

printf("This pthread is not created!\n");

return -1;

}

sleep(1);

 

printf("pthread is created..\n");

return 0;

}

执行结果如下:

 

fs@ubuntu:~/qiang/thread$ ./thread3

The parameter passed from main is Hello ,xiaoqiang!

pthread is created..

fs@ubuntu:~/qiang/thread$

例程总结:

可以看出来main函数中的字符串传入了新建的线程中。

 

例程4

程序功能:向新建的线程传递字符串

程序名称:pthread_struct.c

 

代码如下:

 

#include <stdio.h>

#include <pthread.h>

#include <stdlib.h>

 

struct menber

{

int a;

char *s;

};

 

void *create(void *arg)

{

struct menber *temp;

temp = (struct menber *)arg;

printf("menber->a = %d\n",temp->a);

printf("menber->s = %s\n",temp->s);

 

return (void *)0;

}

 

int main()

{

int error;

pthread_t id1;

struct menber *p;

p = (struct menber *)malloc(sizeof(struct menber));

p->a = 1;

p->s = "xiaoqiang!";

 

error = pthread_create(&id1,NULL,create,(void *)p);

 

if(error)

{

printf("pthread is not created!\n");

return -1;

}

sleep(1);

printf("pthread is created!\n");

 

free(p);

p = NULL;

return 0;

}

执行结果如下:

 

fs@ubuntu:~/qiang/thread$ vi thread4.c

fs@ubuntu:~/qiang/thread$ gcc -o thread4 thread4.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread4

menber->a = 1

menber->s = xiaoqiang!

pthread is created!

fs@ubuntu:~/qiang/thread$

例程总结:

    可以看出来main函数中的一个结构体传入了新建的线程中。

    线程包含了标识进程内执行环境必须的信息。他集成了进程中的所有信息都是对线程进行共享的,包括文本程序、程序的全局内存和堆内存、栈以及文件描述符

 

例程5

程序目的:验证新建立的线程可以共享进程中的数据

程序名称:pthread_share.c

 

代码如下:

 

#include <stdio.h>

#include <pthread.h>

 

static int a = 5;

 

void *create(void *arg)

{

printf("New pthread...\n");

printf("a = %d\n",a);

 

return (void *)0;

}

 

int main(int argc, const char *argv[])

{

int error;

pthread_t id1;

 

error = pthread_create(&id1, NULL, create, NULL);

if(error != 0)

{

printf("new thread is not created!\n");

return -1;

}

sleep(1);

printf("New thread is created...\n");

 

return 0;

}

结果如下:

 

fs@ubuntu:~/qiang/thread$ vi thread5.c

fs@ubuntu:~/qiang/thread$ gcc -o thread5 thread5.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread5

New pthread...

a = 5

New thread is created...

fs@ubuntu:~/qiang/thread$

例程总结:

可以看出来,我们在主线程更改了我们的全局变量a的值的时候,我们新建立的线程则打印出来了改变的值,可以看出可以访问线程所在进程中的数据信息。

 

 

2、线程的终止

如果进程中任何一个线程中调用exit_Exit,或者是_exit,那么整个进程就会终止,

与此类似,如果信号的默认的动作是终止进程,那么,把该信号发送到线程会终止进程。

线程的正常退出的方式:

(1) 线程只是从启动例程中返回,返回值是线程中的退出码

(2) 线程可以被另一个进程进行终止

(3) 线程自己调用pthread_exit函数

 

两个重要的函数原型:

 

include <pthread.h>

void pthread_exit(void *rval_ptr);

/*rval_ptr 线程退出返回的指针*/

 

int pthread_join(pthread_t thread,void **rval_ptr);

   /*成功结束进程为0,否则为错误编码*/

 

pthread_join使一个线程等待另一个线程结束。

代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

头文件 #include <pthread.h>

函数定义: int pthread_join(pthread_t thread, void **retval);

描述 pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

参数 thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。

返回值 0代表成功。 失败,返回的则是错误号。

 

 

例程6

程序目的:线程正常退出,接受线程退出的返回码

程序名称:pthread_exit.c

执行代码如下:

 

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

 

void *create(void *arg)

{

printf("new thread is created ... \n");

return (void *)0;

}

 

int main(int argc,char *argv[])

{

pthread_t tid;

int error;

void *temp;

 

error = pthread_create(&tid, NULL, create, NULL);

 

if( error )

{

printf("thread is not created ... \n");

return -1;

}

error = pthread_join(tid, &temp);

 

if( error )

{

printf("thread is not exit ... \n");

return -2;

}

 

printf("thread is exit code %d \n", (int )temp);

   

return 0;

}

执行结果如下:

 

fs@ubuntu:~/qiang/thread$ vi thread6.c

fs@ubuntu:~/qiang/thread$ gcc -o thread6 thread6.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread6

new thread is created ...

thread is exit code 0

fs@ubuntu:~/qiang/thread$

例程总结:

可以看出来,线程退出可以返回线程的int数值。

 

 

线程退出不仅仅可以返回线程的int数值,还可以返回一个复杂的数据结构

 

例程7

程序目的:线程结束返回一个复杂的数据结构

代码如下:

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>

 

struct menber

{

int a;

char *b;

}temp={8,"xiaoqiang"};

 

void *create(void *arg)

{

printf("new thread ... \n");

return (void *)&temp;

}

 

int main(int argc,char *argv[])

{

int error;

pthread_t tid;

struct menber *c;

 

error = pthread_create(&tid, NULL, create, NULL);

 

if( error )

{

printf("new thread is not created ... \n");

return -1;

}

printf("main ... \n");

 

error = pthread_join(tid,(void *)&c);

 

if( error )

{

printf("new thread is not exit ... \n");

return -2;

}

printf("c->a = %d  \n",c->a);

printf("c->b = %s  \n",c->b);

sleep(1);

return 0;

}

执行结果如下:

fs@ubuntu:~/qiang/thread$ gcc -o thread7 thread7.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread7

main ...

new thread ...

c->a = 8 

c->b = xiaoqiang 

fs@ubuntu:~/qiang/thread$

例程总结:

一定要记得返回的数据结构要是在这个数据要返回的结构没有释放的时候应用,如果数据结构已经发生变化,那返回的就不会是我们所需要的,而是脏数据。

 

3、线程标识

      函数原型:

#include <pthread.h>

pthread_t pthread_self(void);

pid_t getpid(void);

    getpid()用来取得目前进程的进程识别码,函数说明

 

例程8

程序目的:实现在新建立的线程中打印该线程的id和进程id

代码如下:

#include <stdio.h>

#include <pthread.h>

#include <unistd.h> /*getpid()*/

 

void *create(void *arg)

{

printf("New thread .... \n");

printf("This thread's id is %u  \n", (unsigned int)pthread_self());

printf("The process pid is %d  \n",getpid());

return (void *)0;

}

 

int main(int argc,char *argv[])

{

pthread_t tid;

int error;

 

printf("Main thread is starting ... \n");

 

error = pthread_create(&tid, NULL, create, NULL);

 

if(error)

{

printf("thread is not created ... \n");

return -1;

}

printf("The main process's pid is %d  \n",getpid());

sleep(1);

return 0;

}

<span style="font-family:Arial;BACKGROUND-COLOR: #ffffff"></span>

执行结果如下:

fs@ubuntu:~/qiang/thread$ gcc -o thread8 thread8.c -lpthread

fs@ubuntu:~/qiang/thread$ ./thread8

Main thread is starting ...

The main process's pid is 4955 

New thread ....

This thread's id is 3075853120 

The process pid is 4955 

fs@ubuntu:~/qiang/thread$

 

版权声明:本文为CSDN博主「zqixiao_09」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zqixiao_09/java/article/details/50298693

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200