带你学习多线程编程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你学习多线程编程相关的知识,希望对你有一定的参考价值。

线程概念

定义

线程就是进程内部的执行流,一个进程至少有一个线程,线程拥有自己的私有资源同时也会和进程共享资源。

线程独有的资源

  • 线程描述符
  • 寄存器
  • 线程栈
  • errno
  • 信号掩码
  • 实时调度策略

线程和进程共享的资源

  • 全局变量
  • 代码段
  • 文件描述符表
  • 进程ID和组ID
  • 每种信号的处理方式
  • 当前工作目录

    线程和进程的区别

  • 线程是资源调度的最小单位 ,进程时资源分配的最小单位
  • 进程是一次程序运行活动,线程是进程中的一个执行路径
  • 进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。
  • 进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。

线程的创建

#include <pthread.h>

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

参数:

  1. thread:返回线程的id
  2. attr:线程属性,可以手动设置(下文将详细描述线程属性)
  3. start_routine:线程执行的函数的函数地址
  4. arg:线程执行函数的参数
    返回值:
    成功返回0,失败返回错误码。

    线程的终止

    有三种方式终止一个线程。

  5. 用return返回。
  6. 用pthread_exit();
    #include <pthread.h>
    void pthread_exit(void *retval);

    参数:
    retval:保存线程退出码,<font color="#dd00dd">这个指针一定要是全局变量或者堆上开辟的。</font><br/>

  7. 用pthread_cancel()
    #include <pthread.h>
    int pthread_cancel(pthread_t thread);

    参数:
    thread:结束的线程ID(可以结束任意线程)
    返回值:
    成功返回0,失败返回错误码。
    线程的终止不能用exit(),这是进程的终止方式。

线程的等待与分离

为什么需要线程等待

  • 不等待,线程结束后不会自动释放资源。
  • 会导致过多线程描述符被占用,无法创建新的线程。

    线程等待函数

    #include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);

    参数:
    thread:等待线程的ID
    retval:保存退出状态码
    返回值:
    成功返回0,失败返回错误码。

    线程分离

    当我们不关心线程的退出状态,只希望线程结束系统会自动清理和释放资源,这时我们就可以使用线程分离。

    #include <pthread.h>
    int pthread_detach(pthread_t thread);

    参数:
    thread:分离的线程ID
    返回值:
    成功返回0,失败返回错误码。
    线程一旦被pthread_detach()分离,就不能再用pthread_join()获取其状态了,也不能再返回链接状态。

    线程属性

    在前面pthread_create()函数时,就涉及到pthread_attr_t *attr线程属性,我们可以手动设置线程属性。

    int pthread_attr_init(pthread_attr_t *attr);//初始化线程属性
    int pthread_attr_destroy(pthread_attr_t *attr);//销毁线程属性对象
    Detach state        = PTHREAD_CREATE_JOINABLE//分离属性
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//设置分离属性
    int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);//获得分离属性
    Scope               = PTHREAD_SCOPE_SYSTEM//抢夺资源范围
    Inherit scheduler   = PTHREAD_INHERIT_SCHED//是否继承线程调度策略
    Scheduling policy   = SCHED_OTHER//调度策略
    Scheduling priority = 0//调度优先级
    Guard size          = 4096 bytes//线程栈之间的安全区
    Stack address       = 0x40196000//自己指定线程栈
    Stack size          = 0x201000 bytes//栈的大小

    线程属性这一块,本文只是简单列出,更深层次的掌握需要大家自行查阅资料。

    线程的优缺点

    线程的优点

  • 创建一个线程的代价要比创建一个进程的代价小得多。
  • 与进程之间的切换相比较,线程之间的切换操作系统要做的工作少得多。
  • 线程占用的资源比进程少得多。
  • 能充分利用 多处理器的可并行数量。
  • 在等待慢速I/O操作结束时,程序可执行其他的计算任务。
  • 计算密集型应用,多处理器运行,可以将计算分布到多个线程中计算。
  • I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以等待不同的I/O操作。

    线程的缺点

  • 性能损失
  • 健壮性降低。
  • 缺乏访问控制
  • 编程难度提高
  • 多线程对GDB支持不好
  • 多线程对信号支持不好

    线程互斥

    互斥量

    互斥量

    1. 定义互斥锁:pthread_mutex_t mutex;
    2. 初始化:pthread_mutex_init(&mutex,NULL);
    3. 上锁:pthread_mutex_lock(&mutex);
    4. 解锁:pthread_mutex_unlock(&mutex);
    5. 销毁:pthread_mutex_destroy(&mutex);

      自旋锁

    6. 定义自旋锁:pthread_spinlock_t spin;
    7. 初始化:int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
    8. 上锁:int pthread_spin_lock(pthread_spinlock_t *lock);
    9. 解锁:int pthread_spin_unock(pthread_spinlock_t *lock);
    10. 销毁锁:int pthread_spin_destroy(pthread_spinlock_t *lock);

自旋锁与互斥锁的区别

自旋锁和互斥所的区别:互斥锁是当阻在pthread_mutex_lock时,放弃CPU,好让别人使用CPU。自旋锁阻塞在spin_lock时,不会释放CPU,不断的问CPU可以使用了不

读写锁

  1. pthread_rwlock_t lock;
  2. 初始化:int pthread_rwlock_init(pthread_rwlock_t restrict rwlock,const pthread_rwlockattr_t restrict attr);
  3. 读锁:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
  4. 写锁:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
  5. 解锁:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
  6. 销毁锁:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

读读共享,读写排他,写写排他,写锁优先.

同步

条件变量

  1. 定义条件变量:pthread_cond_t cond;
  2. 初始化条件变量:int pthread_cond_init(pthread_cond_t restrict cond,const pthread_condattr_t restrict attr);
  3. 等待条件:int pthread_cond_wait(pthread_cond_t restrict cond,pthread_mutex_t restrict mutex);
        如果没有在锁环境下使用,互斥量形同虚设
        如果在锁环境下,将mutex解锁
        wait返回时,将mutex置为原来的状态
  4. 使条件满足:int pthread_cond_signal(pthread_cond_t *cond);
  5. 销毁条件变量:int pthread_cond_destroy(pthread_cond_t *cond);

综合案例

pthread_mutex_t mutex;//创建互斥量
int a = 0;
int b = 0;
void *r1(void* arg) //线程1执行函数
{
    while(1)
    {
        pthread_mutex_lock(&mutex);//上锁
        a++;
        b++;
        if(a != b)
        {
            printf("%d != %d\n",a,b);
        }
        pthread_mutex_unlock(&mutex);//解锁
    }
}

void *r2(void* arg)//线程2执行函数
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        a++;
        b++;
        if(a != b)
        {
            printf("%d != %d\n",a,b);
        }
        pthread_mutex_unlock(&mutex);
    }   
}
int main(void)
{
    pthread_t t1,t2;
    pthread_mutex_init(&mutex,NULL);//初始化互斥量
    pthread_create(&t1,NULL,r1,NULL);//创建线程
    pthread_create(&t2,NULL,r2,NULL);//创建线程

    pthread_join(t1,NULL);//线程等待
    pthread_join(t2,NULL);
    return 0;
}

以上是关于带你学习多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

并发编程之美,带你深入理解java多线程原理

线程学习知识点总结

学多线程的看过来,带你学习多线程中断机制

多线程编程

多线程编程

带你搞定多线程,并发编程之volatile关键字