线程与线程控制

概念(源自宋宝华老师的解析)

  Linux内核提供了轻量级进程的支持,未实现线程模型,但是Linux最大限度的优化了进程的调度开销,这弥补了无线程的缺陷。Linux用轻量进程对应一个线程,将线程调度等同于进程调度,交给核心完成。

  Linux是一个多进程单线程的操作系统,线程本质上在内核里仍然是进程。进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(共享地址空间)(如同全局变量)。Linux中所谓的线程只是在被创建的时候clone了父进程的资源,因此clone出来的进程表现为“线程”。

  目前Linux流行的线程机制为LinuxThreads,所采用的是”线程——进程“一对一模型。调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。按照POSIX 1003.1c标准编写的程序与Linuxthread库相连接即可支持linux平台上的多线程,头文件为pthread.h,编译时使用命令:

gcc -D -REENTRANT -lpthread xxx.c

  其中-REENTRANT 宏使得相关库函数(如 stdio.h、errno.h 中函数) 是可重入的、线程安全的(thread-safe),-lpthread 则意味着链接库目录下的 libpthread.a 或 libpthread.so 文件。使用 Linuxthread库需要 2.0 以上版本的 Linux 内核及相应版本的 C 库(libc 5.2.18、libc 5.4.12、libc 6)。

  接下来讲述线程控制方面。

pthread_create创建线程

pthread_create (pthread_t *thread, const pthread_attr_t *attr, void*
(*start_routine)(void*), void *arg);
//start_routine 为新线程的入口函数,arg 为传递给 start_routine 的参数

  为了唯一标识每个线程以便于在进程内区分,每个线程都有自己的ID。线程ID在 pthread_create 调用时返回给创建函数的调用者:一个线程也可以在创建后调用  pthread_self (void) 调用获取自己的线程ID。

  pthread_create的参数介绍:

    • thread:该参数是一个指针,当线程创建成功时,用来返回创建的线程ID。
    • attr:该参数用于指定线程的属性,NULL表示使用默认属性
    • start_routine:该参数为函数指针(形式为:类型名 (*指针变量名)() ,如int (*p) (int i, int j); ),指向创建进程后所要调用的函数。这个被线程调用的函数也称为线程函数。
    • arg:该参数指向传递给线程函数的参数,详见如下:”之后贴上代码“

  返回值:线程创建成功时,pthread_create 返回0, 失败则返回非0。

让线程中的代码块只执行一次

  如果你有多个线程,它们都多次调用同一个函数,但是你想让这一个函数只执行一次,那么你可以使用pthread_once函数。

  

  我们需要定义一个全局变量 pthread_once_t once = PTHREAD_ONCE_INIT 以及一个只需要执行一次的函数init_routine,然后在线程模块里去调用pthread_once函数。

  pthread_once函数的第一个参数是once_control,即全局变量once,用于标记调用函数的执行次数。第二个参数是被线程调用的函数init_routine,形参是一个函数指针。

线程退出(三种办法)

  1、执行完成后隐式退出

  2、线程本身调用pthread_exit 函数退出(不推荐使用):

pthread_exit (void * retval); 

  3、被其他线程用pthread_cance函数终止:

pthread_cance (pthread_t thread);  //终止由参数thread指定的线程

  值得一提的是线程控制中也有类似系统调用wait的函数:pthread_join 函数。该函数的作用是调用pthread_join 的线程将被挂起直到线程ID为参数thread的函数终止:

pthread_join (pthread_t thread, void** threadreturn);

  

  或许有人会有疑问,为什么不能直接exit呢?是和进程一样父进程直接退出会使得子进程变成僵尸进程吗?

  道理大致相同,主线程其实也是需要wait等待其他进程的。与进程中终止情况的不同点是如果主线程结束的比其他线程还快的话,其他线程就会直接被掐死而导致剩下的代码无法执行。我们用以下例程证明这个观点(先用sleep函数让主线程挂起一秒,看看效果):

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

