linux多线程_linux多线程编程 - CSDN
  • Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量、信号量和读写锁。  下面是思维导图: 简介 进程— 资源分配的最小单位 线程— 程序执行的最小单位 进程是一个程序的一个实例,拥有自...

    本篇博文转自http://zhangxiaoya.github.io/2015/05/15/multi-thread-of-c-program-language-on-linux/

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量、信号量和读写锁。 
    下面是思维导图:

    简介

    进程— 资源分配的最小单位
    线程— 程序执行的最小单位

    进程是一个程序的一个实例,拥有自己独立的各种段(数据段,代码段等等),每次创建一个进程需要从操作系统分配这些资源给他,消耗一定的时间,在linux下C语言创建一个进程使用fork()函数;
    线程是一个轻量级的进程,除了自己少数的资源,不用用其他资源,且一个进程可以创建多个线程,这些线程共享进程的资源,创建线程的时间要比创建进程少很多,(几十分之一),从函数角度是使用pthread_create()创建。
    使用线程处理文件I/O或者socket处理都是非常有优势的,将一个大人物分解成若干个小任务,每个线程处理一个任务,线程之间切换不需要花很多时间,而且线程之间数据交换很方便,共享存储区。

    C语言中使用多线程的函数

    表 1. 线程函数列表

    对象 操作 Linux Pthread API Windows SDK 库对应 API
    线程 创建 pthread_create CreateThread
    退出 pthread_exit ThreadExit
    等待 pthread_join WaitForSingleObject
    互斥锁 创建 pthread_mutex_init CreateMutex
    销毁 pthread_mutex_destroy CloseHandle
    加锁 pthread_mutex_lock WaitForSingleObject
    解锁 pthread_mutex_unlock ReleaseMutex
    条件 创建 pthread_cond_init CreateEvent
    销毁 pthread_cond_destroy CloseHandle
    触发 pthread_cond_signal SetEvent
    广播 pthread_cond_broadcast SetEvent / ResetEvent
    等待 pthread_cond_wait / pthread_cond_timedwait SingleObjectAndWait

    多线程开发在 Linux 平台上已经有成熟的 Pthread 库支持。其涉及的多线程开发的最基本概念主要包含四点:线程,互斥锁,条件变量、读写锁。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。

    创建线程

    int pthread_create(pthread_t * tid, const pthread_attr_t * attr, void * ( * func) (void * ), void * arg);
    其返回值是一个整数,若创建进程成功返回0,否则,返回其他错误代码,也是正整数。

    创建线程需要的参数:

    • 线程变量名:pthread_t *类型,是标示线程的id,一般是无符号整形,这里也可以是引用类型,目的是用于返回创建线程的ID
    • 线程的属性指针:制定线程的属性,比如线程优先*级初始栈大小等,通常情况使用的都是指针。
    • 创建线程的程序代码:一般是函数指针,进程创建后执行该函数指针只想的函数。
    • 程序代码的参数:若线程执行的函数包含由若干个参数,需要将这些参数封装成结构体,并传递给它指针。
      创建线程的函数的形式如下:

    结束线程

    结束进程的函数定义如下:

    void pthread_exit (void *status);
    参数是指针类型,用于存储线程结束后返回状态。

    线程等待

    int pthread_join (pthread_t tid, void ** status);

    • 第一个参数表示要等待的进程的id;
    • 第二参数表示要等待的进程的返回状态,是个二级指针。

    线程创建后怎么执行,新线程和老线程谁先执行这些不是程序来决定,而是由操作系统进行调度的,但是在编程的时候我们常常需要多个线程配合工作,比如在结束某个线程之前,需要等待另外一个线程的处理结果(返回状态等信息),这时候就需要使用线程等待函数,这个函数的定义如下:

    其他关于进程的函数

    1. 返回当前线程ID

      pthread_t pthread_self (void);
      用于返回当前进程的ID

    2. 制定线程变成分裂状态

      int pthread_detach (pthread_t tid);
      参数是指定线程的ID,指定的ID的线程变成分离状态;若指定线程是分离状态,则 如果线程退出,那么它所有的资源都将释放,如果线程不是分离状态,线程必须保留它的线程ID、退出状态,直到其他线程对他调用的pthread_join()函数

    参考实例一

    代码如下:

    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    void print_message_func(void *ptr);
    
    int main()
    {
        int tmp1,tmp2;
        void *retival;
        pthread_t thread1,thread2;
        char *message1 = "thread1";
        char *message2 = "thread2";
    
        int ret_thread1,ret_thread2;
        ret_thread1 = pthread_create(&thread1,NULL,(void *)&print_message_func,(void *)message1);
    
        if(ret_thread1 == 0)
            printf("create thread 1 true\n");
        else
            printf("create thread 1 false\n");
    
        tmp1 = pthread_join(thread1,&retival);
        printf("thread 1 return value (tmp1) is %d\n",tmp1);
    
        if(tmp1 != 0)
            printf("cannot join with thread 1\n");
    
        ret_thread2 = pthread_create(&thread2,NULL,(void *)&print_message_func,(void *)message2);
    
        if(ret_thread2 == 0)
            printf("create thread 2 true\n");
        else
            printf("create thread 2 false\n");
    
        tmp2 = pthread_join(thread2,&retival);
        printf("thread 2 return value (tmp2) is %d\n",tmp2);
        if(tmp2 != 0)
            printf("cannot join with thread 2\n");
    }
    
    void print_message_func(void *ptr)
    {
        for(int i=0;i<5;++i)
        {
            printf("%s:%d\n",(char*)ptr,i);
        }
    }
    

    这个代码比较简单,就是演示这几个常用函数的使用。

    这里是纯C语言程序,在Linux下的编译命令是gcc test.c -o test -lpthread,运行程序是./test,后面的程序同样

    多线程的同步与互斥

    锁机制

    多线程之间可能需要互斥的访问一些全局变量,这就需要互斥的来访问,这些需要共享访问的字段被称作是临界资源,访问临界资源的程序段称作是临界区
    实现线程间的互斥与同步机制的是锁机制,下面是常用的锁机制的函数和类。

    1. pthread_mutex_t mutex 锁对象
    2. pthread_mutex_init(&mutex,NULL) 在主线程中初始化锁为解锁状态
    3. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER 编译时初始化锁位解锁状态
    4. pthread_mutex_lock(&mutex)(阻塞加锁)访问临界区加锁操作
    5.  pthread_mutex_trylock( &mutex)(非阻塞加锁); pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。
    6. pthread_mutex_unlock(&mutex): 访问临界区解锁操作

    参考实例二(不加锁访问互斥全局变量)

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    int sharei = 0;
    void increase_num(void);
    
    int main()
    {
      int ret;
      pthread_t thread1,thread2,thread3;
      ret = pthread_create(&thread1,NULL,(void *)&increase_num,NULL);
      ret = pthread_create(&thread2,NULL,(void *)&increase_num,NULL);
      ret = pthread_create(&thread3,NULL,(void *)&increase_num,NULL);
    
      pthread_join(thread1,NULL);
      pthread_join(thread2,NULL);
      pthread_join(thread3,NULL);
    
      printf("sharei = %d\n",sharei);
    
      return 0;
    }
    
    void increase_num(void)
    {
      long i,tmp;
      for(i =0;i<=10000;++i)
      {
        tmp = sharei;
        tmp = tmp + 1;
        sharei = tmp;
      }
    }

    编译运行结果,多运行几次,发现结果都不一样。这就是因为对于全局变量,没有添加互斥锁,导致的问题。

    参考实例三 (访问全局变量添加互斥锁)

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    int sharei = 0;
    void increase_num(void);
    // add mutex
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    int main()
    {
      int ret;
      pthread_t thread1,thread2,thread3;
      ret = pthread_create(&thread1,NULL,(void *)&increase_num,NULL);
      ret = pthread_create(&thread2,NULL,(void *)&increase_num,NULL);
      ret = pthread_create(&thread3,NULL,(void *)&increase_num,NULL);
    
      pthread_join(thread1,NULL);
      pthread_join(thread2,NULL);
      pthread_join(thread3,NULL);
    
      printf("sharei = %d\n",sharei);
    
      return 0;
    }
    
    void increase_num(void)
    {
      long i,tmp;
      for(i =0;i<=10000;++i)
      {
        // lock
        if(pthread_mutex_lock(&mutex) != 0)
        {
          perror("pthread_mutex_lock");
          exit(EXIT_FAILURE);
        }
        tmp = sharei;
        tmp = tmp + 1;
        sharei = tmp;
        // unlock
        if(pthread_mutex_unlock(&mutex) != 0)
        {
          perror("pthread_mutex_unlock");
          exit(EXIT_FAILURE);
        }
      }
    }

    添加互斥锁后,就发现,多次运行的结果都是一样的。

    1. 其实这里的加锁不是对共享变量(全局变量)或者共享内存进行保护,这里的加锁实际上是对临界区的控制,所谓的临界区就是访问临界资源的那一段代码,这段代码对临界资源进行多种操作,正确的情况是不允许这段代码执行到一半,处理器使用权就被其他线程抢走,所以这段代码具有原子性,即要么执行,要么不执行,不能执行到一半就被抢走处理权,这样就会造成共享数据被污染。
    2. 还有一点,添加锁来控制临界区是有代价的,这个代价表现出来就是时间的额外开销,内部过程是因为要保护现场,会利用一些资源,也需要处理器处理的时间。

    信号量机制

    锁机制使用是有限制的,锁只有两种状态,即加锁和解锁,对于互斥的访问一个全局变量,这样的方式还可以对付,但是要是对于其他的临界资源,比如说多台打印机等,这种方式显然不行了。
    信号量机制在操作系统里面学习的比较熟悉了,信号量是一个整数计数器,其数值表示空闲临界资源的数量。
    当有进程释放资源时,信号量增加,表示可用资源数增加;当有进程申请到资源时,信号量减少,表示可用资源数减少。这个时候可以把锁机制认为是0-1信号量。
    关于信号量机制的函数。

    int sem_init(sem_t * sem, int pshared, unsigned int value);初始化信号量

    - 成功返回0,失败返回-1;
    - 参数sem:表示指向信号结构的指针。
    - 参数pshared:不是0 的时候该信号量在进程间共享,否则只能在当前进程的所有线程间共享。
    - 参数value:信号量的初始值。
    

    int sem_wait(sem_t *sem); 信号量减一操作,有线程申请资源

    - 成功返回0,否则返回-1
    - 参数sem:指向一个信号量的指针
    

    int sem_post(sem_t *sem);信号量加一操作,有线程释放资源

    - 成功返回0,否则返回-1
    - 参数sem:指向一个信号量指针
    

    int sem_destroy(sem_t *sem); 销毁信号量。

    - 成功返回0,否则返回-1
    - 参数sem:指向一个信号量的指针。
    

    参考实例四(生产者消费者)

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    #define MAXSIZE 10
    
    int stack[MAXSIZE];
    
    int size =0;
    sem_t sem;
    
    void privide_data(void)
    {
      int i;
      for(i =0;i<MAXSIZE;++i)
      {
        stack[i] = i;
        sem_post(&sem);
      }
    }
    
    void handle_data(void)
    {
      int i;
      while((i = size ++) <MAXSIZE)
      {
        sem_wait(&sem);
        printf("cross : %d X %d = %d \n",stack[i],stack[i],stack[i] * stack[i]);
        sleep(1);
      }
    }
    
    int main()
    {
      pthread_t privider,handler;
      sem_init(&sem,0,0);
      pthread_create(&privider,NULL,(void *)&privide_data,NULL);
      pthread_create(&handler,NULL,(void *)&handle_data,NULL);
      pthread_join(privider,NULL);
      pthread_join(handler,NULL);
      sem_destroy(&sem);
    
      return 0;
    }

    这段代码是经典的生产者消费者问题,只有当生产者把资源放入存储区,消费者才能取得。

     

     

    附:(知识点)

    Linux 线程编程中的 5 条经验

    尽量设置 recursive 属性以初始化 Linux 的互斥变量

    互斥锁是多线程编程中基本的概念,在开发中被广泛使用。其调用次序层次清晰简单:建锁,加锁,解锁,销毁锁。但是需要注意的是,与诸如 Windows 平台的互斥变量不同,在默认情况下,Linux 下的同一线程无法对同一互斥锁进行递归加速,否则将发生死锁。

    所谓递归加锁,就是在同一线程中试图对互斥锁进行两次或两次以上的行为。其场景在 Linux 平台上的代码可由清单 1 所示。

    清单 1. Linux 重复对互斥锁加锁实例

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    // 通过默认条件建锁

        pthread_mutex_t *theMutex = new pthread_mutex_t;

        pthread_mutexattr_t attr;

        pthread_mutexattr_init(&attr);

        pthread_mutex_init(theMutex,&attr);

        pthread_mutexattr_destroy(&attr);

     

        // 递归加锁

        pthread_mutex_lock (theMutex);

        pthread_mutex_lock (theMutex);

        pthread_mutex_unlock (theMutex);

        pthread_mutex_unlock (theMutex);

    在以上代码场景中,问题将出现在第二次加锁操作。由于在默认情况下,Linux 不允许同一线程递归加锁,因此在第二次加锁操作时线程将出现死锁。

    Linux 互斥变量这种奇怪的行为或许对于特定的某些场景会所有用处,但是对于大多数情况下看起来更像是程序的一个 bug 。毕竟,在同一线程中对同一互斥锁进行递归加锁在尤其是二次开发中经常会需要。

    这个问题与互斥锁的中的默认 recursive 属性有关。解决问题的方法就是显式地在互斥变量初始化时将设置起 recursive 属性。基于此,以上代码其实稍作修改就可以很好的运行,只需要在初始化锁的时候加设置一个属性。请看清单 2 。

    清单 2. 设置互斥锁 recursive 属性实例

    1

    2

    3

    4

    pthread_mutexattr_init(&attr);

        // 设置 recursive 属性

        pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP);

        pthread_mutex_init(theMutex,&attr);

    因此,建议尽量设置 recursive 属性以初始化 Linux 的互斥锁,这样既可以解决同一线程递归加锁的问题,又可以避免很多情况下死锁的发生。这样做还有一个额外的好处,就是可以让 Windows 和 Linux 下让锁的表现统一。

    注意 Linux 平台上触发条件变量的自动复位问题

    条件变量的置位和复位有两种常用模型:第一种模型是当条件变量置位(signaled)以后,如果当前没有线程在等待,其状态会保持为置位(signaled),直到有等待的线程进入被触发,其状态才会变为复位(unsignaled),这种模型的采用以 Windows 平台上的 Auto-set Event 为代表。其状态变化如图 1 所示:

    图 1. Windows 的条件变量状态变化流程

    Windows 的条件变量状态变化流程

    第二种模型则是 Linux 平台的 Pthread 所采用的模型,当条件变量置位(signaled)以后,即使当前没有任何线程在等待,其状态也会恢复为复位(unsignaled)状态。其状态变化如图 2 所示:

    图 2. Linux 的条件变量状态变化流程

    Linux 的条件变量状态变化流程

    具体来说,Linux 平台上 Pthread 下的条件变量状态变化模型是这样工作的:调用 pthread_cond_signal() 释放被条件阻塞的线程时,无论存不存在被阻塞的线程,条件都将被重新复位,下一个被条件阻塞的线程将不受影响。而对于 Windows,当调用 SetEvent 触发 Auto-reset 的 Event 条件时,如果没有被条件阻塞的线程,那么条件将维持在触发状态,直到有新的线程被条件阻塞并被释放为止。

    这种差异性对于那些熟悉 Windows 平台上的条件变量状态模型而要开发 Linux 平台上多线程的程序员来说可能会造成意想不到的尴尬结果。试想要实现一个旅客坐出租车的程序:旅客在路边等出租车,调用条件等待。出租车来了,将触发条件,旅客停止等待并上车。一个出租车只能搭载一波乘客,于是我们使用单一触发的条件变量。这个实现逻辑在第一个模型下即使出租车先到,也不会有什么问题,其过程如图 3 所示:

    图 3. 采用 Windows 条件变量模型的出租车实例流程

    索引使用的容量要求

    然而如果按照这个思路来在 Linux 上来实现,代码看起来可能是清单 3 这样。

    清单 3. Linux 出租车案例代码实例

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    ……

     // 提示出租车到达的条件变量

     pthread_cond_t taxiCond;

     

     // 同步锁

     pthread_mutex_t taxiMutex;

     

     // 旅客到达等待出租车

     void * traveler_arrive(void * name) {

        cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;

        pthread_mutex_lock(&taxiMutex);

        pthread_cond_wait (&taxiCond, &taxtMutex);

        pthread_mutex_unlock (&taxtMutex);

        cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;

        pthread_exit( (void *)0 );

     }

     

     // 出租车到达

     void * taxi_arrive(void *name) {

        cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl;

        pthread_cond_signal(&taxtCond);

        pthread_exit( (void *)0 );

     }

     

     void main() { 

        // 初始化

        taxtCond= PTHREAD_COND_INITIALIZER;

        taxtMutex= PTHREAD_MUTEX_INITIALIZER;

        pthread_t thread;

        pthread_attr_t threadAttr;

        pthread_attr_init(&threadAttr);

     

        pthread_create(&thread, & threadAttr, taxt_arrive, (void *)( ” Jack ” ));

        sleep(1);

        pthread_create(&thread, &threadAttr, traveler_arrive, (void *)( ” Susan ” ));

        sleep(1);

        pthread_create(&thread, &threadAttr, taxi_arrive, (void *)( ” Mike ” ));

        sleep(1);

     

        return 0;

     }

    好的,运行一下,看看结果如清单 4 。

    清单 4. 程序结果输出

    1

    2

    3

    4

    Taxi Jack arrives.

        Traveler Susan needs a taxi now!

        Taxi Mike arrives.

        Traveler Susan now got a taxi.

    其过程如图 4 所示:

    图 4. 采用 Linux 条件变量模型的出租车实例流程

    图 4. 采用Linux条件变量模型的出租车实例流程

    通过对比结果,你会发现同样的逻辑,在 Linux 平台上运行的结果却完全是两样。对于在 Windows 平台上的模型一, Jack 开着出租车到了站台,触发条件变量。如果没顾客,条件变量将维持触发状态,也就是说 Jack 停下车在那里等着。直到 Susan 小姐来了站台,执行等待条件来找出租车。 Susan 搭上 Jack 的出租车离开,同时条件变量被自动复位。

    但是到了 Linux 平台,问题就来了,Jack 到了站台一看没人,触发的条件变量被直接复位,于是 Jack 排在等待队列里面。来迟一秒的 Susan 小姐到了站台却看不到在那里等待的 Jack,只能等待,直到 Mike 开车赶到,重新触发条件变量,Susan 才上了 Mike 的车。这对于在排队系统前面的 Jack 是不公平的,而问题症结是在于 Linux 平台上条件变量触发的自动复位引起的一个 Bug 。

    条件变量在 Linux 平台上的这种模型很难说好坏。但是在实际开发中,我们可以对代码稍加改进就可以避免这种差异的发生。由于这种差异只发生在触发没有被线程等待在条件变量的时刻,因此我们只需要掌握好触发的时机即可。最简单的做法是增加一个计数器记录等待线程的个数,在决定触发条件变量前检查下该变量即可。改进后 Linux 函数如清单 5 所示。

    清单 5. Linux 出租车案例代码实例

    ……
    
     // 提示出租车到达的条件变量
    
     pthread_cond_t taxiCond;
    
    
     // 同步锁
    
     pthread_mutex_t taxiMutex;
    
    
     // 旅客人数,初始为 0
    
     int travelerCount=0;
    
    
     // 旅客到达等待出租车
    
     void * traveler_arrive(void * name) {
    
        cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;
    
        pthread_mutex_lock(&taxiMutex);
    
    
        // 提示旅客人数增加
    
        travelerCount++;
    
        pthread_cond_wait (&taxiCond, &taxiMutex);
    
        pthread_mutex_unlock (&taxiMutex);
    
        cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;
    
        pthread_exit( (void *)0 );
    
     }
    
    
     // 出租车到达
    
     void * taxi_arrive(void *name)
    
     {
    
        cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl;
    
    
     while(true)
    
     {
    
            pthread_mutex_lock(&taxiMutex);
    
    
    
            // 当发现已经有旅客在等待时,才触发条件变量
    
            if(travelerCount>0)
    
            {
    
                pthread_cond_signal(&taxtCond);
    
                pthread_mutex_unlock (&taxiMutex);
    
                break;
    
            }
    
            pthread_mutex_unlock (&taxiMutex);
    
        }
    
        pthread_exit( (void *)0 );
    
     }


    注意条件返回时互斥锁的解锁问题因此我们建议在 Linux 平台上要出发条件变量之前要检查是否有等待的线程,只有当有线程在等待时才对条件变量进行触发。

    在 Linux 调用 pthread_cond_wait 进行条件变量等待操作时,我们增加一个互斥变量参数是必要的,这是为了避免线程间的竞争和饥饿情况。但是当条件等待返回时候,需要注意的是一定不要遗漏对互斥变量进行解锁。

    Linux 平台上的 pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 函数返回时,互斥锁 mutex 将处于锁定状态。因此之后如果需要对临界区数据进行重新访问,则没有必要对 mutex 就行重新加锁。但是,随之而来的问题是,每次条件等待以后需要加入一步手动的解锁操作。正如前文中乘客等待出租车的 Linux 代码如清单 6 所示:

    清单 6. 条件变量返回后的解锁实例

    1

    2

    3

    4

    5

    6

    7

    8

    void * traveler_arrive(void * name) {

        cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl;

        pthread_mutex_lock(&taxiMutex);

        pthread_cond_wait (&taxiCond, &taxtMutex);

        pthread_mutex_unlock (&taxtMutex);

        cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl;

        pthread_exit( (void *)0 );

     }

    等待的绝对时间问题

    超时是多线程编程中一个常见的概念。例如,当你在 Linux 平台下使用 pthread_cond_timedwait() 时就需要指定超时这个参数,以便这个 API 的调用者最多只被阻塞指定的时间间隔。pthread_cond_timedwait() 函数定义

    1

    2

    3

    int pthread_cond_timedwait(pthread_cond_t *restrict cond,

                  pthread_mutex_t *restrict mutex,

                  const struct timespec *restrict abstime);

    参数 abstime 在这里用来表示和超时时间相关的一个参数,但是需要注意的是它所表示的是一个绝对时间,而不是一个时间间隔数值,只有当系统的当前时间达到或者超过 abstime 所表示的时间时,才会触发超时事件。

     相对时间到绝对时间转换实例

    1

    2

    3

    4

    5

    6

    7

    /* get the current time */

        struct timeval now;

        gettimeofday(&now, NULL);

         

        /* add the offset to get timeout value */

        abstime ->tv_nsec = now.tv_usec * 1000 + (dwMilliseconds % 1000) * 1000000;

        abstime ->tv_sec = now.tv_sec + dwMilliseconds / 1000;

    Linux 的绝对时间看似简单明了,却是开发中一个非常隐晦的陷阱。而且一旦你忘了时间转换,可以想象,等待你的错误将是多么的令人头疼:如果忘了把相对时间转换成绝对时间,相当于你告诉系统你所等待的超时时间是过去式的 1970 年 1 月 1 号某个时间段,于是操作系统毫不犹豫马上送给你一个 timeout 的返回值,然后你会举着拳头抱怨为什么另外一个同步线程耗时居然如此之久,并一头扎进寻找耗时原因的深渊里。

    正确处理 Linux 平台下的线程结束问题

    在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。 Pthread_join() 函数的定义:

    1

    int pthread_join(pthread_t th, void **thread_return);

    调用该函数的线程将挂起,等待 th 所表示的线程的结束。 thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表示的线程必须是 joinable 的,即处于非 detached(游离)状态;并且只可以有唯一的一个线程对 th 调用 pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调用将返回错误。

    如果你压根儿不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而来让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为 detached 状态可以通过两种方式来实现。一种是调用 pthread_detach() 函数,可以将线程 th 设置为 detached 状态。

    pthread_detach 函数定义:

    1

    int pthread_detach(pthread_t th);

    另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传入线程创建函数 pthread_create(),这样所创建出来的线程就直接处于 detached 状态。方法如下。

    创建 detach 线程代码实例:

    1

        pthread_t tid;

        pthread_attr_t  attr;

        pthread_attr_init(&attr);

        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

        pthread_create(&tid, &attr, THREAD_FUNCTION, arg);

    总之为了在使用 Pthread 时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调用 pthread_join() 函数来对其进行资源回收。

    总结与补充

    本文以上部分详细介绍了 Linux 的多线程编程的 5 条高效开发经验。另外你也可以考虑尝试其他一些开源类库来进行线程开发。

    1. Boost 库

    Boost 库来自于由 C++ 标准委员会类库工作组成员发起,致力于为 C++ 开发新的类库的 Boost 组织。虽然该库本身并不是针对多线程而产生,但是发展至今,其已提供了比较全面的多线程编程的 API 支持。 Boost 库对于多线程支持的 API 风格上更类似于 Linux 的 Pthread 库,差别在于其将线程,互斥锁,条件等线程开发概念都封装成了 C++ 类,以方便开发调用。 Boost 库目前对跨平台支持的很不错,不仅支持 Windows 和 Linux ,还支持各种商用的 Unix 版本。如果开发者想使用高稳定性的统一线程编程接口减轻跨平台开发的难度, Boost 库将是首选。

    2. ACE

    ACE 全称是 ADAPTIVE Communication Environment,它是一个免费的,开源的,面向对象的工具框架,用以开发并发访问的软件。由于 ACE 最初是面向网络服务端的编程开发,因此对于线程开发的工具库它也能提供很全面的支持。其支持的平台也很全面,包括 Windows,Linux 和各种版本 Unix 。 ACE 的唯一问题是如果仅仅是用于线程编程,其似乎显得有些过于重量级。而且其较复杂的配置也让其部署对初学者而言并非易事。

    展开全文
  • Linux多线程编程入门

    千次阅读 多人点赞 2017-02-23 15:07:19
    线程基本知识\quad进程是资源管理的基本单元,而线程是系统调度的基本单元,线程是操作系统能够进行调度运算的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,...

    1 线程基本知识

    进程是资源管理的基本单元,而线程是系统调度的基本单元,线程是操作系统能够进行调度运算的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    一个进程在某一个时刻只能做一件事情,有了多个控制线程以后,在程序的设计成在某一个时刻能够做不止一件事,每个线程处理独自的任务。

    需要注意的是:即使程序运行在单核处理器上,也能够得到多线程编程模型的好处。处理器的数量并不影响程序结构,所以不管处理器个数多少,程序都可以通过线程得以简化。

    linux操作系统使用符合POSIX线程作为系统标准线程,该POSIX线程标准定义了一整套操作线程的API。

    2. 线程标识

    与进程有一个ID一样,每个线程有一个线程ID,所不同的是,进程ID在整个系统中是唯一的,而线程是依附于进程的,其线程ID只有在所属的进程中才有意义。线程ID用pthread_t表示。

    //pthread_self直接返回调用线程的ID
    include <pthread.h>
    pthread_t pthread_self(void);

    判断两个线程ID的大小是没有任何意义的,但有时可能需要判断两个给定的线程ID是否相等,使用以下接口:

    //pthread_equal如果t1和t2所指定的线程ID相同,返回0;否则返回非0值。
    include <pthread.h>
    int pthread_equal(pthread_t t1, pthread_t t2);

    3. 线程创建

    一个线程的生命周期起始于它被创建的那一刻,创建线程的接口:

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

    函数参数:

    thread(输出参数),由pthread_create在线程创建成功后返回的线程句柄,该句柄在后续操作线程的API中用于标志该新建的线程;
    start_routine(输入参数),新建线程的入口函数;
    arg(输入参数),传递给新线程入口函数的参数;
    attr(输入参数),指定新建线程的属性,如线程栈大小等;如果值为NULL,表示使用系统默认属性。

    函数返回值:

    成功,返回0;
    失败,返回相关错误码。

    需要注意:

    1. 主线程,这是一个进程的初始线程,其入口函数为main函数。
    2. 新线程的运行时机,一个线程被创建之后有可能不会被马上执行,甚至,在创建它的线程结束后还没被执行;也有可能新线程在当前线程从pthread_create前就已经在运行,甚至,在pthread_create前从当前线程返回前新线程就已经执行完毕。

    程序实例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void printids(const char *s){
        pid_t pid;
        pthread_t tid;
        pid = getpid();
        tid = pthread_self();
        printf("%s, pid %lu tid %lu (0x%lx)\n",s,(unsigned long)pid,(unsigned long)tid,
        (unsigned long)tid);
    }
    
    void *thread_func(void *arg){
        printids("new thread: ");
        return ((void*)0);
    }
    int main() {
        int err;
        pthread_t tid;
        err = pthread_create(&tid,NULL,thread_func,NULL);
        if (err != 0) {
            fprintf(stderr,"create thread fail.\n");
        exit(-1); 
        }
        printids("main thread:");
        sleep(1);   
        return 0;
    }

    注意上述的程序中,主线程休眠一秒,如果不休眠,则主线程不休眠,则其可能会退出,这样新线程可能不会被运行,我自己注释掉sleep函数,发现好多次才能让新线程输出。

    编译命令:

    gcc -o thread thread.c -lpthread

    运行结果如下:

    main thread:, pid 889 tid 139846854309696 (0x7f30a212f740)
    new thread: , pid 889 tid 139846845961984 (0x7f30a1939700)

    可以看到两个线程的进程ID是相同的。其共享进程中的资源。

    4. 线程终止

    线程的终止分两种形式:被动终止和主动终止

    被动终止有两种方式:

    1. 线程所在进程终止,任意线程执行exit_Exit或者_exit函数,都会导致进程终止,从而导致依附于该进程的所有线程终止。
    2. 其他线程调用pthread_cancel请求取消该线程。

    主动终止也有两种方式:

    1. 在线程的入口函数中执行return语句,main函数(主线程入口函数)执行return语句会导致进程终止,从而导致依附于该进程的所有线程终止。
    2. 线程调用pthread_exit函数,main函数(主线程入口函数)调用pthread_exit函数, 主线程终止,但如果该进程内还有其他线程存在,进程会继续存在,进程内其他线程继续运行。

    线程终止函数:

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

    线程调用pthread_exit函数会导致该调用线程终止,并且返回由retval指定的内容。
    注意:retval不能指向该线程的栈空间,否则可能成为野指针!

    5. 管理线程的终止

    5.1 线程的连接

    一个线程的终止对于另外一个线程而言是一种异步的事件,有时我们想等待某个ID的线程终止了再去执行某些操作,pthread_join函数为我们提供了这种功能,该功能称为线程的连接:

    include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);

    参数说明:

    thread(输入参数),指定我们希望等待的线程
    retval(输出参数),我们等待的线程终止时的返回值,就是在线程入口函数中return的值或者调用pthread_exit函数的参数

    返回值:

    成功时,返回0
    错误时,返回正数错误码

    当线程X连接线程Y时,如果线程Y仍在运行,则线程X会阻塞直到线程Y终止;如果线程Y在被连接之前已经终止了,那么线程X的连接调用会立即返回。

    连接线程其实还有另外一层意义,一个线程终止后,如果没有人对它进行连接,那么该终止线程占用的资源,系统将无法回收,而该终止线程也会成为僵尸线程。因此,当我们去连接某个线程时,其实也是在告诉系统该终止线程的资源可以回收了。

    注意:对于一个已经被连接过的线程再次执行连接操作, 将会导致无法预知的行为!

    5.2 线程的分离

    有时我们并不在乎某个线程是不是已经终止了,我们只是希望如果某个线程终止了,系统能自动回收掉该终止线程所占用的资源。pthread_detach函数为我们提供了这个功能,该功能称为线程的分离:

    #include <pthread.h>
    int pthread_detach(pthread_t thread);

    默认情况下,一个线程终止了,是需要在被连接后系统才能回收其占有的资源的。如果我们调用pthread_detach函数去分离某个线程,那么该线程终止后系统将自动回收其资源。

    /*
    * 文件名: thread_sample1.c
    * 描述:演示线程基本操作
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    
    /*子线程1入口函数*/
    void *thread_routine1(void *arg)
    {
        fprintf(stdout, "thread1: hello world!\n");
        sleep(1);
        /*子线程1在此退出*/
        return NULL;
    }
    
    /*子线程2入口函数*/
    void *thread_routine2(void *arg)
    {
    
        fprintf(stdout, "thread2: I'm running...\n");
        pthread_t main_thread = (pthread_t)arg;
    
        /*分离自我,不能再被连接*/
        pthread_detach(pthread_self());
    
        /*判断主线程ID与子线程2ID是否相等*/
        if (!pthread_equal(main_thread, pthread_self())) {
            fprintf(stdout, "thread2: main thread id is not equal thread2\n");
        }
    
        /*等待主线程终止*/
        pthread_join(main_thread, NULL);
        fprintf(stdout, "thread2: main thread exit!\n");
    
        fprintf(stdout, "thread2: exit!\n");
        fprintf(stdout, "thread2: process exit!\n");
        /*子线程2在此终止,进程退出*/
        pthread_exit(NULL);
    }
    
    int main(int argc, char *argv[])
    {
    
        /*创建子线程1*/
        pthread_t t1;
        if (pthread_create(&t1, NULL, thread_routine1, NULL)!=0) {
            fprintf(stderr, "create thread fail.\n");
            exit(-1);
        }
        /*等待子线程1终止*/
        pthread_join(t1, NULL);
        fprintf(stdout, "main thread: thread1 terminated!\n\n");
    
        /*创建子线程2,并将主线程ID传递给子线程2*/
        pthread_t t2;
        if (pthread_create(&t2, NULL, thread_routine2, (void *)pthread_self())!=0) {
            fprintf(stderr, "create thread fail.\n");
            exit(-1);
        }
    
        fprintf(stdout, "main thread: sleeping...\n");
        sleep(3);
        /*主线程使用pthread_exit函数终止,进程继续存在*/
        fprintf(stdout, "main thread: exit!\n");
        pthread_exit(NULL);    
    
        fprintf(stdout, "main thread: never reach here!\n");
        return 0;
    }

    最终的执行结果如下:

    thread1: hello world!
    main thread: thread1 terminated!
    
    main thread: sleeping...
    thread2: I'm running...
    thread2: main thread id is not equal thread2
    main thread: exit!
    thread2: main thread exit!
    thread2: exit!
    thread2: process exit!
    

    参考资料:

    1. https://www.shiyanlou.com/courses/731
    2. 《unix环境高级编程》
    展开全文
  • Linux多线程编程(一)---多线程基本编程

    万次阅读 多人点赞 2020-03-19 13:20:49
    线程是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。...一个进程可以有很多线程,每个线程并行执行不同的任务。

    线程概念

        线程是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该系统中的全部系统资源,比如文件描述符和信号处理等。一个进程可以有很多线程,每个线程并行执行不同的任务。

    线程与进程比较

       ①  和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护其代码段、堆栈段和数据段,这种多任务工作方式的代价非常“昂贵”。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需要时间也远远小于进程间切换所需要的时间。

       ②  线程间方便的通信机制。对不同进程来说它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行。这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,不仅方便,而且快捷。

    线程基本编程

       Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread。例如:gcc  filename  -lpthread。注意,这里要讲的线程相关操作都是用户空间中的线程的操作。

       线程创建:创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。

      

      线程退出:在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。

      

      线程等待:由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

      

      线程取消:前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。

      

      线程标识符获取:获取调用线程的标识ID。

      

      线程清除:线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都回存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。

      从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

      

       

    实验1

       功能:使用pthread_create()函数创建线程的实例

       代码:thread_create.c文件

        

         

        编译:使用命令:gcc thread_create.c -o thread_create -lpthread编译,注意不要忘了加 -lpthread,否则会出现如下的错误

    /tmp/ccjfZIN3.o: In function `main':
    thread_create.c:(.text+0x8b): undefined reference to `pthread_create'
    thread_create.c:(.text+0xc0): undefined reference to `pthread_create'
    thread_create.c:(.text+0xeb): undefined reference to `pthread_join'
    thread_create.c:(.text+0xfc): undefined reference to `pthread_join'
    collect2: ld returned 1 exit status

       执行:

       

    实验2

      功能:使用pthread_exit()函数退出线程的举例

      代码:thread_exit.c文件

         

      编译:gcc thread_exit.c -o thread_exit -lpthread

      执行:./thread_exit

      

    实验3

      功能:用pthread_join()实现线程等待。

      代码:thread_join.c文件

      

      编译:gcc thread_join.c -o thread_join -lpthread  

      执行:./thread_join

      

     可以看出,pthread_join()等到线程结束后,程序才继续执行。

    实验4

      功能:使用pthread_self()获取线程ID

      代码:thread_id.c文件

      

      编译:gcc thread_id.c -o thread_id -lpthread

      执行:./thread_id

      

    实验5

      功能:线程清理函数的使用

      代码:thread_clean.c

      

      

       

       注意,在编写的代码的时候,自己修改一下传递的参数和clean_pop函数的参数,相信你会更有收获。

      编译:gcc thread_clean.c -o thread_clean -lpthread

      执行:./thread_clean

       

    实验6

      功能:本实验创建了3个进程,为了更好的描述线程之间的并行执行,让3个线程共用同一个执行函数。每个线程都有5次循环(可以看成5个小任务),每次循环之间会随机等待1~10s的时间,意义在于模拟每个任务的到达时间是随机的,并没有任何特定的规律。

      代码:thread.c文件,该代码文件点此下载

      

      

      编译:gcc thread.c -o thread -lpthread

      执行:./thread

        

      从实验结果可以看出,线程执行的顺序杂乱无章的,看着就头疼,下一节就利用线程之间的同步与互斥机制处理此文件http://blog.csdn.net/mybelief321/article/details/9390707

     

     

    展开全文
  • linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。 #include &lt;pthread.h&gt; int pthread_create(pthread_t *restrict tidp,  const pthread_attr_t *restrict attr, ...

    linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。

    #include <pthread.h>

    int pthread_create(pthread_t *restrict tidp,
                       const pthread_attr_t *restrict attr,
                       void *(*start_rtn)(void), 
                       void *restrict arg);
    Returns: 0 if OK, error number on failure

    C99 中新增加了 restrict 修饰的指针: 由 restrict 修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的指针表达式中。 由 restrict 修饰的指针主要用于函数形参,或指向由 malloc() 分配的内存空间。restrict 数据类型不改变程序的语义。 编译器能通过作出 restrict 修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。

    第一个参数为指向线程标识符的指针。
    第二个参数用来设置线程属性。
    第三个参数是线程运行函数的起始地址。
    最后一个参数是运行函数的参数。

    下面这个程序中,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

    #include<stdio.h>
    #include<pthread.h>
    #include<string.h>
    #include<sys/types.h>
    #include<unistd.h>
    pthread_t ntid;
     
    void printids(const char *s)
    {
        pid_t pid;
        pthread_t tid;
     
        pid = getpid();
        tid = pthread_self();
        printf("%s pid %u tid %u (0x%x)\n",s,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid);
     
    }
     
    void *thread(void *arg)
    {
        printids("new thread:");
        return ((void *)0);
    }
     
    int main()
    {
        int temp
        if((temp=pthread_create(&ntid,NULL,thread,NULL))!= 0)
        {
            printf("can't create thread: %s\n",strerror(temp));
            return 1;
         }
         printids("main thread:");
         sleep(1);
         return 0;
    }

    把APUE2上的一个程序修改一下,然后编译。
    结果报错:
    pthread.c:(.text+0x85):对‘pthread_create’未定义的引用

    由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:
    gcc -o pthread -lpthread pthread.c

    这是一个关于Posix线程编程的专栏。作者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第一篇将向您讲述线程的创建与取消。

    1.1 线程与进程
    相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。

    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

    1.2 创建线程
    POSIX通过pthread_create()函数创建线程,API定义如下:

    int   pthread_create(pthread_t   *   thread, pthread_attr_t * attr,
    void * (*start_routine)(void *), void * arg)
    与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性(见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。
    1.3 线程创建属性
    pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:

    __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到 PTHREAD_CREATE_JOINABLE状态。

    __schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过 pthread_setschedparam()来改变。

    __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR 或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。

    __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。

    __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

    pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。

    为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_attr_set---函数。

    1.4 线程创建的Linux实现
    我们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork (),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由 __clone()传入。

    Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。

    2.1 线程取消的定义
    一般情况下,线程在其主体函数退出的时候会自动终止,但同时也可以因为接收到另一个线程发来的终止(取消)请求而强制终止。

    2.2 线程取消的语义
    线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

    线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

    2.3 取消点
    根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用 pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

    pthread_testcancel();
         retcode = read(fd, buffer, length);
         pthread_testcancel();
    2.4 程序设计方面的考虑
    如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

    2.5 与线程取消相关的pthread函数
    int pthread_cancel(pthread_t thread)
    发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

    int pthread_setcancelstate(int state, int *oldstate)
    设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。

    int pthread_setcanceltype(int type, int *oldtype)
    设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。

    void pthread_testcancel(void)
    检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。
    --------------------- 
    作者:koches 
    来源:CSDN 
    原文:https://blog.csdn.net/koches/article/details/7624400?utm_source=copy 
    版权声明:本文为博主原创文章,转载请附上博文链接!

    展开全文
  • Linux C 多线程编程总结

    千次阅读 2016-11-09 21:42:54
    简单的多线程编程   Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统...
  • linux 多线程串口编程总结

    千次阅读 2019-04-04 10:01:16
    最近在玩DJI M100,调用API获取GPS位置时发现高程定位完全是错的(负的几百多米),查了一下文档说高程数据是由气压计得到的,而飞行控制时又需要比较可靠的高度信息,于是乎决定用上我们实验室的搭载Ublox芯片的...
  • Linux多线程编程

    2020-09-11 00:41:22
    开篇来一首音乐放松一下,来自歌手简弘亦的:就算我唱遍所有情歌。1、线程与多线程的定义线程存在于进程当中,是操作系统调度执行的最小单位。说通俗点线程就是干活,多线程也就是同时可以干不同的活...
  • Linux多线程(2)

    千次阅读 2019-05-17 18:26:52
    线程的知识点太,太重要,所以分成三部分进行总结学习 线程安全 线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。 线程对临界...
  • Linux 多线程编程(三)

    千次阅读 2018-06-02 09:46:36
    1 线程安全多线程编程环境中,多个线程同时调用某些函数可能会产生错误结果,这些函数称为非线程安全函数。如果库函数能够在多个线程中同时执行并且不会互相干扰,那么这个库函数就是线程安全( thread-safe)函数 ...
  • Linux多线程并发总结

    千次阅读 2017-09-28 10:34:42
    总结thread async future等并发的技术
  • Linux线程详解

    千次阅读 2019-06-03 12:06:33
    (并发是指同一时刻只能有一条指令执行,但个进程指令被快速轮换执行,使得在宏观上有个进程被同时执行的效果--宏观上并行,针对单核处理器) 互斥:进程间相互排斥的使用临界资源的现象,就叫互斥。 ...
  • linux多线程编程是指基于Linux操作系统下的多线程编程,包括多任务程序的设计,并发程序设计,网络程序设计,数据共享等。Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用...
  • 我推荐了《Linux 多线程服务器端编程——使用 muduo C++ 网络库》给他,他在网上书店看了以后问我为什么推荐这么厚一本书给他,正好这本书我已经早就看完了,一直也想写篇“书评”,就在这里多扯几句。其实实在算不...
  • linux多线程编程是指基于Linux操作系统下的多线程编程,包括多任务程序的设计,并发程序设计,网络程序设计,数据共享等。Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用...
  • linux 多线程并发服务器(TCP)

    千次阅读 2018-08-27 09:24:10
    linux 多线程并发服务器(TCP) ​ 所谓多线程并发服务器就是基于线程,每个客户端来了创建一个线程,由线程去处理客户端的请求。相对于多线程服务器来说,多进程服务器在创建进程时要消耗较大的系统资源,所以我们...
  • linux 多线程的实现的基本原理

    千次阅读 2016-03-30 16:49:44
    1. linux 多线程的基本概念  linux 是多用户、多任务的并发执行;所谓的并发是通过多进程、多线程来实现的;  1). 其中多进程有3种方式: 单机多实例(机器复用,一台机器启动多个进程,每个进程干自己的事情)多...
  • Linux 多线程 pthread库初探

    千次阅读 2018-02-13 20:01:37
    Linux 多线程 pthread库用法(一) Linux 多线程编程基介绍 Linux 线程有时候也叫 Light Weight Process LWP 轻量级线程,是进程的一个执行流,有自己的执行栈,是操作系统调度的最小单位。 多线程优势在于切换...
  • linux多线程编程——同步与互斥

    千次阅读 2015-12-16 18:48:15
    我们在前面文章中已经分析了多线程VS多进程,也分析了线程的使用,现在我们来讲解一下linux多线程编程之同步与互斥。 现在,我们不管究竟是多线程好还是多进程好,先讲解一下,为什么要使用多线程? 一、 为什么要用...
  • Linux 多线程编程(一)

    千次阅读 2016-09-28 21:53:54
    Linux 多线程编程   线程(Thread)已被许多操作系统所支持,包括Windows/NT ,Linux 以前的多线程其实是多进程,而现在意味着一个进程中有多个线程   使用多线程的原因(多线程的优点): 1.“节省”,启动...
  • Linux 多线程编程线程分类线程按照其调度者可以分为用户级线程和内核级线程两种。内核级线程在一个系统上实现线程模型的方式有好几种,因内核和用户空间提供的支持而有一定程度的级别差异。最简单的模型是在内核为...
1 2 3 4 5 ... 20
收藏数 349,283
精华内容 139,713
关键字:

linux多线程