Linux线程库

线程引言

Linux上最有名的线程库是LinuxThreads和NPTL,它们都是采用1:1的方式实现的。现代Linux上模式运行的线程库是NPTL(Native POSIX Thread Library)。可以使用

getconf GNU_LIBPTHREAD_VERSION

查看当前系统上使用的线程库。

从Linux内核2.6开始,提供了真正的内核线程。新的NPTL线程库应运而生。相对于LinuxThreads,NPTL的主要优势在于:

  1. 内核线程不再是一个进程,这就避免了很多进程模拟内核线程导致的语义问题。
  2. 抛弃了管理线程,终止线程、回收线程堆栈等工作可以由内核来完成。
  3. 由于不存在管理线程,所以一个进程的线程可以运行在不同的CPU上,从而充分利用了多处理器系统的优势。
  4. 线程的同步由内核来完成,属于不同进程的线程之间也能共享互斥锁,因此可实现跨进程的线程同步。

 创建线程和结束线程

创建

在pthread.h的头文件中,创建线程的函数pthread_create定义如下:

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

thread参数是新线程的标识符,attr参数用于设置新线程的属性。给他传递NULL表示使用默认线程属性。start_routine和arg分别为回调函数以及传给回调函数的参数。

退出

线程一旦被创建好,内核就可以调度内核线程来执行start_routine回调函数了,线程函数在结束时最好屌用退出函数,以确保安全,干净的删除。

void thread_exit(void* retval);

pthread_exit函数通过retval参数向线程的回收者传递其退出信息。它执行完之后不会返回到调用者,并且不会失败。

回收线程

一个进程中的所有线程都可以使用pthread_join函数来回收其他线程(线程是可回收的情况下)

,即等待其他线程结束。pthread_join定义如下:

pthread_join(pthread_t thread, void** retval);

thread参数是目标线程的标识符,retval参数则是目标线程返回的退出信息。该函数会一直阻塞,直到背回收的线程结束为止。该函数成功时返回0,失败则返回错误码。

终止线程

int pthread_cancel(pthread_t thread)

thread参数是目标线程的标识符,回收成功返回0,失败返回错误码。

接收到取消请求的目标线程可以决定是否允许被取消以及如何取消,这分别由如下两个函数完成:

#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type,int* oldtype);

 state有两个可选值:

  • PTHREAD_CANCEL_ENABLE,允许线程被取消。他是线程被创建时的默认取消状态。
  • PTHREAD_CANCEL_DISABLE,禁止线程被取消。这种情况下,如果一个线程收到取消请求,则它会将请求挂起,知道该线程允许被取消。 

 type参数同样有两个可选值:

  • PTHREAD_CANCEL_ASYNCHRONOUS,线程随时都可以被取消。它将使得接受到的取消请求目标线程立即采取行动。
  • TRHREAD_CANCEL_DEFERRED,允许目标线程推迟行动,知道它调用取消点函数中的一个:pthread_join,pthread_testcancel,pthread_cond_wait,pthread_cond_timedwait,sem_wait和sigwait。根据POSIX标准,其他可能阻塞的系统调用,比如read,wait可以成为取消点。不过为了安全起见,我们最好在可能会被取消的代码中调用pthread_testcancel函数以设置取消点。

pthread_setcancelstate和pthread_setcanceltype成功时返回0,失败返回错误码。 

POSIX信号量

 和多进程程序一样,多线程程序也必须考虑同步问题。pthread_join可以看作一种简单的线程同步方式,不过很显然,他无法高效地实现复杂的同步需求,比如控制对共享资源的独占访问,或者在某个条件满足后唤醒一个线程。接下来我们讨论3中专门用于线程同步机制:POSIX信号量、互斥量和条件变量

POSIX信号量函数的名字都以sem_开头,并不想大多数线程函数那样以pthread_开头,常用的POSIX信号量函数是下面5个:

#include <semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int val);
int sem_destory(sem_t* sem);
int sem_wait(sem_t* sem);
int sem_trywait(sem_t* sem);
int sem_post(sem_t* sem);

sem_init用于初始化一个未命名的信号量,pshared参数制定信号量的类型,如果其值为0,就代表这个信号量是当前进程局部信号量,否则该信号量就可以在多个进程之间共享。val制定信号量的初始值。

sem_destory销毁信号量。

sem_wait、sem_trywait函数以原子的操作方式将信号量的值-1.如果信号量的值为0,则sem_wait将被阻塞,直到这个信号量具有非0值。sem_trywait是sem_wait的非阻塞版本,当信号量为非0时,sem_trywait对信号量执行-1,当信号量的值为0时,他将返回-1,并设置errno为EAGAIN

sem_post函数将以原子的方式将信号量的值+1,当信号量的val>0时,其他正在调用sem_wait等待信号量的线程将被唤醒。

互斥锁

互斥锁(也成互斥量)可以用于保护关键代码,以确保其独占的访问,这点有点像一个二进制信号量,当进入关键代码时,我们需要获得互斥锁并将其加锁,这等价于二进制信号量的P操作,当离开关键代码时我们需要对互斥锁解锁,以唤醒其他等待该互斥的线程,这等价于二进制信号量的V操作。

POSIX互斥锁的相关函数只要有5个:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

互斥锁的属性

