Linux多线程编程(一)

什么是线程

先来举一个我们生活中的实例,我们都使用过一个强大的软件—迅雷。那你必然知道迅雷有一个边下边播的功能,我们在下载的时候还能同时进行观看。这就是一个多线程实例。
线程是进程内部的执行分支。

  • 打开迅雷软件—–向系统内核索要资源,启动“迅雷”进程,。
  • 开始下载一个电影—–从索要的资源中调度分配一部分资源,启动下载线程。
  • 开始播放电影—–再索要的资源中调度分配一部分资源次从,启动播放线程。

由上我们就不难理解,进程是资源分配的基本单位,线程是调度的基本单位。
线程有主次之分,主线程负责分配调度,新线程负责执行,每个进程只有一个主线程,新线程可以有多个。当主线程被杀死或者运行结束时,整个进程随之结束,当然那么多的线程也会结束。
我们可以建一个模型来理解上述文字:
这里写图片描述
当主线程启动程序关闭时,整个迅雷软件就会关闭,下载,播放线程当然也就关闭。我们不可能在还下载完电影就关闭下载软件吧?
所以这里要非常注意一点,我们在进行多线程编程时,要让主线程等待新线程运行结束后再结束。

线程属性

在Linux在没有实质意义上的线程,Linux是通过进程来模拟线程,而这些模拟出的线程又叫轻量级进程。我们知道,每个进程都有一个进程控制块PCB,操作系统通过控制PCB来控制进程,进程通过PCB与地址空间,页表的合作来访问物理内存,如下图:
这里写图片描述
当我们创建多个线程时,就会拥有过个PCB,这些PCB(包括主线程)共享同一份地址空间,也就是说这些线程共享同一份进程资源和环境。如下图:
这里写图片描述
这些数据包括:文件描述符表,每种信号的处理方式,当前工作目录,用户ID和组ID等。
但是有些资源是每个线程独有一份的:
线程ID,上下文,各种寄存器的值,程序计数器,栈指针,栈空间,error变量,信号屏蔽字,调度优先级等。尤其注意这里的线程ID和栈空间。

线程操作函数

因为Linux上的线程函数位于libpthread共享库中,因此在编译时要加上-lpthread。例如:

gcc -o test test.c -lpthread

具体原因可参照博客:
http://blog.csdn.net/llzk_/article/details/55519242

创建线程

在上文中我提到过一个名字叫线程ID,每个线程都有一个自己的线程ID,就像每个线进程都有一个进程ID一样,进程ID在整个系统中是唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。
线程ID是由pid_t数据类型来表示,是一个非负整数。线程ID是由数据类型 pthread_t来表示。
函数pthread_create用来创建一个线程。

int pthread_create(pthread_t *thread,const pthread_attr_t* attr,void*(*start routine)(void*),void *arg)

参数:

  • thread,为一个pthread_t类型的指针,指向一个内存单元。新创建的线程的线程ID会被设置在thread指向的内存单元。
  • attr,用于定制各种不同的线程属性。
  • start routine,为一个函数指针,新创建的线程从start routine函数的地址开始运行。该函数的参数是一个无类型指针参数。这个参数后面我们会提到。
  • arg,为一个无类型指针,当我们需要为第三个参数函数指针传参时,那么便需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。可以理解为此arg参数就是上面函数指针的参数。

头文件:

#include<pthread.h>

返回值:成功时返回0,失败时返回错误码。以前学的系统函数都是成功返回0,失败返回-1,而错误码保存在全局变量error中,pthread库的函数都是通过返回值返回错误码,虽然每个线程也都有一个error,但这是为了兼容其他函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。

示例代码:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>

