Linux之线程介绍

1、为什么要有线程?

一个进程可以有多个线程,这个进程本身也叫做线程只不过是主线程。通常主线程分配任务给子线程做。程序设计时候就可以某一时刻不止做一件事情,每一个线程处理各自独立的任务。

  • 多个线程自动可以访问相同的存储地址空间和文件描述符。
  • 每个线程都包含有表示执行环境所必须的信息,其中包括线程ID、一组寄存器、栈、调度优先级和策略、信号屏蔽字、errno变量及线程私有数据。一个进程所有信息对该进程所有线程共享,包括可执行代码、程序的全局内存和堆内存、栈以及文件描述符。

2、线程ID

每个线程都有一个线程ID,线程ID只有在它所属的进程上下文才有意义。

pthread_t//用于表示线程ID数据结构

pthread_equal (pthread_t __thread1, pthread_t __thread2);//用于比较线程ID
pthread_t pthread_self (void);//用户返回线程ID
//当线程需要识别以线程ID作为标识的数据结构时,pthread_self 函数可以与pthread_equal函数一起使用。主线程可能把工作任务放在一个队列中,用线程ID来控制每个工作线程和处理哪些作业,线程池的实现需要这两个函数。

3、线程创建

pthread_create()

/*
__newthread:函数成功返回将ID存储在此变量中。
__attr:定制线程属性。
__start_routine:函数指针。
__arg:传递给函数的参数。
*/
int pthread_create (pthread_t *__restrict __newthread,
               const pthread_attr_t *__restrict __attr,
               void *(*__start_routine) (void *),
               void *__restrict __arg);
pthread_t ntid;
void printids(const char *s)
{
    pid_t pid;
    pthread_t tid;
    pid = getpid();//得到进程pid
    tid = pthread_self();//得到当前线程pid,不能直接从内存读,可能在pthread_create没有返回就到了这一步,那么就不是正确的内容。
    printf("%s pid=%lu , tid=%lu(0x%lx)\n" , s ,pid,tid,tid);
}
void *thr_fun(void *arg)//指针函数,参数可以通过一个结构体传进去.
{
    printids("new thread:");
    return ( (void *)0 );//强制类型转换成返回指定类型.
}
int main(void)
{
    int err;
    err = pthread_create(&ntid , NULL , thr_fun  ,NULL);
    /*
        参数一将返回进程ID存在tid里面.
        参数二定制进程属性,后面讨论.
        参数二进程从这个函数地址开始执行.
        参数四,可以给参数三函数地址运行传参数.
    */
    if(err != 0 )
        err_exit(err,"can't creat thread");
    printids("main thread:");//进程号称主进程
    sleep(1);//因为线程创建不确定哪个先运行,防止主线程首先运行,退出了,那么子线程自然也退出了.
    exit(0);
}

main thread: pid=14382 , tid=139681256101696(0x7f0a13a6d740)
new thread: pid=14382 , tid=139681238755072(0x7f0a129e2700)
解释了进程如何创建的过程。pid没有改变,每个进程有一个tid。
tid = pthread_self();//得到当前线程pid,不能直接从内存读,新线程可能在pthread_create没有返回就开始运行,那么通过ntid就得到没有初始化的内容。

4、线程终止

如果进程中的任意线程调用了exit、_Exit或者exit,那么整个进程就会终止。并且如果信号的默认动作是终止进程,那么收到该信号的进程也会终止。
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。

  1. 线程可以简单地从启动例程中返回,返回值是线程的退出码。
  2. 线程可以被同一进程中的其他线程取消。
  3. 线程调用pthead_exit。
void pthread_exit (void *__retval);//指定返回值。

int pthread_join (pthread_t __th, void **__thread_return);
//等待指定的线程(th)终止,并获取进程终止状态(__thread_return)。状态是一个指针,所以得传递指针的指针。
 thr_fun1(void *arg)//指针函数,参数可以通过一个结构体传进去.
 {
     printf("thread 1 returning\n");
     return ( (void *)1 );//强制类型转换成返回指定类型.
 }
 void *thr_fun2(void *arg)//指针函数,参数可以通过一个结构体传进去.
 {
     printf("thread 2 exiting\n");
     pthread_exit( (void *)2);
 }
 int main(void)
 {
     int err;
     pthread_t tid1 , tid2;
     void *tret;
     err = pthread_create(&tid1 , NULL , thr_fun1 , NULL);
     if(err != 0)
         err_exit(err , "cant't create thread 1");

     err = pthread_create(&tid2 , NULL , thr_fun2 , NULL);
     if(err != 0)
         err_exit(err , "cant't create thread 2");

     err = pthread_join(tid1 , &tret);
     if(err != 0)
         err_exit(err , "cant't join thread 1");
     printf("thread 1 exit code %ld\n" , (long)tret);//void *指针转换成整形

     err = pthread_join(tid2 , &tret);//调用线程将一直阻塞,直到线程退出,
     if(err != 0)
         err_exit(err , "cant't join thread 1");
     printf("thread 2 exit code %ld\n" , (long)tret);//void *指针转换成整形

     exit(0);
 }

