精华内容
下载资源
问答
  • 在编写多线程代码时,经常面临线程安全退出的问题。 一般情况下,选择检查标志位的方式: 在线程的while循环中,执行完例程后,都对标志位进行检查,如果标志位指示继续执行则再次执行例程,如果标志位设置为...
  • linux线程创建

    千次阅读 2015-09-12 10:11:43
    linux线程创建

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

    /* example.c*/
    #include <stdio.h>
    #include <pthread.h>
    void thread(void)
    {
    int i;
    for(i=0;i<3;i++)
    printf("This is a pthread.n");
    }

    int main(void)
    {
    pthread_t id;
    int i,ret;
    ret=pthread_create(&id,NULL,(void *) thread,NULL);
    if(ret!=0){
    printf ("Create pthread error!n");
    exit (1);
    }
    for(i=0;i<3;i++)
    printf("This is the main process.n");
    pthread_join(id,NULL);
    return (0);
    }

    我们编译此程序:
    gcc example1.c -lpthread -o example1
    运行example1,我们得到如下结果:
    This is the main process.
    This is a pthread.
    This is the main process.
    This is the main process.
    This is a pthread.
    This is a pthread.
    再次运行,我们可能得到如下结果:
    This is a pthread.
    This is the main process.
    This is a pthread.
    This is the main process.
    This is a pthread.
    This is the main process.

      前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,  pthread_create和pthread_join,并声明了一个pthread_t型的变量。
      pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
      typedef unsigned long int pthread_t;
      它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:
      extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
      void *(*__start_routine) (void *), void *__arg));
      第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。
      函数pthread_join用来等待一个线程的结束。函数原型为:
      extern int pthread_join __P ((pthread_t __th, void **__thread_return));
      第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:
      extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
      唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
      在这一节里,我们编写了一个最简单的线程,并掌握了最常用的三个函数pthread_create,pthread_join和pthread_exit。下面,我们来了解线程的一些常用属性以及如何设置这些属性。

    3 修改线程的属性
      在上一节的例子里,我们用pthread_create函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。的确,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
      属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,喜欢追根问底的人可以自己去查看。属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
      关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
      设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。
    #include <pthread.h>
    pthread_attr_t attr;
    pthread_t tid;

    /*初始化属性值,均设为默认值*/
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

    pthread_create(&tid, &attr, (void *) my_function, NULL);

      线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
      另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。
    #include <pthread.h>
    #include <sched.h>
    pthread_attr_t attr;
    pthread_t tid;
    sched_param param;
    int newprio=20;

    pthread_attr_init(&attr);
    pthread_attr_getschedparam(&attr, &param);
    param.sched_priority=newprio;
    pthread_attr_setschedparam(&attr, &param);
    pthread_create(&tid, &attr, (void *)myfunction, myarg);
      
    4 线程的数据处理
      和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处沿袭的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面,我们就逐步介绍处理线程数据时的有关知识。

    4.1 线程数据
      在单线程的程序里,有两种基本的数据:全局变量和局部变量。但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。
      和线程数据相关的函数主要有4个:创建一个键;为一个键指定线程数据;从一个键读取线程数据;删除键。
      创建键的函数原型为:
      extern int pthread_key_create __P ((pthread_key_t *__key,
      void (*__destr_function) (void *)));
      第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。

      在下面的例子中,我们创建一个键,并将它和某个数据相关联。我们要定义一个函数createWindow,这个函数定义一个图形窗口(数据类型为Fl_Window *,这是图形界面开发工具FLTK中的数据类型)。由于各个线程都会调用这个函数,所以我们使用线程数据。
    /* 声明一个键*/
    pthread_key_t myWinKey;
    /* 函数 createWindow */
    void createWindow ( void ) {
    Fl_Window * win;
    static pthread_once_t once= PTHREAD_ONCE_INIT;
    /* 调用函数createMyKey,创建键*/
    pthread_once ( & once, createMyKey) ;
    /*win指向一个新建立的窗口*/
    win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
    /* 对此窗口作一些可能的设置工作,如大小、位置、名称等*/
    setWindow(win);
    /* 将窗口指针值绑定在键myWinKey上*/
    pthread_setpecific ( myWinKey, win);
    }

    /* 函数 createMyKey,创建一个键,并指定了destructor */
    void createMyKey ( void ) {
    pthread_keycreate(&myWinKey, freeWinKey);
    }

    /* 函数 freeWinKey,释放空间*/
    void freeWinKey ( Fl_Window * win){
    delete win;
    }

      这样,在不同的线程中调用函数createMyWin,都可以得到在线程内部均可见的窗口变量,这个变量通过函数pthread_getspecific得到。在上面的例子中,我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下:
      extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
      extern void *pthread_getspecific __P ((pthread_key_t __key));
      这两个函数的参数意义和使用方法是显而易见的。要注意的是,用pthread_setspecific为一个键指定新的线程数据时,必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键,这个键占用的内存将被释放,但同样要注意的是,它只释放键占用的内存,并不释放该键关联的线程数据所占用的内存资源,而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。

    4.2 互斥锁
      互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。
      我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

    void reader_function ( void );
    void writer_function ( void );

    char buffer;
    int buffer_has_item=0;
    pthread_mutex_t mutex;
    struct timespec delay;
    void main ( void ){
    pthread_t reader;
    /* 定义延迟时间*/
    delay.tv_sec = 2;
    delay.tv_nec = 0;
    /* 用默认属性初始化一个互斥锁对象*/
    pthread_mutex_init (&mutex,NULL);
    pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
    writer_function( );
    }

    void writer_function (void){
    while(1){
    /* 锁定互斥锁*/
    pthread_mutex_lock (&mutex);
    if (buffer_has_item==0){
    buffer=make_new_item( );
    buffer_has_item=1;
    }
    /* 打开互斥锁*/
    pthread_mutex_unlock(&mutex);
    pthread_delay_np(&delay);
    }
    }

    void reader_function(void){
    while(1){
    pthread_mutex_lock(&mutex);
    if(buffer_has_item==1){
    consume_item(buffer);
    buffer_has_item=0;
    }
    pthread_mutex_unlock(&mutex);
    pthread_delay_np(&delay);
    }
    }
      这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。
      pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。
      上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。

    4.3 条件变量  前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。
      条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
      extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
      其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。 
      函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:
      extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
      pthread_mutex_t *__mutex));
      线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。
      另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:
      extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
      pthread_mutex_t *__mutex, __const struct timespec *__abstime));
      它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。
      函数pthread_cond_signal()的原型为:
      extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
      它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出,从而造成无限制的等待。下面是使用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简单的例子。

    pthread_mutex_t count_lock;
    pthread_cond_t count_nonzero;
    unsigned count;
    decrement_count () {
    pthread_mutex_lock (&count_lock);
    while(count==0) 
    pthread_cond_wait( &count_nonzero, &count_lock);
    count=count -1;
    pthread_mutex_unlock (&count_lock);
    }

    increment_count(){
    pthread_mutex_lock(&count_lock);
    if(count==0)
    pthread_cond_signal(&count_nonzero);
    count=count+1;
    pthread_mutex_unlock(&count_lock);
    }
      count值为0
    时,decrement函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock。此时,当调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。读者可以试着让两个线程分别运行这两个函数,看看会出现什么样的结果。
      函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用这个函数。


    展开全文
  • 线程,我自己看到的两篇讲解比较生动形象的易于理解的博文,如下。 线程与进程之间的关系: http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html 对线程的解(一篇比较生动形象的讲解): ...

    线程,我自己看到的两篇讲解比较生动形象的易于理解的博文,如下。


    linux操作系统是支持多线程的,它在一个进程内生成了许多个线程。一个进程可以拥有一至多个线程。多线程相对于多进程还是有不少优点的:

    • 在多进程的情况下,每个进程都有自己独立的地址空间,而在多线程的情况下,同一进程内的的线程共享进程的地址空间。因此,创建一个新的进程时就要耗费时间来为其分配系统资源,而创建一个新的线程花费的时间就很少。
    • 在系统调度方面,由于进程地址空间独立而线程共享地址空间,线程间的切换速度要远远快过进程间的切换速度。
    • 在通信机制方面,进程间的数据空间相互独立,彼此通信要以专门的通信方式进行,通信时必须经过操作系统。而同一进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,而不必经过操作系统。所以,线程间的通信更加方便和省时。
    • 可以提高应用程序的响应速度。
    • 可以提高多处理器的效率。
    • 可以改善程序的结构。对于要处理多个命令的应用程序,可以对每个命令的处理设计成一个线程,从而避免设计成大程序时造成的程序结构复杂。

      虽然线程在进程内共享地址空间,打开的文件描述符等资源。但是线程也有其私有的数据信息。

    • 线程号:每个线程都有一个唯一的线程号一一对应。
    • 寄存器
    • 堆栈
    • 信号掩码
    • 优先级
    • 线程私有的存储空间
      注意:编写linux下的多线程应用程序,需要头文件pthread.h,编译时需要加上-lpthread

      1、创建线程:

      线程的创建通过函数pthread_creat来完成。
      (1) 该函数声明及头文件如下:

    #include<pthread.h>
    int pthread_create(pthread_t *thread,pthread_attr_t *attr,
    (void*)(*start_routine)(void*),void *arg);

    编译链接参数
    -lpthread

    (2)返回值
    若线程创建成功,则返回0。若线程创建失败,则返回出错编号。
    返回成功时,由thread指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。新创建的线程从start_routine函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
    - thread:该参数为指向线程标识符的指针,当线程创建成功时,用来返回创建的线程id
    - attr:该参数用来指定线程属性,NULL表示使用默认属性。
    - start_routine:该参数是一个函数指针,指向线程创建后要调用的函数。
    - arg:该参数指向传递该线程函数的参数。

    (3)第二个参数是一个指向pthread_attr_t结构体(线程属性结构)的指针,该结构体如下:

      typedef struct
      {
                 int                     detachstate;  //   线程的分离状态
                 int                     schedpolicy;   //线程调度策略
                 struct sched_param      schedparam;   //线程的调度参数
                 int                     inheritsched;  //  线程的继承性
                 int                     scope;          //线程的作用域
                 size_t                  guardsize; //线程栈末尾的警戒缓冲区大小
                 int                     stackaddr_set;//堆栈地址集
                 void *                  stackaddr;     // 线程栈的位置
                 size_t                  stacksize;      // 线程栈的大小
     }pthread_attr_t;
    

    (4)创建线程这里还会用到的几个系统函数:

    pthread_t pthread_self(void)      //获取本线程的id
    int pthread_equal(pthread_t thread1,pthread_t thread2)   //判断两个线程id是否指向同一个线程
    int pthread_once(pthread_once_t * once_control,void(*init_routine)(void))  //用来保证init_routine线程函数在进程中仅执行一次

    下面为一个创建线程的例子

    #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 a new thread,thread ID = %lu\n",newthid);
            return NULL;
    }
    int main(void)
    {
            pthread_t thid;
            printf("main thread,ID is %lu\n",pthread_self());  //pthread_self()获取本线程id。
            if(pthread_create(&thid,NULL,(void *)thread,NULL)!=0) //创建线程
            {
                    printf("thread creation failed\n");
                    exit(1);
            }
            sleep(1);
            exit(0);
    }

    运行结果:
    先打印出主线程的ID,然后打印出新创建的线程ID。

    main thread,ID is 140169071576896
    this is a new thread,thread ID = 140169062905600

    有时我们想在多线程的情况下,让某些函数只执行一次,该怎么办?
    这时就要使用函数pthread_once
    示例:

    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<pthread.h>
    pthread_once_t once = PTHREAD_ONCE_INIT;
    
    void run(void)
    {
            printf("Founction run is running in thread %lu\n",pthread_self());
    }
    
    void * thread1(void *arg)
    {
            pthread_t thid=pthread_self();
            printf("当前的线程ID 是 %lu\n",thid);
            pthread_once(&once,run);
            printf("thread1 结束\n");
    }
    
    void * thread2(void * arg)
    {
            pthread_t thid=pthread_self();
            printf("当前的线程 ID 是 %lu\n",thid);
            pthread_once(&once,run);
            printf("thread2 结束\n");
    }
    
    int main()
    {
            pthread_t thid1,thid2;
            //创建线程 
            pthread_create(&thid1,NULL,thread1,NULL);
            pthread_create(&thid2,NULL,thread2,NULL);
            sleep(3);
            printf("主线程退出!\n");
            exit(0);
    }

    运行结果:

    当前的线程 ID 是 140685518632704
    Founction run is running in thread 140685518632704
    thread1 结束
    当前的线程 ID 是 140685510240000
    thread2 结束
    主线程退出!

    我们可以看到两个线程都调用了run函数,但函数run只在线程thread1中运行了一次。

    在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数进执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定的


    2、线程终止

    linux下有两种方式可以使线程终止,第一种通过return从线程函数返回
    第二种是调用函数pthread_exit()使线程退出。
    pthread_exit头文件及其原型:

    #include<pthread.h>
    void pthread_exit(void * retval)

    这里要注意两种特殊情况:

    • 在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程将终止,此时进程中所有线程也将终止,因此在主线程中不能过早地从main函数返回。
    • 如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才会结束。

    (1)线程终止会带带来资源释放问题:特别是临界资源,临界资源在一段时间内只能被一个资源所持有,当线程要使用临界资源时需提出申请,如果该资源未被使用,则申请成功,否则等待。
    临界资源被一个线程所独占,当一个线程终止时,如果不释放其占有的临界资源,则该资源会被认为还被已退出的线程所使用,因而永远不会得到释放。如果一个线程在等待使用这个临界资源,它就有可能无限的等下去,这就形成了死锁。再难也就因此而来。

    为此,系统提供了一些函数,用于自动释放资源,从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(如调用pthread_exit)都将执行pthread_cleanu_push()所指定的清理函数

    pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

    #define pthread_cleanup_push(routine,arg)                                     
      { struct _pthread_cleanup_buffer _buffer;                                   
        _pthread_cleanup_push (&_buffer, (routine), (arg));
    #define pthread_cleanup_pop(execute)                                          
        _pthread_cleanup_pop (&_buffer, (execute)); }

    pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译.


    (2)线程终止还要注意线程间的同步问题,因为进程间各个现成的运行是相互独立的,线程的终止是不会相互通知的,也不会互相影响的,终止的线程所占用的资源并不会随着线程的终止而归还系统,而是仍归线程所在的进程持有,同样,对于这种情况,我们可以利用下面两个函数来解决问题。

    #include<pthread.h>
    void pthread_exit(void * retval);
    int pathread_join(pthread_t th,void * thread_return);
    int pthread_detach(pthread_t th);
    • pthread_join:以阻塞方式等待thred指定的线程结束。只有线程是可接入的时候,调用pthread_join才会成功,其中thrad:线程标识符,即线程ID,retval:用户定义的指针,用来存储被等待线程的返回值。
    • pthread_detach,这个函数可以改变线程的可接入属性,将其改变成分离模式。当线程处于分离模式,表明我们对于线程的返回值并不关心,工作线程执行完毕后会自行退出,内核会释放该线程占用的资源。
      一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join()的线程返回错误代码ESRCH.
      以下为主线程通过pthread_join等待铺主线程结束示例
    #include<stdio.h>
    #include<unistd.h>
    #include<pthread.h>
    void assisthread(void * arg)
    {
            printf("I am helping to do some jobs\n");
            sleep(3);
            pthread_exit(0);
    }
    int main(void)
    {
            pthread_t assistthid;
            int status;
            pthread_create(&assistthid,NULL,(void *)assisthread,NULL);//创建线程
            pthread_join(assistthid,(void *) &status);  //阻塞主线程。等待线程aassistthid结束
            printf("assistthread's exit is caused %d\n",status);
    
            return 0;
    }

    运行结果:

    I am helping to do some jobs
    assistthread's exit is caused 0

    辅助线程线程的退出码为0,pthread_join得出的status也为0,一致。

    展开全文
  • 简单的多线程编程,为了判断数独结果是否有效,把整个...在这里,我们创建了11个线程,1~9个线程分别判断九个宫内的数字是否不重复,第10个线程判断每一行的数字是否不重复,第11个线程判断每一列的数字是否不重复。
  • Linux设备驱动程序中创建线程的方法

    千次阅读 2019-10-20 11:24:11
    参考博客文章来源:... 第一种方法:kernel_thread #include <linux/sched.h> extern pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags); (1)...

    参考博客文章来源:https://blog.csdn.net/ezimu/article/details/60467017

    第一种方法:kernel_thread

    #include <linux/sched.h>
    
    extern pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);

    (1)参数说明:

    fn线程函数地址
    arg线程函数的形参,没有,可以是NULL
    flags标志,一般用CLONE_KERNEL(定义在linux/sched.h中,注意有的版本中,CLONE_FS | CLONE_FILES | CLONE_SIGHAND),其他标志及含义见uapi/linux/sched.h中
    返回值pid_t返回线程ID值

    (2)使用例子:

    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
    #include <linux/kmod.h>
    #include <linux/sched.h>
    #include <linux/delay.h>
    
    MODULE_AUTHOR("pyl");
    MODULE_LICENSE("GPL");
    
    #define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
    
    int my_kernel_thread(void *arg)
    {
            int n = 0;
    
            while(1)
            {
                    printk("%s: %d\n",__func__,n++);
                    ssleep(3);
            }
    
            return 0;
    }
    static int __init practice_call(void)
    {
            printk("%s:\n",__func__);
    
    //创建一个线程,并开始执行
            kernel_thread(my_kernel_thread,NULL,CLONE_KERNEL);    
    
            return 0;
    }
    
    static void __exit practice_call_exit(void)
    {
            printk("%s:\n",__func__);
    }
    
    module_init(practice_call);
    module_exit(practice_call_exit);

    第二种方法:kthread_create

    #include <linux/kthread.h>
    
    struct task_struct *kthread_create_on_node(
                       int (*threadfn)(void *data), 
                       void *data,int node,
                       const char namefmt[], ...);
    
    /**
     * kthread_create - create a kthread on the current node
     * @threadfn: the function to run in the thread
     * @data: data pointer for @threadfn()
     * @namefmt: printf-style format string for the thread name
     * @...: arguments for @namefmt.
     *
     * This macro will create a kthread on the current node, leaving it in
     * the stopped state.  This is just a helper for kthread_create_on_node();
     * see the documentation there for more details.
     */
    #define kthread_create(threadfn, data, namefmt, arg...) \
            kthread_create_on_node(threadfn,
                                   data,
                                   namefmt, ##arg)

    (1)参数说明:

    threadfn

    线程函数地址
    data线程函数形参,如果没有可以定义为NULL
    namefmt,arg…线程函数名字,可以格式化输出名字
    返回值(strcut task_struct *)线程指针

    注意:kthread_create()创建后,线程没有立即运行,需要将返回的值,即线程指针(struct task_struct *),作为参数传入到wake_up_process()唤起线程运行。

    (2)使用例子:

    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
    #include <linux/kmod.h>
    #include <linux/sched.h>
    #include <linux/delay.h>
    #include <linux/kthread.h>
    
    MODULE_AUTHOR("pyl");
    MODULE_LICENSE("GPL");
    
    #define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
    
    //线程指针
    strcut task_struct *practice_task_p = NULL;
    
    int my_kernel_thread(void *arg)
    {
            int n = 0;
    
            while(1)
            {
                    printk("%s: %d\n",__func__,n++);
                    ssleep(3);
    
    //kthread_should_stop判断线程是否应该结束,返回true表示结束,false表示不结束
            if(kthread_should_stop())
                    {
                            break;
                    }
    
            }
    
            return 0;
    }
    static int __init practice_call(void)
    {
            printk("%s:\n",__func__);
            practice_task_p = kthread_create(my_kernel_thread,NULL,"practice task");    
            if(!IS_ERR(practice_task_p))
                    wake_up_process(practice_task_p);//唤醒practice_task_p指向的线程
            return 0;
    }
    
    static void __exit practice_call_exit(void)
    {
            printk("%s:\n",__func__);
            kthread_stop(practice_task_p);//停止线程,kthread_should_stop会做出响应
    }
    
    module_init(practice_call);
    module_exit(practice_call_exit);

    注意:在编写线程循环体时,一般都要加入kthread_should_stop(),如果不加,调用kthread_stop()是没有效果的,也会导致模块退出后,线程仍然还在运行。

    第三种方法:kthread_run

    #include <linux/kthread.h>
    /**
     * kthread_run - create and wake a thread.
     * @threadfn: the function to run until signal_pending(current).
     * @data: data ptr for @threadfn.
     * @namefmt: printf-style name for the thread.
     *
     * Description: Convenient wrapper for kthread_create() followed by
     * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
     */
    #define kthread_run(threadfn, data, namefmt, ...)                          \
    ({                                                                         \
            struct task_struct *__k                                            \
                    = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
            if (!IS_ERR(__k))                                                  \
                    wake_up_process(__k);                                      \
            __k;                                                               \
    })
    

    (1)参数说明:

    threadfn

    线程函数地址
    data线程函数形参,没有可以制定为NULL
    namefmt, …线程名字,可以格式化输出
    返回值__k线程结构体指针(struct task_struct * )

    注意:kthread_run()创建的线程,是立刻运行的。

    (2)使用例子:

    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
    #include <linux/kmod.h>
    #include <linux/sched.h>
    #include <linux/delay.h>
    #include <linux/kthread.h>
    
    MODULE_AUTHOR("zimu");
    MODULE_LICENSE("GPL");
    
    #define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
    
    int my_kernel_thread(void *arg)
    {
            int n = 0;
    
            while(1)
            {
                    printk("%s: %d\n",__func__,n++);
                    ssleep(3);
            if(kthread_should_stop())
                    {
                            break;
                    }
    
            }
    
            return 0;
    }
    static int __init practice_call(void)
    {
            printk("%s:\n",__func__);
    
            practice_task_p = kthread_run(my_kernel_thread,NULL,"practice task");    
    
            return 0;
    }
    
    static void __exit practice_call_exit(void)
    {
            printk("%s:\n",__func__);
            kthread_stop(practice_task_p);
    }
    
    module_init(practice_call);
    module_exit(practice_call_exit);

    注意:kernel_thread()的参数fn,以及kthread_create()/kthread_run()的参数threadfn即使是一样的函数,只要创建的线程不一样(线程名字不一样),那么fn,threadfn函数在不同的线程里面,运行的空间地址是不一样的。


                                                                    线程状态


      一、线程park状态

    (1)park与stop的区别:

    线程的stop(kthread_stop())状态指线程终止,线程的生命周期结束
    线程的park状态(kthread_park())指线程暂时停止(可以理解就是挂起),当执行换醒线程(kthread_unpark())后,线程从挂起状态唤醒,接着运行

    (2)park的挂起与唤醒函数

    挂起kthread_park
    唤醒kthread_unpark

    (3)使用例子

    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <linux/init.h>
    #include <linux/kmod.h>
    #include <linux/sched.h>
    #include <linux/delay.h>
    #include <linux/kthread.h>
    
    MODULE_AUTHOR("zimu");
    MODULE_LICENSE("GPL");
    
    #define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
    
    int my_kernel_thread(void *arg)
    {
            int n = 0;
    
            while(1)
            {
                    printk("%s: %d\n",__func__,n++);
                    ssleep(3);
    
            if(kthread_should_park())
                kthread_parkme();
    
            if(kthread_should_stop())
                    {
                            break;
                    }
    
            }
    
            return 0;
    }
    static int __init practice_call(void)
    {
            printk("%s:\n",__func__);
            practice_task_p = kthread_run(my_kernel_thread,NULL,"practice task");    
    
            return 0;
    }
    
    static void __exit practice_call_exit(void)
    {
            printk("%s:\n",__func__);
            kthread_stop(practice_task_p);
    }
    
    module_init(practice_call);
    module_exit(practice_call_exit);

    (4)park状态判断

    if(kthread_should_park())
        kthread_park();

    kthread_should_park()函数判断了调用kthread_park()函数的该线程是否应该挂起,返回true就挂起,false就是不挂起

    二、线程的freez状态

    1、进程(线程)冻结技术(freezing of tasks)是指在系统hibernate或者suspend的时候,将用户进程和部分内核线程置于“可控”的暂停状态。
    2、当系统进入hibernate或者suspend的时候,线程如果要响应,那么线程需要使用相应接口将线程冻结。

    (1)接口函数

    #include <linux/freezer.h>
    
    static inline bool try_to_freeze(void);
    static inline bool freezing(struct task_struct *p);
    
    #include <linux/kthread.h>
    
    bool kthread_freezable_should_stop(bool *was_frozen);

    (2)使用方法:

    第一种:kthread_freezable_should_stop(bool *was_frozen)函数

    int my_kernel_thread(void *arg)
    {
            int n = 0;
            bool free;
    
            while(1)
            {
                    printk("%s: %d\n",__func__,n++);
                    ssleep(3);
    
    //会一直阻塞在这里,知道系统被唤醒
    //kthread_freezable_should_stop函数会判断这个线程唤醒后是否需要停止
            if(kthread_freezable_should_stop())
            {
                break;
            }
    
            }
    
            return 0;
    }
    kthread_freezable_should_stop如果系统进入hibernate或者suspend,将当前线程挂起,并置位PF_FROZEN标志,调用schedule()放弃CPU控制全,直到系统退出hibernate或者suspend,或者其他唤起,kthread_freezable_should_stop才会返回,否则将一直不返回。
    kthread_freezable_should_stop为了安全,返回的是线程是否需要退出真值(true,false),返回的was_frozen值,true表示线程是从冻结后唤醒退出的,false表示没有被冻结。

    第二种:

    int my_kernel_thread(void *arg)
    {
            int n = 0;
            bool free;
    
            while(1)
            {
                    printk("%s: %d\n",__func__,n++);
                    ssleep(3);
    
            if(freezing(current))
            {
                try_to_freeze();
            }
            if(kthread_should_stop())
                    {
                            break;
                    }
    
            }
    
            return 0;
    }
    
    freezing()判断系统是否进入冻结,ture冻结,false没有冻结
    try_to_freeze()将当前线程冻结,冻结后将不会返回,直到解冻,或者其他唤起,否则一直不返回

    总结:线程是否要响应系统的冻结状态,需要根据情况,看系统冻结后,驱动模块是否需要运行

    三、线程绑定

    #include <linux/kthread.h>
    
    void kthread_bind(struct task_struct *k, unsigned int cpu);
    功能将线程和CPU绑定,这个线程只在绑定的CPU上运行,在多核CPU上用
    k线程结构体指针
    cpu要绑定的CPU号

     

     

    最后总结:在驱动程序中创建一个线程,首先使用线程创建函数生产一个线程,然后使用线程执行函数后者结束函数控制线程开始于停止,也可以使用状态函数让线程处于挂起状态和运行状态,在线程的执行函数中使用状态函数获取线程的当前状态,用于确定具体要执行的任务。线程的执行函数中是一个无线循环体,在循环体里面加入线程的状态判断或获取函数,结合需要,在对应的条件下执行操作,比如停止线程操作就是一个跳出循环的break语句,改变或判断线程状态就是先使用线程的kthread_should_stop或kthread_should_park函数作为执行操作的条件,如果条件符合,就在这个条件的代码段使用线程的控制函数。

    展开全文
  • linux内核线程创建与销毁

    千次阅读 2014-04-07 21:03:00
    linux创建内核线程的工作交给了一个专门的内核线程kthreadd来完成,该线程会检查全局链表kthread_create_list,如果为NULL,就会调schedule()放弃cpu进入睡眠状态,否则就取下该链表中的一项创建对应的线程。...

    linux内核线程的创建与销毁

    linux将创建内核线程的工作交给了一个专门的内核线程kthreadd来完成,该线程会检查全局链表kthread_create_list,如果为NULL,就会调schedule()放弃cpu进入睡眠状态,否则就取下该链表中的一项创建对应的线程。本文就从khtreadd内核线程的创建开始来展示一下内核线程的创建过程。

    1 kthreadd

    linux2.6.30,创建内核线程是通过kethradd内核守护线程来完成的,虽然机制上有所变化,但是最终还是调用do_fork来完成线程的创建。Kthreadd守护线程是在linux内核启动时就已经创建的内核线程。在start_kernel调用的结束位置,会调用rest_init接口,而kthreadd就是在这个接口中创建的,代码如下所示:

    asmlinkage void __init start_kernel(void)

    {

    。。。。。。。。。。。。。。。。。。。。。。

           rest_init();

    }

    static noinline void __init_refok rest_init(void)

           __releases(kernel_lock)

    {            

           int pid;

           /*在启动时创建内核线程,该线程主要用来创建内核线程*/

           pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

           kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);

     }

    Kthreadd守护线程本身也是一个内核线程,这个内核线程本质上与其他内核线程都一样,只不过是这个内核线程所做的工作是用来创建内核线程的。函数的调用关系如下所示:

    Start_kernel=》rest_init=》kernel_thread=》do_fork

    没有内核线程创建请求时,Kthreadd守护线程就处于睡眠状态,一旦有内核线程创建请求,则唤醒kthreadd守护线程,完成内核线程的创建工作。

    kthreadd守护线程主要通过传入内核线程创建参数来创建特定的内核线程,因此,在讲述内核线程的创建之前,有必要了解一下内核线程的创建参数。内核线程创建相关的数据结构主要是struct kthread_create_info,这个数据结构表示创建一个内核线程所需要的信息。结构体定义如下所示:

    structkthread_create_info           

    {

             /* Information passed to kthread() fromkthreadd. */

             int (*threadfn)(void *data);/*内核线程的回调函数*/

             void *data;/*回调函数的参数*/

             struct completion started;/*kthreadd等待新创建的线程启动*/

     

             /* Result passed back to kthread_create()from kthreadd. */

             struct task_struct *result;/*创建成功之后返回的task_struct*/

             struct completion done;/*用户等待线程创建完成*/

     

             struct list_head list;/*主要用于将创建参数结构放在一个全局链表中*/

    };

    用户需要创建一个内核线程时,会填充该数据结构,并将该结构体挂在全局的kthread_create_list链表中,然后唤醒kthreadd守护线程来创建内核线程,而用户线程则阻塞等待内核线程创建完成。详细的用户接口下文会介绍,这里就不在赘述。

      根据前面对内核线程创建结构体的描述,kthreadd守护线程需要完成的工作就比较简单,主要就是遍历kthread_create_list,如果链表中有需要创建的内核线程,则调用create_kthread完成内核线程的创建工作。反之,没有内核线程需要创建,那么kthreadd守护线程将睡眠,等待下次内核线程创建请求的唤醒。

    static voidcreate_kthread(struct kthread_create_info *create)

    {

           int pid;

           /* We want our own signal handler (wetake no signals by default). */

           pid =kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

           if (pid < 0)

                  create->result = ERR_PTR(pid);

           else

                  wait_for_completion(&create->started);/*等待新创建的线程启动*/

           complete(&create->done);/*通知用户请求创建的线程已经完成*/

    }

    create_kthread主要开始调用kernel_thread来创建内核线程,到这里,内核线程的创建工作跟kthreadd内核守护线程创建过程就一致了,可谓是殊途同归。如果Kthreadd创建线程失败,则直接通知用户请求创建动作已经完成,至于是不是创建成功,需要用户自己判断。反之,内核线程创建成功,kthreadd守护线程会等待新创建的线程启动,然后才通知用户请求创建内核线程的动作已经完成。完成了一个内核线程的创建之后,从kthread_create_list链表中摘下下一个需要创建的内核线程,指定所有请求的内核线程创建成功,ktheadd线程睡眠。

    2 kthread_create and kthread_run

    既然linux2.6.30采用了kthreadd守护线程来创建内核线程,那么在内核编程的时候,用户就不应该直接调用kernel_thread来创建内核线程。(这里只是猜测,具体细节有待研究)linux内核提供给用户用来创建内核线程的接口有两个,一个是kthread_create,另一个是kthread_run。kthread_run接口其实也就是kthread_create的封装。所不同的是,kthread_run在内核线程创建成功之后,就直接调用了wake_up_process,来唤醒该内核线程,使其能够立刻得到调度,而通过kthread_create创建的内核线程默认是睡眠的,并不会得到调度,而是需要显示的唤醒该内核线程才能得到调度。代码如下所示:

    structtask_struct * kthread_create(int (*threadfn)(void *data),

                                            void *data,

                                            const char namefmt[],

                                            ...)

    {

             struct kthread_create_info create;

      /*填充create结构体*/

             create.threadfn = threadfn;

             create.data = data;

             init_completion(&create.started);

             init_completion(&create.done);

    /*create结构体挂到全局的kthread_create_list链表中*/

             spin_lock(&kthread_create_lock);

             list_add_tail(&create.list,&kthread_create_list);

             spin_unlock(&kthread_create_lock);

    /*唤醒kthradd守护线程来创建内核线程*/

             wake_up_process(kthreadd_task);

    /*等待线程的创建结束*/

             wait_for_completion(&create.done);

    /*kthreadd线程并不能保证100%创建成功,这里需要在做验证*/

             if (!IS_ERR(create.result)) {

                       struct sched_param param = {.sched_priority = 0 };

                       va_list args;

                        * root may have changed our (kthreadd's)priority or CPU mask.

                        * The kernel thread should not inherit theseproperties.

                        */

    /*设置线程的优先级和cpu亲和性*/

                       sched_setscheduler_nocheck(create.result,SCHED_NORMAL, &param);

                       set_user_nice(create.result,KTHREAD_NICE_LEVEL);

                       set_cpus_allowed_ptr(create.result,cpu_all_mask);

             }

             return create.result;

    }

          由前文可知,创建一个内核线程,首先要做的就是填充kthread_create_info结构体,将线程的处理函数以及相关参数传递给kthreadd守护线程,这样才能完成用户需要新创建线程所完成的工作。填充完结构体之后,还需要将结构体挂到全局的kthread_create_list链表中,然后唤醒kthreadd线程开始创建内核线程,用户线程睡眠等待新线程创建动作的完成,新线程完成之后,设置线程的相关属性,此时,线程就可以完成相关的工作了。

    3 内核线程处理函数

      内核线程的线程处理函数并不是用户自定义的接口,而是内核实现的一个统一的接口。这个统一的接口就是kthread,而用户自定义的线程处理函数是通过该统一接口进行回调的。Kthread接口完成了很多方面的工作,首先,就是我们刚刚说的回调用户自定义的接口,以便于内核线程能够完成用户想让该线程完成的工作。其次,就是通知kthreadd守护线程,新创建的线程已经开始启动,khtreadd线程可以创建下一个内核线程,第三,就是实现新创建的线程在没有显式调用唤醒之前,该线程是睡眠的;第四,就是可以很方便的将新建的内核线程终止,释放相关的资源。代码如下所示:

    static int kthread(void *_create)

    {

             struct kthread_create_info *create =_create;

             int (*threadfn)(void *data);

             void *data;

             int ret = -EINTR;

     

             /* Copy data: it's on kthread's stack*/

             threadfn = create->threadfn;

             data = create->data;

     

             /* OK, tell user we're spawned, waitfor stop or wakeup */

             __set_current_state(TASK_UNINTERRUPTIBLE);

             create->result = current;

             complete(&create->started);

             schedule();

       /*线程没有被终止,则调用用户的回调接口完成相关的工作*/

             if (!kthread_should_stop())

                       ret = threadfn(data);

     

             /* It might have exited on its own, w/okthread_stop.  Check. */

    /*如果用户希望终止该线程,则通知用户已经完成终止的准备动作*/

    if(kthread_should_stop()) {

                       kthread_stop_info.err = ret;

                       complete(&kthread_stop_info.done);

             }

             return 0;

    }

    4 终止内核线程

    内核线程的终止是通过kthread_stop接口完成的。用户一旦调用了kthread_stop接口,首先会唤醒该内核线程,并设置需要终止的线程,然后睡眠,等待线程处理函数的唤醒。由前面的内核线程的处理函数可以看到,就不会在执行用户自定义的回调接口,同时会唤醒用户线程,完成终止内核线程的工作。Kthread_stop用来trace_point来完成内核线程的清理工作,trace_point的工作原理还不清楚,姑且就认为其可以完成我们需要的清理工作吧。

    5 绑定内核线程到指定的cpu

    对应SMP架构的CPU,可以通过kthread_bind接口将创建的内核线程绑定到某个指定的CPU上执行。通过绑定,可以减少内核线程在CPU之间的迁移,提高内核线程的工作效率。绑定CPU,主要原理是通过CPU的亲和性来实现的。这也是LINUX调度器天生就支持的,这里只不过是利用了调度器的亲和性功能实现了内核线程的绑定功能。

    6总结

            内核线程在linux内核中经常被使用,了解linux内核线程的创建以及销毁过程,可以帮助我们理解内核中利用内核线程完成的某些功能的工作原理。例如,work_queue,work_queue就是通过创建内核线程来完成相关的功能,如果我们知道内核线程的工作原理,在利用work_queue的时候,就能做到心中有底,可以知道什么时候work_queue在工作,什么时候在休眠等等。总之,从点滴开始学习linxu内核,可以帮助我们刚好的理解内核的一些功能的机制和原理。

     


    展开全文
  • 为什么在pthread_cond_wait()前要加一个while循环来判断条件是否为假呢?.zip
  • 文章目录问题描述问题分析针对问题1 的猜测:针对问题2 的猜测:原理追踪总结 问题描述 事情开始于一段内存问题,通过gperf工具抓取进程运行过程中的...查看进程代码,发现确实有大量的线程创建,我们知道线程是有自己独
  • linux内核线程

    万次阅读 2018-10-16 18:22:49
    内核经常需要在后台执行一些操作,这种任务就可以通过内核线程(kernle thread)完成,内核线程是独立运行在内核空间的标准进程。...实际上,内核线程只能由其他内核线程创建linux驱动模块中可以用kernel_threa...
  • 一、判断数独是否有效: #include <pthread.h> #include <stdio.h> #include <stdlib.h> int judgeRow(int row);//判断行 int judgeCol(int col);//判断列 int judgeLump(int row, int col);//...
  • linux创建线程用到的函数和返回值

    千次阅读 2019-01-10 10:55:22
    (1)int pthread_create(pthread_t*thread,const pthread_attr_t *attr,void*(*start_routine...返回值:线程创建函数,创建成功返回0,失败返回相关错误代码; 参数: thread:指向线程标识符的指针 tattr:设置...
  • pthread_kill: 别被名字吓到,pthread_kill可不是kill,而是向线程发送signal。还记得signal吗,大部分signal的默认动作是终止进程的运行,所以,我们才要...向指定ID的线程发送sig信号,如果线程代码内不做处理,
  • linux创建线程执行

    千次阅读 2016-06-29 16:39:17
    linux创建线程执行    线程(thread, 台湾称“执行绪”)是“进程”中某个单一顺序的控制流。也被称为轻量进程(lightweight processes)。计算机科学术语,指运行中的程序的调度单位。  线程是操作...
  • linux线程线程同步(锁的应用)

    千次阅读 2018-06-08 15:36:33
    linux原本没有线程,后来在windows多线程编程影响下linux内核开发者在进程基础上在功能上做出了类似windows线程linux版本的线程linux线程归根到底还是进程,只不过是轻量级的进程,开销比真正进程要小得多,...
  • Linux_C编程—创建线程

    千次阅读 2019-09-19 22:42:44
    文章目录线程创建线程 线程 学习了进程后,了解到进程是是资源分配的最小单位,而线程是调度的最小单位,线程相比于进程的不同如下: 1.进程拥有独立的地址空间、代码段、数据段、堆栈段,而线程只有独立的堆栈段;...
  • 函数原型: #include int pthread_create(pthread_t*thread,pthread_attr_t *attr, ... 第一个参数为指向线程标识符的指针。  第二个参数用来设置线程属性。  第三个参数是线程运行函数的地址。  最后一个
  • 文章目录一、Linux线程基本概念二、Linux内核线程实现原理三、创建线程四、线程的优缺点 一、Linux线程基本概念 linux中,线程又叫做轻量级进程(light-weight process LWP),也有PCB,创建线程使用的底层函数和...
  • Linux C线程创建和使用

    千次阅读 2015-05-30 18:35:36
    1 引言  线程(thread)技术早在60年代就被提出,但真正应用多线程到操作...现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。  为什么有了进程的概念后,还要再引入线程呢?使用
  • linux常用线程函数返回值

    千次阅读 2018-05-10 21:42:16
    int pthread_create(pthread_t*thread,const pthread_attr_t *attr,void*(*start_routine)(void *),void*arg) 线程创建函数,创建成功返回0,失败返回相关错误代码;pthread_t pthread_self() 获取线程ID...
  • linux线程创建,同步和退出》

    千次阅读 2015-02-26 10:26:07
    前面有一篇文章专门讲述了进程创建,监控和终止,这一篇文章进一步来谈谈线程创建和同步等操作(这里指的是POSIX规范下的线程,即Pthreads)。和探讨进程的文章类似,还是通过讲述相关调用的使用和注意事项来推进...
  • t 甚或几 p 的数据的数据库系统,到手机上的一个有良好用户响应能力的 app,为了充分利用每个 CPU 内核,都会想到是否可以使用多线程技术。这里所说的“充分利用”包含了两个层面的意思,一
  • 线程面试题(值得收藏)

    万次阅读 多人点赞 2019-08-16 09:41:18
    史上最强多线程面试47题(含答案),建议收藏 金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~ 1、并发编程三要素?...
  • linux 中断线程

    千次阅读 2017-05-27 12:24:01
    Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不到及时的处理。...
  • 使用pthread_kill函数检测一个线程是否还活着的程序,在linux环境下gcc编译通过,现将代码贴在下面:/******************************* pthread_kill.c *******************************/#include &lt;stdio.h&...
  • Linux下c语言多线程编程

    千次阅读 2017-08-03 08:58:04
    系列中不但会详细讲解多线程同步互斥的各种“招式”,而且会进一步的讲解多线程同步互斥的“内功心法”。有了“招式”和“内功心法”,相信你也能对多线程挥洒自如,在笔试面试中顺利的秒杀多线程试题。  --------...
  • Linux线程模型

    千次阅读 2019-02-28 22:53:19
    一直以来, linux内核并没有线程的概念.每一个执行实体都是一个task_struct结构,通常称之为进程.Linux内核在2.0.x版本就已经实 现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建...
  • linux下进程和线程状态查看

    万次阅读 2017-09-05 10:28:45
    检查 使用 ps -fe |grep programname 查看获得进程的pid,再使用 ps -Lf pid 查看对应进程下的线程数. 查找资料发现可以通过设置 ulimit -s 来增加每进程线程数。 每进程可用线程数 = VIRT上限/stack size 32位x86...
  • Linux pthread_creat() 创建线程失败问题总结 目录 问题场景 问题详细描述 问题分析定位 1)pthread_create() 函数原型 2)实测系统最多可创建线程数 3)测试结果 4)查看shell启动进程所占用的资源...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 93,094
精华内容 37,237
关键字:

linux判断线程已创建

linux 订阅