POSIX 多线程开发

POSIX 多线程开发


一 线程的创建


  POSIX API中,创建线程的函数是 pthread_create,声明如下:

int pthread_create(pthread_t *pid, const pthread_attr_t *attr, (void*) (*start_routine)(void*), void *arg);

  其中,参数Pid指向返回线程的ID,attr是指向属性的指针,如果为NULL,则使用默认属性。start_routine是线程函数的地址,arg指向传给线程函数的参数。如果执行成功,返回0。

  POSIX 还提供了pthread_join函数来等待子线程结束,并回收子线程资源。声明如下:

int pthread_join(pthread_t pid, void **value_ptr);

  value_ptr用来获得子线程的返回值。如果执行成功,返回0。


举例:创建一个线程,并传递结构体作为参数

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

typedef struct
{
    int n;
    char *str;
}MYSTRUCT;

void *ThreadFun(void *arg)
{
    MYSTRUCT *p = (MYSTRUCT *)arg;
    printf("in Thread function: n = %d, str = %s\n", p->n, p->str);
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t threadId;
    int ret;
    MYSTRUCT mystruct;

    mystruct.n = 10;
    mystruct.str = (char *)("hello world");

    ret = pthread_create(&threadId, NULL, ThreadFun, (void *)&mystruct);
    if (ret)
    {
        printf("pthread_create failed: %d\n", ret);
        return -1;
    }
    pthread_join(threadId, NULL);
    printf("in main: thread is created\n");

    return 0;
}

  终端编译时,需要连接linux下pthread库,输入命令:

g++ -o test test.cpp -lpthread


二 线程的属性


  POSIX标准规定线程有多个属性。主要包括:分离状态、调试策略和参数、作用域、栈尺寸、栈地址、优先级等。Linux将线程属性用一个联合体 pthread_attr_t 表示,用函数 pthread_getattr_np 来获取结构体值。声明如下:

#define _GNU_SOURCE		//	必须在头文件之前定义宏
#include <pthread.h>

int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);

  当获得的属性的结构体不再需要的时候,要用函数 pthread_attr_destroy进行销毁,从而释放相关的资源。

int pthread_attr_destroy(pthread_attr_t *attr);

  另外提供 pthread_attr_init 函数来初始化一个线程属性结构体。声明如下:

int pthread_attr_init(pthread_attr_t *attr);

1 分离状态

  POSIX线程的分离状态决定一个线程以什么样的方式来终止自己。分离状态只有两种:

  1. 分离的(detached):一个分离的是不可能被其它线程回收或杀死的,它所占的资源在终止时由系统自动释放。
  2. 可连接的(joinable):默认情况下创建的线程是可连接的,一个可连接的线程结束时不会自动回收线程资源(线程所占用的堆栈、线程描述符等),必须等待其它线程通过 pthread_join 函数回收。

  将线程转为可分离线程可以通过 pthread_detach 函数 或者通过 函数 pthread_attr_setdetachstate 设置线程分离状态属性。声明如下:

int pthread_detach(pthread_t thread);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  POSIX还提供了 pthread_exit 函数让线程退出。但是:当主线程使用此函数时,并不会结束进程,如果有其它创建正在运行的话,它只会提前终止主线程。


举例:创建一个可分离的线程,且 main 线程先退出

#include <iostream>
#include <pthread.h>

using namespace std;

void *ThreadFun(void *arg)
{
    cout << "sub thread is running" << endl;
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t threadId;
    pthread_attr_t threadAttr;
    int res;

    res = pthread_attr_init(&threadAttr);
    if (res)
    {
        cout << "pthread_attr_init failed" << endl;
    }

    res = pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);	//设置分离状态
    if (res)
    {
        cout << "pthread_attr_setdetachstate failed:" << res << endl;
    }

    res = pthread_create(&threadId, &threadAttr, ThreadFun, NULL);
    if (res)
    {
        cout << "pthread_create failed:" << res << endl;
    }

    cout << "main thread will exit" << endl;
    pthread_exit(NULL);	//	主线程退出,但进程不会退出,下面的语句不会再执行。
    cout << "main thread has exited, this line will not run\n" << endl;
    return 0;
}

举例:把可连接线程转换为可分离线程。

#ifndef _GNU_SOURCE
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#endif

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

