线程操作:锁、条件变量的使用

简介: 线程操作:锁、条件变量的使用

关于本文提到的线程操作所用到的函数以及数据结构全部在一个头文件中定义:

#include<pthread.h>

1.线程的创建和销毁:

每一个线程都有一个id,叫做句柄,一个线程创建完毕时,它的句柄也被确定下来

句柄存放在句柄变量里,就像指针存放在指针变量里一样,句柄变量的类型是pthread_t

下面的代码创建了一个用于存放句柄的数组:

pthread_t threadid[100] = {0}

开始创建线程:

pthread_create(&thread[i], NULL, thread_callback, &argc);

第一个参数是对句柄变量的引用,需要pthread_create函数能对句柄变量进行修改,函数执行成功则将创建好的线程的句柄赋值给此句柄变量

第二个参数是创建线程的属性,一般写NULL,为默认

第三个参数是该线程执行的函数,需要函数的返回类型为void*,如果不是,需要强制转换

第四个参数是传入第三个参数的参数,需要void*类型,如果有多个参数,需要用一个结构体的引用来传入这些参数

主进程结束前需要确保子线程结束并正确销毁以释放资源,否则他将成为孤儿进程或僵尸进程,这两种情况都是应该避免的

可以在主进程中等待子进程正确完成:

pthread_join(thread[i], NULL); 

该函数会等待参数句柄所指代的线程完成,否则会阻塞

第一个参数是需要等待的句柄,不需要引用,第二个参数是用来获取该线程完成时的返回值,需要一个指针,线程的返回值会保存到这个指针,设置为NULL表示不需要获取该线程的返回值

2.互斥锁、自旋锁用来解决多个线程同时访问共享变量的问题,也叫做竞态条件,而出现竞态条件也算是时钟中断机制的一个缺点。

对共享变量操作的代码前后加锁解锁就可以避免竞态条件:

pthread_mutex_lock(&mutex);//加锁,参数是一个被初始化后的互斥锁的地址
 (*pcount)++; // 使用互斥锁对共享变量进行保护,避免竞态条件
 pthread_mutex_unlock(&mutex);//解锁,参数是一个互斥锁的地址

创建互斥锁变量:

pthread_mutex_t mutex;

其中pthread_mutex_t是互斥锁变量的类型

之所以为变量是因为它可以具有不同的属性:

初始化互斥锁变量:

pthread_mutex_inint(&mutex, NULL);

第一个参数是对一个互斥锁变量的引用

第二个参数规定了该互斥锁的属性,默认为NULL

互斥锁的生命周期:创建-> 初始化-> 被引用-> 被销毁

需要注意的是,使用未初始化的互斥锁变量可能会访问位置内存,造成崩溃,程序结束前未销毁互斥锁变量可能会造成资源泄露

互斥锁的销毁:

pthread_mutex_destory(&mutex);

对于自旋锁,操作上与互斥锁无区别,只需要把mutex字段改成spin

cas和原子操作另作文章

3.条件变量:

pthread_cond_t cond;

pthread_cond_t 是条件变量的类型, 用来声明一个条件变量,使用前需要初始化:

pthraed_cond_init(&cond, NULL)

第二个参数代表条件变量的属性,默认NULL

先看一个例子:

static void *thread_pool_callback(void *arg) {
    struct workers *worker = (struct workers*)arg;
    while (1) {
        pthread_mutex_lock(&worker->manager->mutex);
        while (worker->manager->tasks == NULL) {
            if (worker->terminate) break;
            pthread_cond_wait(&worker->manager->cond, &worker->manager->mutex);
            if (worker->terminate) break;
        }
        if (worker->terminate) {
            pthread_mutex_unlock(&worker->manager->mutex);
            break;
        }
        struct tasks *task = worker->manager->tasks;
        LIST_DELETE(task, worker->manager->tasks);
        pthread_mutex_unlock(&worker->manager->mutex);
        task->task_func(task);
    }
    free(worker);
    return NULL;
}

程序会阻塞到pthread_cond_wait(&cond,&mutex)直到收到信号将它唤醒

而这个信号需要有函数来发送:

1.pthread_cond_signal(&cond):唤醒一个正在条件变量cond上等待的线程,如果有多个线程在等待,具体唤醒哪一个线程我们无从得知,取决于系统的调度策略

2.pthread_cond_broadcast(&cond):唤醒所有在cond上等待的线程

注意:使用信号来唤醒等待的线程时,wait和signal、broadcast必须使用同一个条件变量cond

相信你也注意到了,pthread_cond_wait(&cond, &mutex);的第二个参数是一个互斥锁变量,这是用来确保wait操作的原子性,也就是说pthread_cond_wait(&cond, &mutex);前后必须上锁、解锁

最后销毁条件变量:

pthread_cond_destory(&cond);

注意:在尝试销毁处于等待状态的条件变量时,会失败,不能过河拆桥!

目录
相关文章
|
18天前
|
安全 Java 编译器
线程安全问题和锁
本文详细介绍了线程的状态及其转换,包括新建、就绪、等待、超时等待、阻塞和终止状态,并通过示例说明了各状态的特点。接着,文章深入探讨了线程安全问题,分析了多线程环境下变量修改引发的数据异常,并通过使用 `synchronized` 关键字和 `volatile` 解决内存可见性问题。最后,文章讲解了锁的概念,包括同步代码块、同步方法以及 `Lock` 接口,并讨论了死锁现象及其产生的原因与解决方案。
55 10
线程安全问题和锁
|
13天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
1月前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
24天前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
24天前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
2天前
|
存储 算法 Java
关于python3的一些理解(装饰器、垃圾回收、进程线程协程、全局解释器锁等)
该文章深入探讨了Python3中的多个重要概念,包括装饰器的工作原理、垃圾回收机制、进程与线程的区别及全局解释器锁(GIL)的影响等,并提供了详细的解释与示例代码。
10 0
|
1月前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
28 1
|
25天前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
11 0
|
1月前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
|
1月前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
32 0