【原创】手把手教你Linux下的多线程设计--Linux下多线程编程详解(三)

简介: 本文可任意转载,但必须注明作者和出处。【原创】手把手教你Linux下的多线程设计(三)                                      --Linux下多线程编程详解 原创作者:Frozen_socker(冰棍)    E_mail:dlskyfly@163.com线程互斥 互斥操作,就是对某段代码或某个变量修改的时候只能有一个线程在执行这段代码,其他线程不能同时进入这段代码或同时修改该变量。

本文可任意转载,但必须注明作者和出处。

【原创】手把手教你Linux下的多线程设计(三)
                                      --Linux下多线程编程详解
 
原创作者:Frozen_socker(冰棍)  
 E_mail:dlskyfly@163.com

线程互斥

 

互斥操作,就是对某段代码或某个变量修改的时候只能有一个线程在执行这段代码,其他线程不能同时进入这段代码或同时修改该变量。这个代码或变量称为临界资源。

 

 

例如:有两个线程A和B,临界资源为X,首先线程A进入,将X置为加锁状态,在A将锁打开之前的这段时间里,如果此时恰巧线程B也欲获得X,但它发现X处于加锁状态,说明有其它线程正在执行互斥部分,于是,线程B将自身阻塞。。。线程A处理完毕,在退出前,将X解锁,并将其它线程唤醒,于是线程B开始对X进行加锁操作了。通过这种方式,实现了两个不同线程的交替操作。

 

 

 记住一个互斥体永远不可能同时属于两个线程。或者处于锁定状态;或者空闲中,不属于任何一个线程。

 

 

 

 代码如下:

//example_3.c
#include <stdio.h>
#include 
<pthread.h>

void * pthread_func_test(void *  arg);

pthread_mutex_t mu;

int
 main()
{
    
int
 i;
    pthread_t pt;
    
    pthread_mutex_init(
&mu,NULL);        //声明mu使用默认属性,此行可以不写

    pthread_create(&pt,NULL,pthread_func_test,NULL);
    
for(i = 0; i < 3; i++
)
    
{
        pthread_mutex_lock(
&
mu);
        printf(
"主线程ID是:%lu  ",pthread_self());        //pthread_self函数作用:获得当前线程的id

        pthread_mutex_unlock(&mu);
        sleep(
1
);
    }
    
}


void * pthread_func_test(void *  arg)
{
    
int
 j;
    
for(j = 0; j < 3; j++
)
    
{
        pthread_mutex_lock(
&
mu);
        printf(
"新线程ID是:%lu  "
,pthread_self());
        pthread_mutex_unlock(
&
mu);
        sleep(
1
);
    }

}

 

 

 

终端输出结果:

主线程ID是 : 3086493376
新线程ID是 : 
3086490512

主线程ID是 : 
3086493376
新线程ID是 : 
3086490512

主线程ID是 : 
3086493376
新线程ID是 : 
3086490512

 

注:在你机器上运行的结果很可能与这里显示的不一样。

 

 

   

pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,都处于加锁状态中,即同一时间只能被一个线程调用执行。当另一个线程执行到pthread_mutex_lock处时,如果该锁此时被其它线程使用,那么该线程被阻塞,即程序将等待到其它线程释放此互斥锁。

 

 

 

上述例子中,涉及到了几个函数:pthread_mutex_init/pthread_mutex_lock/pthread_mutex_unlock/pthread_mutex_destroy/pthread_self

 

 

  函数原型:

int pthread_mutex_init(pthread_mutex_t * restrict mutex,
                                                
const pthread_mutexattr_t *restrict attr);

 

函数作用:

初始化互斥体类型变量mutex,变量的属性由attr进行指定。attr设为NULL,即采用默认属性,这种方式与pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER的方式等价。

 

 

函数原型: 

int pthread_mutex_lock(pthread_mutex_t *mutex);

 

函数作用:

用来锁住互斥体变量。如果参数mutex所指的互斥体已经被锁住了,那么发出调用的线程将被阻塞直到其他线程对mutex解锁为止。

 

 

 

函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

函数作用:

如果当前的线程拥有参数mutex所指定的互斥体,那么该函数调用将该互斥体解锁。

 

 

 

函数原型:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

 

函数作用:

用来释放互斥体所占用的资源。

 

 

 

函数原型:

pthread_t pthread_self( void );

函数作用:获得线程自身的ID。前面我们已经提到过,pthread_t的类型为unsigned long int,所以在打印的时候要使用%lu方式,否则将产生奇怪的结果。

  

 

 

但是上面的代码并不完善,假设将循环次数修改得足够的长,打印后的结果可能并不是我们所希望看到的交替打印,可能象下面这样:

主线程ID是 : 3086493376
新线程ID是 : 
3086490512
 
主线程ID是 : 
3086493376
新线程ID是 : 
3086490512

新线程ID是 : 
3086490512
主线程ID是 : 
3086493376

 

 

 

这是什么原因呢?因为Linux是分时操作系统,采用的是时间片轮转的方式,主线程和新线程可能因为其它因素的干扰,获得了非顺序的时间片。如果想要严格的做到“交替”方式,可以略施小计,即加入一个标志。

 

 

   

完整程序如下:

//example_4.c
#include <stdio.h>
#include 
<pthread.h>

void * pthread_func_test(void *  arg);

pthread_mutex_t mu;
int flag = 0
;

int
 main()
{
    
int
 i;
    pthread_t pt;
    
    pthread_mutex_init(
&
mu,NULL);
    pthread_create(
&
pt,NULL,pthread_func_test,NULL);
    
for(i = 0; i < 3; i++
)
    
{
        pthread_mutex_lock(
&
mu);
        
if(flag == 0
)
                printf(
"主线程ID是:%lu  "
,pthread_self());    
        flag 
= 1
;        
        pthread_mutex_unlock(
&
mu);
        sleep(
1
);
    }

    pthread_join(pt, NULL);
    pthread_mutex_destroy(
&mu);    
}


void * pthread_func_test(void *  arg)
{
    
int
 j;
    
for(j = 0; j < 3; j++
)
    
{
        pthread_mutex_lock(
&
mu);
        
if(flag == 1
)
            printf(
"新线程ID是:%lu  "
,pthread_self());
        flag 
== 0
;
        pthread_mutex_unlock(
&
mu);
        sleep(
1
);
    }

}

 

 

在使用互斥锁的过程中很有可能会出现死锁:即两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,A线程先锁定互斥锁1,B线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock,该函数企图锁住一个互斥体,但不阻塞。

 

 

 

函数原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

函数pthread_mutex_trylock()用来锁住参数mutex所指定的互斥体。如果参数mutex所指的互斥体已经被上锁,该调用不会阻塞等待互斥体的解锁,而会返回一个错误代码。通过对返回代码的判断,程序员就可以针对死锁做出相应的处理。所以在对多个互斥体编程中,尤其要注意这一点。

 

 

 

经过以上的讲解,我们就学习了Linux下关于多线程方面对互斥体变量的操作。下一节,将给大家讲解有关线程同步方面的知识点。

目录
相关文章
|
11月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
458 83
|
8月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
301 6
|
11月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
432 0
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
431 0
|
9月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
552 16
|
8月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
729 0
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
440 67
|
11月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
12月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
784 5