Linux线程pthread、及互斥锁、条件

Linux线程

Linux线程是操作系统内核中对线程的实现,它是轻量级的进程。Linux线程可以在多核处理器上并发执行,提高程序的运行效率。Linux线程支持多种线程同步机制,如互斥量,条件变量,读写锁等。

在Linux系统中,线程是通过clone系统调用实现的,这个系统调用可以复制当前进程的所有资源,并创建一个新的线程。新线程和原来的线程共享同一地址空间,但是有自己的线程上下文和线程ID。

通过pthread库可以方便的使用Linux线程,这个库提供了很多函数来创建和管理线程,还有线程同步和线程间通信的机制等。

进程与线程的区别

进程和线程都是操作系统中资源分配和调度的基本单位。

进程是资源分配和调度的最大单位,它拥有独立的地址空间和资源,如文件描述符和系统资源。每个进程都有自己的程序计数器,寄存器和栈空间。进程之间的资源独立,相互之间不会影响。

线程是资源调度的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。线程有自己的程序计数器,寄存器和栈空间,但是它和同属于一个进程的其它线程共享进程的其它资源,如地址空间和全局变量。

因此,线程比进程更轻量级,创建和管理线程的代价比进程小,但是线程之间的资源共享会带来同步和通信上的问题。

pthread.h

pthread.h是pthread库的头文件,它定义了线程相关的类型和函数。这个库提供了在C语言中创建和管理线程的功能。

其中一些常用的函数有:
pthread_create():创建一个新线程
pthread_join():等待线程结束
pthread_exit():终止调用线程
pthread_cancel():取消一个线程
pthread_self():获取当前线程ID
pthread_equal():比较两个线程ID是否相同
pthread_mutex_lock():获取互斥锁
pthread_mutex_unlock():释放互斥锁
pthread_cond_wait():等待条件变量
pthread_cond_signal():唤醒等待条件变量的线程
使用pthread库创建线程比较简单,可以解决多线程编程中的问题。

函数原型及使用方法:

pthread_create

pthread_createpthread库中用来创建新线程的函数。它的原型是:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

thread: 指向pthread_t类型的指针,用于存储新线程的线程ID。
attr: 指向pthread_attr_t类型的指针,用于设置线程的属性,如线程的分离属性等,如果为NULL,则使用默认属性
start_routine: 指向线程入口函数的指针,该函数将在新线程中执行
arg: 传递给线程入口函数的参数,它可以是任意类型的指针。

在调用pthread_create函数后,操作系统会创建一个新线程,并在新线程中执行start_routine函数,同时在主线程继续执行。

新线程和主线程是并发执行的

pthread_create函数返回0表示成功创建了新线程,返回非0的值表示创建线程失败。

例如:

void* thread_function(void* arg) {
    //线程执行的代码
    return NULL;
}

int main() {
    pthread_t thread_id;
    int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("Error creating thread\n");
        return -1;
    }
    //主线程的代码
    return 0;
}

pthread_join

pthread_joinpthread库中用来等待线程结束的函数。它的原型是:

int pthread_join(pthread_t thread, void **retval);

thread: 要等待结束的线程ID
retval: 用于存储线程结束时返回值的指针

调用pthread_join函数会使调用线程阻塞,直到thread线程结束为止。如果thread线程已经结束,pthread_join函数会立即返回。

如果我们需要等待所有线程结束后才继续执行主线程的代码,可以在创建线程后立即调用pthread_join等待线程结束。

例如:

void* thread_function(void* arg) {
    //线程执行的代码
    return NULL;
}

int main() {
    pthread_t thread_id;
    int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("Error creating thread\n");
        return -1;
    }
    ret = pthread_join(thread_id, NULL);
    if (ret != 0) {
        printf("Error joining thread\n");
        return -1;
    }
    //主线程的代码
    return 0;
}

上面代码中,主线程调用pthread_create函数创建了新线程,并调用pthread_join等待新线程结束。

返回0表示成功,返回非0值表示失败。

pthread_exit

pthread_exitpthread库中用来终止线程并返回结果的函数。它的原型是:

void pthread_exit(void *retval);

retval: 线程的返回值,可以是任意类型的指针
pthread_exit函数用来终止调用它的线程并返回retval作为线程的结束状态。