thread 2 exiting
thread 1 returning
thread 1 exit code 1
thread 2 exit code 2
按 来关闭窗口…
可以通过退出函数返回一些重要的参数,也可以通过创建函数传入更加重要的参数。但是假如返回是局部变量,那么会出现问题。

 struct foo{
     int a , b , c ,d;
 };
void printfoo(const char *str,struct foo *fp)
{
    printf("%s" , str);
    printf("struct foo at ox%lx\n" , (unsigned long)fp);
    printf("foo.a = %d\n" , fp->a);
    printf("foo.b = %d\n" , fp->b);
    printf("foo.c = %d\n" , fp->c);
    printf("foo.d = %d\n" , fp->d);
}
 void *thr_fun1(void *arg)//指针函数,参数可以通过一个结构体传进去.
 {
     struct foo foo = {1,2,3,4};//自动变量,线程退出,自动销毁
     printf("thread 1 returning\n");
     printfoo("thread 1:" , &foo);
     pthread_exit ( (void *)&foo );//强制类型转换成返回指定类型.
     //返回自动变量给主线程,会出现问题,因为会自动注销,所以不正确.
 }
 void *thr_fun2(void *arg)//指针函数,参数可以通过一个结构体传进去.
 {
     printf("thread 2 exiting\n");
     printf("thread 2 :id is %lu\n" , (unsigned long)pthread_self());
     pthread_exit( (void *)2);
 }
 int main(void)
 {
     int err;
     pthread_t tid1 , tid2;
     struct foo *fp;

     err = pthread_create(&tid1 , NULL , thr_fun1 , NULL);
     if(err != 0)
         err_exit(err , "cant't create thread 1");
     err = pthread_join(tid1 , (void **)&fp);//将其转换为指针的指针,注意这里必须是指针的指针.
     if(err != 0)
         err_exit(err , "cant't join thread 1");
     printfoo("parent:\n" , fp);//打印从子线程返回的自动变量,肯定会有问题,因为局部变量内存以及释放了
    sleep(1);//等待子线程优先执行完毕
    err = pthread_create(&tid2 , NULL , thr_fun2 , NULL);
    if(err != 0)
        err_exit(err , "cant't create thread 2");
    sleep(1);//等待子线程优先执行.


     exit(0);
 }

thread 1 returning
thread 1:struct foo at 0x7fa7e653bef0
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
parent:
struct foo at 0x7fa7e653bef0
foo.a = 0
foo.b = 0
foo.c = -423819950
foo.d = 32679
thread 2 exiting
thread 2 :id is 140359100516096

当主线程访问这个结构时,结构的内容(在线程tid1的栈上分配的)己经改变了。注意第二个线程 (tid2) 的校是如何覆盖第一个线程的栈的。为了解决这个问题,可以使用全局结构,或者用malloc函数。

5、线程清理

简单就是线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序。一个线程可以建立多个清理处理程序,处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。

pthead_clean_pop()//将清理函数压栈
pthead_clean_push()//将清理函数弹出栈,执行操作。
#include<stdio.h>
#include<pthread.h>
void
cleanup(void *arg)
{
    printf("cleanup: %s\n", (char *)arg);
}

void *
thr_fn1(void *arg)
{
    printf("thread 1 start\n");
    pthread_cleanup_push( cleanup, (void *)"thread 1 first handler");
    pthread_cleanup_push( cleanup, (void *)"thread 1 second handler");
    printf("thread 1 push complete\n");
    if (arg)
        pthread_exit((void *)1);//线程退出,push进入的函数自动执行
    pthread_cleanup_pop(0);//否则删除栈上的函数
    pthread_cleanup_pop(0);
    return((void *)1);
}
void *
thr_fn2(void *arg)
{
    printf("thread 2 start\n");
    pthread_cleanup_push(cleanup, (void *)"thread 2 first handler");
    pthread_cleanup_push(cleanup, (void *)"thread 2 second handler");
    printf("thread 2 push complete\n");
    if (arg)//传入参数非0
        pthread_exit((void *)2);//线程退出,push进入的函数自动执行
    pthread_cleanup_pop(0);//否则删除栈上的函数
    pthread_cleanup_pop(0);
    pthread_exit((void *)2);
}