int *thread(void *arg)
{
    pthread_t newthid;
    newthid = pthread_self();
    printf("this is new thread , thread ID is = %lu
", newthid);
    return NULL;
}

int main()
{
    pthread_t thid;
    
    printf("main thread , ID is %lu
",pthread_self());
    if(pthread_create(&thid, NULL, (void *)thread, NULL) != 0){
        printf("fail!
");
        exit(-1);
    }
    sleep(1);
    exit(0);
    return 0;
}

   此时执行程序,执行结果如下:

main thread , ID is 140269454681856
this is new thread , thread ID is = 140269446350592

  去掉sleep(1),相当于主线程不等待1秒再执行代码而是直接执行之后代码,此时执行结果如下:

main thread , ID is 139988096497408

  显然子线程还没print时,由于主线程的eixt,它就被迫终止了。

  

私有数据

  在多线程环境下,进程内的所有线程共享进程的数据空间,所以全局变量为所以线程共有。但是存在这么一个情况:存在某个变量,比如errno,它返回标准的出错代码。它不应是个局部变量,因为所有线程都可以访问它;它不应是个全局变量,否则线程中的出错信息可能是另一个线程的。所以有了线程的私有数据(Thread-Specific Data,简称TSD)这么一个概念。

  TSD采用一种叫一键多值的技术,即一个键对应多个数值。比如说健key,在各线程内部,都可以使用这个公共的键来指代线程数据,但是在不同的线程中,这个键代表的数据是不同的。给同一键设置不同值的操作set通过 pthread_setspecific 函数实现。

/* 创建一个公共键key */
pthread_key_t key;

  图解一键多值技术:

  一键多值

  TSD的create、set、get、delete操作是通过TSD池实现的。TSD池是一个结构体数组。

static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { {0, NULL} }:

  create:创建一个TSD就相当于将结构体数组中的某一项设置为in_use,并将其索引返回给 *key ,然后设置destructor函数为destr_function。

  set:该函数将一pointer的值(不是内容的值)与key相关联。注意执行set之前必须先释放原有的线程数据以回收空间。

  get:得到与key相关联的数据。

  delete:用于删除一个键,删除后,该键的内存将被释放。需要注意,虽然该键的内存被释放了,但是与该键相关的线程数据所占的内存并不被释放,因此线程数据的释放必须要在释放键之前完成。

  

  之前我们提到了errno在多线程编程中的实现,这里提供关于errno的例程:

/*   my_errno.c  
     编译链接:gcc my_errno.c -o my_errno -lpthread  
*/   
    
// 线程私有数据,一键多值,tsd池   
#include <stdio.h>   
#include <string.h>   
#include <pthread.h>   
    
int my_errno = 0;   
pthread_key_t key;   
    
void print_errno(char *str)   
{   
    printf("%s my_errno:%d
",str, my_errno);   
}   
void *thread2(void *arg)   
{   
    printf("thread2 %ld is running
",pthread_self());   
    pthread_setspecific(key, (void *)my_errno);   
    printf("thread2 %ld returns %d
",pthread_self(),   
            pthread_getspecific(key));   
    my_errno = 2;       
    print_errno("thread2");   
}   
    
void *thread1(void *arg)   
{   
    pthread_t thid2;   
    
    printf("thread1 %ld is running
",pthread_self());   
    pthread_setspecific(key, (void *)my_errno);   
    pthread_create(&thid2, NULL, thread2, NULL);   
    sleep(2);   
    printf("thread1 %ld returns %d
",pthread_self(),   
         pthread_getspecific(key));   
    my_errno = 1;   
    print_errno("thread1");   
}   
    
void destr(void *arg)   
{   
    printf("destroy memory
");   
}   
    
int main(void)   
{   
    pthread_t thid1;   
    
    printf("main thread begins running. my_errno=%d
",my_errno);   
    pthread_key_create(&key, destr);   
    pthread_create(&thid1, NULL, thread1, NULL);   
    sleep(4);   
    pthread_key_delete(key);   
       
    printf("main thread exit
");   
    
    return 0;   
}   
  ./my_errno 后你会发现虽然my_errno是全局的,但在thread1与thread2保留的是私有数据。

  

  

————全心全意投入,拒绝画地为牢
原文地址:https://www.cnblogs.com/Bw98blogs/p/7631498.html