//新线程,每秒打印一次,共打印五次。
void* thread_run(void *arg)
{
    int count = 0;
    while(count++ < 5)
    {
        sleep(1);
        printf("thread , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    printf("thread is over...\n");
    return  NULL;
}


int main()
{
    //主线程
    printf("phread\n");
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,thread_run,pthread_self());
    if(ret != 0)
    {
        printf("pthread_creat error\n");
        return -1;
    }
    else
    {
            sleep(1);
            printf("main , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }

    int exitCode;
    pthread_join(tid,(void**)&exitCode);//用来等待新线程的结束

    printf("main is over...%d\n",exitCode);
    return 0;
}

上面代码中用到了一个pthread_join函数,此函数的功能就是等待一个线程的结束。此例中让主线程等待新线程的结束。第一个参数为新线程的线程ID,第二个参数用来接收新线程的退出码。
执行结果应为主线程打印1次,新线程打印五次:

这里写图片描述

线程终止

线程终止有三个方式。

  • 简单的从启动例程中返回,返回值是线程的退出码。
    这种方式是最简单的方式,上面例子thread_run函数直接return就是这种方式。

  • 线程可以被同一进程中的其他线程调用pthread_cancle终止。
    例如在主线程中终止新线程,仍为上面的例子。pthread_cancle的参数为要结束线程的线程ID。
    代码:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>

void* thread_run(void *arg)
{
    int count = 0;
    while(count++ < 5)
    {
        sleep(1);
        printf("thread , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    printf("thread is over...\n");
    return  NULL;
}
int main()
{
    printf("phread\n");
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,thread_run,pthread_self());
    if(ret != 0)
    {
        printf("pthread_creat error\n");
        return -1;
    }
    else
    {
            sleep(1);
            printf("main , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    pthread_cancel(tid);//在主线程中结束子线程

    int exitCode;
    pthread_join(tid,(void**)&exitCode);

    printf("main is over...%d\n",exitCode);
    return 0;
}

执行结果应为主线程打印一次后直接结束,新线程退出码为-1。如果一个线程被其他线程调用pthread_cancel异常终止掉,它返回的退出码将是常数PTHREAD_CANCELED。这个宏被定义在pthread.h中,值为-1。

这里写图片描述

  • 线程可以调用pthread_exit自己终止自己。
    pthread_exit的参数为退出码。注意,pthread_exit的参数或者新线程return的指针所指向的内存单元必须是全局的或者由malloc分配的,因为当其他线程pthread_join得到这个返回指针时新线程函数已经退出了。
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>

void* thread_run(void *arg)
{
    int count = 0;
    pthread_t mid = *(pthread_t*)arg;
    while(count++ < 5)
    {
        sleep(1);
        printf("thread , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    printf("thread is over...\n");
    pthread_exit((void*)10);
}
int main()
{
    printf("phread\n");
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,thread_run,pthread_self());
    if(ret != 0)
    {
        printf("pthread_creat error\n");
        return -1;
    }
    else
    {
            sleep(1);
            printf("main , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    int exitCode;
    pthread_join(tid,(void**)&exitCode);
    printf("main is over...%d\n",exitCode);
    return 0;
}

执行结果为:程序正常执行,但新线程退出码为10。

这里写图片描述

一般情况下,线程终止后,资源不会被立即释放,其终止状态一直保留到其他线程调用pthread_join获取它的状态为止,这时系统擦才会释放它所占用的资源。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用额度所有资源,而不保留终止状态,这时主线程也不必一直阻塞等待,可以去做其他的工作。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。对一个detach的线程调用pthread_join或者pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

这里要补充一个有关线程的概念:

  • 分离线程

    线程是可分离的(detached)或者结合的(joinable),一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,他的存储器资源(eg:栈)是不可释放的。相反一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
    默认情况下,线程被创建成可结合的。为了避免村吃起的泄露,每个可结合线程都应该被显示的回收,即调用pthread_join。若没有被join,则会造成资源,内存的泄露。
    因为调用pthread_join后,如果应该被join的新线程没有运行结束,调用者会被阻塞,我们有时候并不希望如此。这时可在新线程中加入代码pthread_detach(pthread_self())或者主线程调用pthread_detach(thread_id)来将新线程设置成可分离的,如此一来,新线程运行结束后会自动释放所有资源。

话说回来。除去以上三种线程终止方式外,当任意一个线程调用exit或者_exit时,则整个进程的所有线程都终止。

我们能不能用新线程结束主线程呢?

答案是可以的,但是新线程如何获取主线程的线程ID呢?
这时就用到了pthread_create函数的第四个参数。将主线程的线程ID作为第四个参数传入。在新线程中就可以以参数msg获取到。具体实现看代码:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>

void* thread_run(void *arg)
{
    int count = 0;
    pthread_t mid = *(pthread_t*)arg;//直接解引用msg获取主线程ID
    pthread_cancel(mid);
    while(count++ < 5)
    {
        sleep(1);
        printf("thread , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    printf("thread is over...\n");
    return  NULL;   
}
int main()
{
    printf("phread\n");
    pthread_t tid;
    pthread_t mid = pthread_self();//调用self函数获取主线程ID
    int ret = pthread_create(&tid,NULL,thread_run,(void*)&mid);//将主线程ID作为第四个参数传入
    if(ret != 0)
    {
        printf("pthread_creat error\n");
        return -1;
    }
    else
    {
            sleep(1);
            printf("main , pid: %d , ppid: %d , tid: %lu\n",getpid(),getppid(),pthread_self());
    }
    int exitCode;
    pthread_join(tid,(void**)&exitCode);

    printf("main is over...%d\n",exitCode);
    return 0;
}

运行结果为:主线程不运行,新线程运行5次后结束。

这里写图片描述

  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值