Linux多线程编程---线程间同步(互斥锁、条件变量、信号量和读写锁)

2023-11-11

本篇博文转自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 的唯一问题是如果仅仅是用于线程编程,其似乎显得有些过于重量级。而且其较复杂的配置也让其部署对初学者而言并非易事。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux多线程编程---线程间同步(互斥锁、条件变量、信号量和读写锁) 的相关文章

  • Python-Scrapy安装

    描述 安装爬虫框架Scrapy 基本使用 知识点总结 目录 一 Scrapy安装 1 1 scrapy是什么 1 2 安装环境 1 3 步骤安装 1 4 测试是否安装成功 1 4 第三方库 一 Scrapy安装 1 1 scrapy是什么

随机推荐

  • .NetCore之log4net的使用

    1 首先下载log4ne的包 2 添加配置文件log4net config
  • CodeWhisperer的注册、安装与使用指南。

    CodeWhisperer是一款功能强大的代码编辑器 它支持多种编程语言 并提供了许多有用的编辑和调试功能 下面是使用CodeWhisperer 需要进行注册 安装和使用 下面是详细的指南 一 注册CodeWhisperer 在使用Code
  • [物联网方案-3]:井下作业需要检测气体类型与常见的传感器

    井下作业的主要职业危险是缺氧窒息 一氧化碳 硫化氢中毒和可燃气爆炸 其中最常见的现象是硫化氢中毒 复合式四合一气体检测仪能及时检测出井内有毒有害气体的浓度 并能自动报警 1 一氧化碳中毒 一氧化碳中毒 呼吸浅而急促 失去知觉时面颊及身上有红
  • TEASER

    本文是对文章 TEASER Fast and Certifiable Point Cloud Registration 的解读 摘要 这篇文章提出了第一个快速且可证明的算法 用于存在大量外点对应的情况下两组3D点的配准 可证明的算法尝试求解
  • Spring Boot 安全的最佳实践

    Spring Boot 安全的最佳实践 在 Web 应用程序中 安全性是至关重要的 恶意攻击者可能会利用您的应用程序中的弱点来获取敏感信息或者窃取用户数据 为了保护您的应用程序和用户数据 您需要遵循一些最佳实践 本文将介绍 Spring B
  • C#简单的制作一个窗体应用

    废话少说下面先看效果 登陆管理员 注册账号和管理账号 修改密码界面 功能界面 功能一 连接中国移动物联网平台检测温湿度 输入wendu可以查询 功能二 查看图片 功能三 读写通知管理员可以写但普通成员只可以读 功能四 计算工具 可以计算三角
  • 一百四十六、Xmanager——Xmanager5连接Xshell7并控制服务器桌面

    一 目的 由于kettle安装在Linux上 Xshell启动后需要Xmanager 而Xmanager7版本受限 没有免费版 所以就用Xmanager5去连接Xshell7 二 Xmanager5安装包来源 一 注册码 注册码 10121
  • Linux——TCP传输可靠性

    TCP传输可靠性的前提条件 重传机制 针对数据包丢失或者出现定时器超时 确认应答 停止等待协议 发送之后等待收到应答 序列号 针对数据包到达接收端主机顺序乱掉 流量控制 针对避免网络拥堵时候 针对高效传输数据包的流动窗口的控制 拥塞控制 针
  • qt中Graphic中 View的坐标和Scene的坐标不匹配的问题

    在QT中使用QGraphicView 和QGraphicsSce 时 会遇到一个这样一个问题 Scene中绘制图的坐标与View显示坐标不符 例如 直接在scene中添加直线 并且设置起点是0 0 但是我们会发现他的起点并不是0 0 如下图
  • 【数据结构入门】队列(Queue)详解(定义、销毁、入队、出队等)

    文章目录 1 前言 1 队列的概念 2 队列的结构 2 队列的实现 链式结构 1 队列的定义 2 队列的初始化 3 队列的销毁 4 入队 尾插 5 出队 头删 6 获取队列元素个数 7 获取队头元素 8 获取队尾元素 9 检查队列是否为空
  • Qt 获取程序所在路径等特殊路径的方法

    目录 经常我们的程序中需要访问一些特殊的路径 比如程序所在的路径 用户目录路径 临时文件夹等 在 Qt 中实现这几个功能所用的方法虽然都不难 但是各不相同 每次用到时还要现去查 很不方便 因此就写了这篇博客 把这几种需求的实现方式总结了一下
  • 2022春招前端最新面试题分享(牧原股份)

    牧原股份 公司及岗位信息 公司 牧原股份 岗位 前端开发工程师 地点 河南 薪资 12k 16k 面试结果 一面后暂时未接到通知 一面HR技术群面 2022 04 19 自我介绍 期望薪资 你认为你为什么值这个钱 JS常用的数据类型 分辨引
  • Spring Boot(一)

    什么是Spring Boot Spring Boot 是由 Pivotal 团队提供的全新框架 其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程 该框架使用了特定的方式来进行配置 从而使开发人员不再需要定义样板化的配置 Sp
  • UG10.0安装方法及步骤

    1 右击软件压缩包 选择解压到 UG10 64bit 选项 2 打开破解文件夹下的NX10 0 JAVA X64位exe文件 3 然后点下一步 4 下一步 5 选择安装目录 默认安装在 C Program Files Java jdk18
  • 面试华为,花了2个月才上岸,真的难呀····

    花2个月时间面试一家公司 你们觉得值吗 背景介绍 美本计算机专业 代码能力一般 之前有过两段实习以及一个学校项目经历 第一份实习是大二暑期在深圳的一家互联网公司做前端开发 第二份实习由于大三暑假回国的时间比较短 小于两个月 于是找的实习是在
  • 最大化期望算法(EM)详解

    我们知道最大似然估计的根本目的是根据抽样的到的样本 即数据 反推出最有可能的分布参数 即模型 这是一个非常典型的机器学习的思想 所以在很多领域最大似然估计有着极为广泛的应用 然而 如果已知的数据中含有某些无法观测的隐藏变量时 直接使用最大似
  • 手写Vue个人组件库———fl-Cascader

    您好 如果喜欢我的文章 可以关注我的公众号 量子前端 将不定期关注推送前端好文 接上文 封装了个人Vue组件库的Cascader级联选择器 源码附在了文章末尾 如下是文档使用说明 Cascader 级联选择器 当一个数据集合有清晰的层级结构
  • 滑动窗口的最大值java

    题目描述 给定一个数组和滑动窗口的大小 找出所有滑动窗口里数值的最大值 例如 如果输入数组 2 3 4 2 6 2 5 1 及滑动窗口的大小3 那么一共存在6个滑动窗口 他们的最大值分别为 4 4 6 6 6 5 针对数组 2 3 4 2
  • 分布式任务调度平台XXL-JOB(超详细喂嘴版)

    1 XXL JOB框架 1 0 环境 Maven3 Jdk1 8 Mysql5 7 1 1 下载源码 https github com xuxueli xxl job 1 2 中文文档 https www xuxueli com xxl j
  • Linux多线程编程---线程间同步(互斥锁、条件变量、信号量和读写锁)

    本篇博文转自http zhangxiaoya github io 2015 05 15 multi thread of c program language on linux Linux下提供了多种方式来处理线程同步 最常用的是互斥锁 条件