pthread_mutexattr_t结构体定义了一套完整的互斥锁属性。线程库提供了一系列函数来操作pthread_mutexattr_t类型的变量,以方便我们获取和设置互斥锁属性,下面列出一些主要的函数。

#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
//获取和设置互斥锁的pshared
int pthread_mutexattr_getpshared(const pthread_mutexattr_t* attr, int* pshared);
int pthread_mutexattr_setpshared(const pthread_mutexattr_t* attr, int pshared);
//获取和设置互斥锁的type属性
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type);
int pthread_mutexattr_settype(const pthread_mutexattr_t* attr, int type);

两种常用的属性:pshared,type

pshared:

PTHREAD_PROCESS_SHARED--互斥锁可以被跨进程共享

PTHREAD_PROCESS_PRIVATE--互斥锁只能被初始化线程隶属同一个进程的线程共享。

type:

PTHREAD_MUTEX_NORMAL--普通锁,默认锁

PTHREAD_MUTEX_ERRORCHECK--检测锁

PTHRED_MUTEX_RECURSIVE--嵌套锁

PTHREAD_MUTEX_DEFAULT--默认锁

线程死锁举类

#ifndef TEST_FFMPEG_SISUO_H
#define TEST_FFMPEG_SISUO_H
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex_a;
pthread_mutex_t mutex_b;

void* another(void* arg){
    pthread_mutex_lock(&mutex_b);
    printf("in child thread, got mutex b ,waiting for mutex a\n");
    sleep(2);
    pthread_mutex_lock(&mutex_a);
    pthread_mutex_unlock(&mutex_a);
    pthread_mutex_unlock(&mutex_b);
    pthread_exit(nullptr);
}

void test_sisuo(){
    pthread_t id;
    pthread_mutex_init(&mutex_a,NULL);
    pthread_mutex_init(&mutex_b,NULL);
    pthread_create(&id, NULL, another, NULL);
    pthread_mutex_lock(&mutex_a);
    printf("in main thread, got mutex a , waiting for mutex b\n");
    sleep(2);
    pthread_mutex_lock(&mutex_b);
    pthread_mutex_unlock(&mutex_b);
    pthread_mutex_unlock(&mutex_a);

    //线程同步
    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex_a);
    pthread_mutex_destroy(&mutex_b);
}

#endif //TEST_FFMPEG_SISUO_H

当执行时,主线程拿到a的锁,开启的线程拿到了b,主线程再申请锁时,锁被开启的子线程拿到了,同步等待,子线程申请a的锁时,又被主线程锁上了,两个线程互相拿到了对方的锁。

用一个形象的例子说明一下,一个人需要使用同时拿到簸箕和扫帚才能工作,假设两个人分别拿到了苕帚和簸箕,两个人同时都准备工作时,都向对方要对方的工具,这就造成了两个都无法工作,造成了死锁。

条件变量

互斥锁是用于同步线程对共享数据的访问,条件变量则是用于在线程之间同步共享数据的值。条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。

#include <pthead.h>
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destory(pthread_cond_t* cond);
//以广播方式唤醒所有等待目标条件变量的线程
int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond)
// lock->unlock->wait->..lock..unlock
int pthread_cond_wait(pthread_cond_t* cond);

生产者消费者例子

//
// Created by wenfan on 2021/6/29.
//

#ifndef TEST_FFMPEG_CONDITION_VARIABLE_H
#define TEST_FFMPEG_CONDITION_VARIABLE_H
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

typedef struct node{
    int num;
    struct node* next;
}node_t;

node_t* head = nullptr;
node_t* tmp = nullptr;

void* producer(void* arg){
    while (true){
        pthread_mutex_lock(&mutex);
        tmp = new node;
        int rand_num = rand()%100;
        if(rand_num == 100)
            break;
        tmp->next = head;
        tmp->num = rand_num;
        head = tmp;
        std::cout << "生产:" << rand_num << std::endl;
        pthread_mutex_unlock(&mutex);
        //通知
        pthread_cond_signal(&cond);
        sleep(10);
    }

}

void* consumer(void* arg){
    while (true){
        pthread_mutex_lock(&mutex);
        if(head == nullptr){
            pthread_cond_wait(&cond, &mutex);
            std::cout << "pthread_cond_wait" << std::endl;
        }

        tmp = head;
        head = tmp->next;
        std::cout <<  "消费:"  << tmp->num << std::endl;
        delete tmp;
        tmp = nullptr;
        pthread_mutex_unlock(&mutex);
        sleep(rand()%3+1);
    }
}


void consumer_producer_test(){
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);

    pthread_t consumer_t, producer_t;
    pthread_create(&consumer_t, nullptr, consumer, nullptr);
    pthread_create(&producer_t, nullptr, producer, nullptr);
    pthread_join(consumer_t, nullptr);
    pthread_join(producer_t, nullptr);

}

#endif //TEST_FFMPEG_CONDITION_VARIABLE_H

在这个例子中,当头节点head==nullptr时,释放锁,消费者consumer尽心等待,然后生产者producer获得了锁,生产区块,然后使用pthread_cond_signal进行通知,此时的消费者往下执行(又获得了锁)。

相较于mutex而言,条件变量可以减少无意义的竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

顾文繁

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值