static void *ThreadStart(void *arg)
{
    int i,s;
    pthread_attr_t pthreadAttr;

    s = pthread_getattr_np(pthread_self(), &pthreadAttr);
    if (s != 0)
    {
        printf("pthread_getattr_np failed\n");
    }
    s = pthread_attr_getdetachstate(&pthreadAttr, &i);
    if (s)
    {
        printf("pthread_attr_getdetachstate failed\n");
    }
    printf("Detach state = %s\n",
           (i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :
           (i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :
           "???");

    pthread_detach(pthread_self());	//	转换线程为可分离线程
    s = pthread_getattr_np(pthread_self(), &pthreadAttr);
    if (s != 0)
    {
        printf("pthread_getattr_np failed\n");
    }
    s = pthread_attr_getdetachstate(&pthreadAttr, &i);
    if (s)
    {
        printf("pthread_attr_getdetachstate failed\n");
    }
    printf("after pthread_detach,\n Detach state  = %s\n",
           (i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :
           (i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :
           "???");

    pthread_attr_destroy(&pthreadAttr);	//	销毁属性
}

int main(int argc, char *argv[])
{
    pthread_t threadID;
    int s;
    s = pthread_create(&threadID, NULL, &ThreadStart, NULL);
    if (s != 0)
    {
        printf("pthread_create failed\n");
        return 0;
    }

    pthread_exit(NULL);	//	主线程退出,但进程并不马上结束
}

2 栈尺寸

  线程的默认堆栈大小是1MB,局部变量、函数参数、返回地址等都存放在栈空间里,动态分配的内存属于堆空间。因此,线程函数中的局部变量大小不能超过默认栈尺寸大小。获取线程栈尺寸属性的函数是 pthread_attr_getstacksize,声明如下:

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

  stacksize的单位是字节,如果执行成功,返回0,否则返回错误码。


举例:获得线程默认栈尺寸和最小尺寸

#ifndef _GNU_SOURCE
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#endif

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>

static void *ThreadFunc(void *arg)
{
    int i,res;
    size_t stackSize;
    pthread_attr_t attr;

    res = pthread_getattr_np(pthread_self(), &attr);
    if (res)
    {
        printf("pthread_getattr_np failed\n");
    }

    res = pthread_attr_getstacksize(&attr, &stackSize);
    if (res)
    {
        printf("pthread_attr_getstacksize failed\n");
    }

    printf("Default stack size is %u byte; minimum is %u byte;\n", stackSize, PTHREAD_STACK_MIN);

    pthread_attr_destroy(&attr);
}

int main(int argc, char *argv[])
{
    pthread_t threadID;
    int res;

    res = pthread_create(&threadID, NULL, &ThreadFunc, NULL);
    if (res)
    {
        printf("pthread_create failed\n");
        return 0;
    }

    pthread_join(threadID, NULL);
    return 0;
}

运行结果:

Default stack size is 8392704 byte; minimum is 16384 byte;

3 调度策略

  如何管理多个线程去抢占CPU,这就是线程的调度。

  Linux下线程的调度策略有三种:

  1. SHED_OTHER(分时调试策略):这是一种非实时调试策略,系统会为每个线程分配时间片。没有优先级之分,每个线程轮流占用CPU。

  2. SHED_FIFO(先来先服务调度策略):支持优先级抢占。CPU会让一个先来的线程执行完再调度下一个线程,顺序就是按照创建线程的先后。线程一旦占用CPU就会一直运行,直到有更高优先级的任务到达或自己放弃CPU。如果有和正在运行的线程具有同样优先级的线程已就绪,就必须等等正在运行的线程主动放弃后才可以运行这个就绪的线程。

  3. SHED_RR(实时调度策略,时间片轮转):支持优先级抢占,因此也是一种实时调试策略。CPU会给每个线程分配一个特定的时间片,当线程的时间片用完时,系统将重新分配时间片,并将线程置于实时线程就绪队列的尾部,保证其它相同优先级的线程能被公平的调度。

  获取调度策略最小和最大优先级的函数是 sched_get_priority_min 和 sched_get_priority_max,声明如下:

int sched_get_priority_min (int policy);
int sched_get_priority_max (int policy);

  policy的取值可以为SHED_OTHER、SHED_FIFO、SHED_RR。


举例:获取线程3种调度策略下可设置的最小和最大优先级

#include <stdio.h>
#include <sched.h>

int main()
{
    printf("Valid priority range for SCHED_OTHER: %d - %d\n",
            sched_get_priority_min(SCHED_OTHER),
            sched_get_priority_max(SCHED_OTHER));

    printf("Valid priority range for SCHED_FIFO: %d - %d\n",
           sched_get_priority_min(SCHED_FIFO),
           sched_get_priority_max(SCHED_FIFO));

    printf("Valid priority range for SCHED_RR: %d - %d\n",
           sched_get_priority_min(SCHED_RR),
           sched_get_priority_max(SCHED_RR));

    return 0;
}

运行结果:

Valid priority range for SCHED_OTHER: 0 - 0
Valid priority range for SCHED_FIFO: 1 - 99
Valid priority range for SCHED_RR: 1 - 99


三 线程的结束

  线程结束有以下四种方式:

  1. 在线程函数中调用了pthread_exit函数。
  2. 线程所属的进程结束了,比如进程调用了exit。
  3. 线程函数执行结束后返回了。
  4. 线程被同一进程中的其他线程通知结束或取消。

  其中1和3是线程主动结束,2,4属于被动结束,如果被动结束时线程资源没有回收,就会引发资源泄漏。

  pthread_exit函数声明如下:

void pthread_exit (void *retval);

  retval是返回值,类型是void *,另外,在main线程中调用 pthread_exit 函数时,将结束 main 线程,但进程并不立即退出。线程间发送信号的函数是 pthread_kill,它需要接收信号的线程先用 sigaction 函数注册该信号的处理函数。声明如下:

void pthread_kill (pthread_t threadId, int signal);

  signal 通常大于0,如果等于0就是用来检测线程是否存在。函数如果执行成功,返回0,否则返回错误码。如果没注册信号处理函数,则整个进程退出。
  另外还有其它两个函数,pthread_cancel 函数:取消线程的执行(也是发一个请求,不一定成功。) pthread_testcancel 函数:检查是否有取消请求发送给当前线程,如果有,就算是在死循环中,也能将线程停止下来。声明如下:

void pthread_cancel (pthread_t threadId);
void pthread_testcancel (void);

举例:通知取消线程

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

void *ThreadFun(void *arg)
{
    int i = 1;
    printf("thread start ---------------\n");

    while (1)
    {
        i++;
        pthread_testcancel();   //  让系统测试取消请求
    }

    return NULL;
}

int main()
{
    void *ret = NULL;
    int res = 0;
    pthread_t threadId;

    res = pthread_create(&threadId, NULL, ThreadFun, NULL);
    if (res)
    {
        printf("pthread_create failed\n");
        return 0;
    }
    sleep(1);

    pthread_cancel(threadId);
    pthread_join(threadId, &ret);
    if (ret == PTHREAD_CANCELED)
    {
        printf("thread has stopped, and exit code is %d\n", ret);
    }
    else
    {
        printf("some error occured\n", ret);
    }

    return 0;
}

运行结果:

thread start ---------------
thread has stopped, and exit code is -1


四 线程退出时的清理机会

  如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,是线程退出时要解决的问题。

  POSIX线程库提供了函数 pthread_cleanup_push 和 pthread_cleanup_pop。前者会把一个函数压入清理函数调用栈,后者用来弹出栈顶的清理函数,并根据参数来决定是否执行清理函数。两个 函数必须成对出现在同一函数中,否则编译不过。声明如下:

void pthread_cleanup_push (void (*routine)(void *), void *arg);
void pthread_cleanup_pop (int execute);

  execute 取0时表示不执行清理函数,非0时执行清理函数。pthread_cleanup_push 压入的函数会在下面3种情况下执行:

  1. 线程主动结束时,比如return 或 pthread_exit。
  2. 调用pthread_cleanup_pop并且其参数不为0时。
  3. 线程被其它线程取消时,也就是有其它的线程对该线程调用pthread_cancel函数。


举例:pthread_cleanup_pop调用清理函数

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

void CleanFunc(void *arg)
{
    printf("clean func num: %d\n", *(int *)arg);
}

void *ThreadFun(void *arg)
{
    int n = 1;
    int m = 2;
    printf("sub thread begin\n");
    pthread_cleanup_push(CleanFunc, &m);
    pthread_cleanup_push(CleanFunc, &n);
    pthread_cleanup_pop(1); //  保证编译通过
    pthread_cleanup_pop(1); //  虽然两次都是取1,但是因为thread正常return,所以会调用清理函数两次。
    return NULL;
}

int main(void)
{
    pthread_t threadID;
    int res;

    res = pthread_create(&threadID, NULL, ThreadFun, NULL);
    if (res)
    {
        printf("pthread_create failed\n");
        return 0;
    }
    pthread_join(threadID, NULL);

    return 0;
}

运行结果:

sub thread begin
clean func num: 1
clean func num: 2
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值