当线程调用pthread_exit函数时,它会立即终止,不会再执行后面的代码。如果有多个线程等待该线程结束,它们会立即返回。

如果线程没有显式调用pthread_exit函数,那么在线程的入口函数执行完毕后,线程也会终止并返回。

例如:

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

void* thread_function(void* arg) {
    int* result = (int*)malloc(sizeof(int)); // 分配内存
    *result = 10;
    pthread_exit(result); // 返回结果
}

int main() {
    pthread_t thread_id;
    int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("Error creating thread\n");
        return -1;
    }
    void* thread_result;
    ret = pthread_join(thread_id, &thread_result);
    if (ret != 0) {
        printf("Error joining thread\n");
        return -1;
    }
    int* result = (int*) thread_result;
    printf("Thread returned: %d\n", *result);
    free(result); //释放内存
    return 0;
}


在这个例子中,主线程调用pthread_create创建了新线程并传入了thread_function作为入口函数。在新线程中,thread_function函数分配了一个int类型的内存并将其赋值为10,最后使用pthread_exit函数返回该内存地址。

主线程在调用pthread_join等待新线程结束,并将线程返回值存储在thread_result中。最后,主线程打印返回值并释放内存。

这样做的好处是,我们可以在线程结束时获取其返回值,并在主线程中使用该值进行后续的操作。

另外,为了避免内存泄漏,在使用完返回值后,需要调用free函数释放内存。

pthread_cancel

pthread_cancelpthread库中用来取消线程的函数。它的原型是:

int pthread_cancel(pthread_t thread);

thread: 要取消的线程ID
pthread_cancel函数用来取消thread线程。取消线程后,线程的执行会立即终止,不会再执行后面的代码。如果有多个线程等待该线程结束,它们会立即返回。

需要注意,调用pthread_cancel并不会立即终止线程,而是在线程下一次取消点处终止线程。取消点是指线程执行到特定位置时会检查是否被取消的位置。

在Linux下,如果线程调用了cancel-safe函数(如pthread_cond_wait, pthread_join)将会检查取消信号,如果检测到信号就会终止线程。

pthread_cancel函数返回0表示成功取消了线程,返回非0的值表示取消线程失败。

例如:

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

void* thread_function(void* arg) {
    int i;
    // 线程执行的代码
    for (i = 0; i < 10; i++) {
        printf("线程运行中: %d\n", i);
        sleep(1); // 每隔一秒输出一次
    }
    return NULL;
}

int main() {
    pthread_t thread_id;
    int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("创建线程失败\n");
        return -1;
    }
    sleep(5); // 主线程等待5秒
    ret = pthread_cancel(thread_id);
    if (ret != 0) {
        printf("取消线程失败\n");
        return -1;
    }
    ret = pthread_join(thread_id, NULL);
    if (ret != 0) {
        printf("等待线程结束失败\n");
        return -1;
    }
    printf("线程已取消\n");
    return 0;
}

上面的代码中,主线程调用pthread_create函数创建了新线程,新线程执行thread_function函数。在函数中输出"线程运行中: i",每隔1秒输出一次。主线程等待5秒后,调用pthread_cancel取消新线程。最后调用pthread_join等待新线程结束并打印"线程已取消"。

需要注意的是,在上面的例子中,由于新线程中没有调用取消点函数(如pthread_testcancel),所以线程需要等待输出完所有的"线程运行中: i"后才会被取消。如果需要在调用pthread_cancel后立即终止线程,可以在线程函数中调用pthread_testcancel函数。

pthread_self

pthread_self是pthread库中用来获取当前线程ID的函数。它的原型是:

pthread_t pthread_self(void);

pthread_self函数返回当前线程的线程ID,返回值类型是pthread_t,是一个整数。

例如:

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

void* thread_function(void* arg) {
    pthread_t thread_id = pthread_self();
    printf("Thread ID: %lu\n", thread_id);
    return NULL;
}

int main() {
    pthread_t thread_id;
    int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("Error creating thread\n");
        return -1;
    }
    pthread_join(thread_id, NULL);
    return 0;
}