int
main(void)
{
    int         err;
    pthread_t   tid1, tid2;
    void        *tret;

    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0)
        err_exit(err, "can't create thread 1");
    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    if (err != 0)
        err_exit(err, "can't create thread 2");
    err = pthread_join(tid1, &tret);//等待1结束,并取得退出码
    if (err != 0)
        err_exit(err, "can't join with thread 1");
    printf("thread 1 exit code %ld\n", (long)tret);//打印退出码
    err = pthread_join(tid2, &tret);//等待2结束,并取得退出码
    if (err != 0)
        err_exit(err, "can't join with thread 2");
    printf("thread 2 exit code %ld\n", (long)tret);//打印退出码
    exit(0);
}

这里写图片描述
上述结果很清楚。

6、线程属性

暂时未用到,先忽略。

7、线程特定数据

线程特定数据称为线程私有数据,是存储和查询某个特定线程相关数据的一种机制。这样每个线程可以访问它自己单独的数据副本,而不需要担心与其他线程的同步访问问题。

在单线程程序中,我们经常要用到”全局变量”以实现多个函数间共享数据。在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有。 但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问。POSIX线程库通过维护一定的数据结构来解决这个问题,这个些数据称为线程特定数据(Thread-specific Data,或 TSD)。

int pthread_key_create (pthread_key_t *__key,
                 void (*__destr_function) (void *));
/*
__key:创建的键存储在key指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。创建新键时,每个线程的数据地址设为空值。也就是每个线程使用相同的键索引不同的线程特定数据空间,一般通过malloc分配。线程可以给特定数据分配多个键,也就是一个线程含有多个特定数据区域。
__destr_function:设置对应的析构函数,当线程返回或者pthread_exit时,如果其特定数据地址空间已经被置为非空值,那么析构函数就会被调用清理特定数据区域,由于分配通常用malloc如果不清理会造成内存泄漏。,线程取消时,只有在最后的清理处理程序返回之后,析构函数才会被调用。pthread_key_create的时候,必须保证多线程竞争的问题,防止创建失败。
*/
int pthread_key_delete (pthread_key_t __key);
//取消键与线程特定数据值关联关系。注意不会激活析构函数。

int pthread_setspecific (pthread_key_t __key, const void *__pointer)
/*
键一旦创建以后,就可以通过调用pthread_setspecific函数把键和线程特定数据关联起来。注意这个地址空间通常需要程序员手动malloc分配。
*/

void *pthread_getspecific (pthread_key_t __key);
/*
获得线程特定数据的地址,假如没有设置则返回NULL。可以用这个返回值来确定是否需要调用pthread_setspecific.
*/

下面通过getenv获取环境变量对象的值是线程安全的。通过pthread_once保证pthread_key_create仅仅初始化一次,然后将环境变量对象的数值全部存储在线程特定数据空间上。

#include <limits.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

#define MAXSTRINGSZ 4096

static pthread_key_t key;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;
pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;

extern char **environ;

static void
thread_init(void)
{
    pthread_key_create(&key, free);//析构函数利用free。因为我们打算用malloc分配
}

char *
getenv(const char *name)
{
    int     i, len;
    char    *envbuf;

    pthread_once(&init_done, thread_init);
/*此函数表示如果当第一个线程调用它时会执行相应的函数,然后从pthread_once返回,而接下去的其他线程调用它时将不再执行thread_init函数,此举是为了只调用一次pthread_key_create,即产生一个pthread_key_t值,并且可以防止多线程竞争保证初始化一次。
*/
    pthread_mutex_lock(&env_mutex);//加锁,
    envbuf = (char *)pthread_getspecific(key);//获取特定数据,假如地址为空,那么重新malloc
    if (envbuf == NULL) {
        envbuf = malloc(MAXSTRINGSZ);
        if (envbuf == NULL) {
            pthread_mutex_unlock(&env_mutex);
            return(NULL);
        }
        pthread_setspecific(key, envbuf);//设置malloc新的空间为当前线程私有数据。
    }
    len = strlen(name);
    for (i = 0; environ[i] != NULL; i++) {
        if ((strncmp(name, environ[i], len) == 0) &&
          (environ[i][len] == '=')) {
            strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);//将环境变量对应值拷贝到线程私有空间
            pthread_mutex_unlock(&env_mutex);
            return(envbuf);
        }
    }
    pthread_mutex_unlock(&env_mutex);
    return(NULL);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有时需要偏执狂

请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值