上面的代码中,主线程调用pthread_create函数创建了新线程,新线程执行thread_function函数。在函数中调用pthread_self函数获取当前线程的线程ID并打印出来。主线程调用pthread_join等待新线程结束。

可以看到,pthread_self函数可以在任意线程中调用,获取当前线程的线程ID。这在线程间通信中经常用到。

需要注意的是,pthread_self返回的线程ID是一个整数,可能不是进程中的进程ID。进程ID和线程ID是不同的。

pthread_equal

pthread_equalpthread库中用来比较两个线程ID是否相等的函数。它的原型是:

int pthread_equal(pthread_t t1, pthread_t t2);

t1: 第一个线程ID
t2: 第二个线程ID
pthread_equal函数比较t1和t2两个线程ID是否相等,相等返回非0值,不相等返回0。

例如:

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

void* thread_function(void* arg) {
    pthread_t thread_id = pthread_self();
    printf("线程ID: %lu\n", thread_id);
    return NULL;
}

int main() {
    pthread_t thread1_id, thread2_id;
    int ret = pthread_create(&thread1_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("创建线程1失败\n");
        return -1;
    }
    ret = pthread_create(&thread2_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("创建线程2失败\n");
        return -1;
    }
    int equal = pthread_equal(thread1_id, thread2_id);
    if (equal != 0) {
        printf("线程1和线程2相等\n");
    } else {
        printf("线程1和线程2不相等\n");
    }
    pthread_join(thread1_id, NULL);
    pthread_join(thread2_id, NULL);
    return 0;
}

上面的代码中,主线程调用pthread_create函数创建了两个新线程,新线程执行thread_function函数。在函数中调用pthread_self函数获取当前线程的线程ID并打印出来。 主线程调用pthread_equal函数判断两个新线程是否相等,并打印出结果。最后主线程调用pthread_join等待两个新线程结束。

互斥锁与条件

与进程的信号量相似。这个操作存在与线程当中。
互斥锁的4个基本操作:
1、创建
2、销毁
3、加锁
4、解锁
条件的5个基本操作:
1、创建
2、销毁
3、发送
4、广播 这里就是发送的升级版可以给多个等待中的线程发送信号,这里不展开写了
5、等待

互斥锁与条件的初始化及释放(创建操作)

pthread_mutex_init(&mutex, NULL)
pthread_cond_init(&cond,NULL);

pthread_mutex_init()函数 使用 attr(第二个参数) 指定的 属性 初始化 mutex 引用的 互斥量。如果 attr (第二个参数)为 NULL,则使用默认的互斥锁属性;效果与传递默认互斥属性对象的地址相同。成功初始化后,互斥锁的状态变为初始化和解锁。
pthread_cond_init()函数 使用attr(第二个参数) 引用的 属性 初始化 cond引用的条件变量。如果 attr(第二个参数) 为 NULL,则使用默认的条件变量属性;效果与传递默认条件变量属性对象的地址相同。成功初始化后,条件变量的状态变为初始化状态。

与之相对的释放(销毁)

pthread_mutex_destroy(&mutex) //函数销毁 mutex 引用的 互斥 对象;互斥对象实际上变为未初始化。
pthread_cond_destroy(&cond) //销毁 cond指定的给定条件变量;该对象实际上变为未初始化。

初始化互斥锁及条件变量的宏

初始化互斥锁:PTHREAD_MUTEX_INITIALIZER
初始化条件变量:PTHREAD_COND_INITIALIZER

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_mutex_lock(加锁) 与 pthread_mutex_unlock(解锁)

pthread_mutex_lockpthread库中用来获取互斥锁的函数。
pthread_mutex_unlockpthread库中用来释放互斥锁的函数。它俩的原型是:

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);

mutex: 指向互斥锁的指针

pthread_mutex_lock函数尝试获取互斥锁,如果该互斥锁当前没有被其他线程锁定,则获取成功并返回0。如果该互斥锁当前被其他线程锁定,则该函数会阻塞等待直到该互斥锁被释放。
pthread_mutex_unlock函数释放互斥锁,释放指定的互斥锁。
例如:

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

pthread_mutex_t mutex; // 互斥锁
int shared_var = 0; // 共享变量

void* thread_function(void* arg) {
    int i;
    for (i = 0; i < 1000000; i++) {
        pthread_mutex_lock(&mutex); // 获取互斥锁
        shared_var++; // 对共享变量进行操作
        pthread_mutex_unlock(&mutex); // 释放互斥锁
    }
    return NULL;
}

int main() {
    pthread_t thread1_id, thread2_id;
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
    int ret = pthread_create(&thread1_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("创建线程1失败\n");
        return -1;
    }
    ret = pthread_create(&thread2_id, NULL, thread_function, NULL);
    if (ret != 0) {
        printf("创建线程2失败\n");
        return -1;
    }
    pthread_join(thread1_id, NULL);
    pthread_join(thread2_id, NULL);
    printf("最终的共享变量值: %d\n", shared_var);
    pthread_mutex_destroy(&mutex); // 销毁互斥锁
    return 0;
}

上面的代码中,主线程创建了两个新线程,新线程执行thread_function函数。在函数中,先使用pthread_mutex_lock获取互斥锁,然后对共享变量进行操作,最后使用pthread_mutex_unlock释放互斥锁,期间如果有其他线程都将阻塞直到释放互斥锁。

pthread_cond_signal 与 pthread_cond_wait(发送与等待)

pthread_cond_signalpthread库中用来发送条件的函数。
pthread_cond_waitpthread库中用来等待条件的函数。它俩的原型是:

pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

cond条件变量
mutex互斥锁

需要注意的是:
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

这个函数会先解锁 mutex ,然后等待cond条件变量,当其他线程调用pthread_cond_signal或者pthread_cond_broadcast唤醒等待的线程,然后再加锁 mutex.

下面是一个实例代码用来理解互斥锁与条件:

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

/* 初始化互斥锁和条件变量 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int flag = 0;

void *thread1(void *arg)
{
    int ret;
    /* 加锁 */
    ret = pthread_mutex_lock(&mutex);
    if (ret != 0) {
        /* 错误处理 */
        perror("pthread_mutex_lock");
        pthread_exit(NULL);
    }
    printf("线程1:加锁成功\n");
    flag = 1;
    /* 发送信号 */
    pthread_cond_signal(&cond);
    printf("线程1:发送信号\n");
    /* 解锁 */
    ret = pthread_mutex_unlock(&mutex);
    if (ret != 0) {
        /* 错误处理 */
        perror("pthread_mutex_unlock");
        pthread_exit(NULL);
    }
    printf("线程1:解锁成功\n");
    pthread_exit(NULL);
}

void *thread2(void *arg)
{
    int ret;
    /* 加锁 */
    ret = pthread_mutex_lock(&mutex);
    if (ret != 0) {
        /* 错误处理 */
        perror("pthread_mutex_lock");
        pthread_exit(NULL);
    }
    printf("线程2:加锁成功\n");
    /* 等待信号 */
    while(!flag) {
        printf("线程2:等待信号\n");
        pthread_cond_wait(&cond, &mutex);
    }
    printf("线程2:收到信号\n");
    /* 解锁 */
    ret = pthread_mutex_unlock(&mutex);
    if (ret != 0) {
        /* 错误处理 */
        perror("pthread_mutex_unlock");
        pthread_exit(NULL);
    }
    printf("线程2:解锁成功\n");
pthread_exit(NULL);
}

int main()
{
int ret;
pthread_t t1, t2;
/* 创建线程 */
ret = pthread_create(&t1, NULL, thread1, NULL);
if (ret != 0) {
/* 错误处理 */
perror("pthread_create thread1");
return -1;
}
ret = pthread_create(&t2, NULL, thread2, NULL);
if (ret != 0) {
/* 错误处理 */
perror("pthread_create thread2");
return -1;
}
/* 等待线程结束 */
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}

在这个例子中, thread1 会在第一次运行时首先获得互斥锁,并将 flag 设置为 1。 然后它会发出一个信号,告诉 thread2 条件已经满足,可以继续运行了。 thread2 会一直等待,直到条件变量被信号触发。

结束

爆肝学习过程,对你有帮助的话,点个赞支持一下吧。
转载请联系本人。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Strange_Head

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

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

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

打赏作者

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

抵扣说明:

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

余额充值