有关多线程

线程概念

线程实际上是应用层的概念,在Linux内核中,所有的调度实体都被称为任务(task),他们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源。

进程:进程是操作系统资源分配的基本实体
线程:线程是cpu调度的基本单位,是进程的内部资源,是linux中最小的资源单位

基本接口

2.1线程的创建

<br class="Apple-interchange-newline"><div></div>

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

线程传参

demo

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

void *func(void *arg)
{
    //printf("%s\n",(char *)arg);
    int data = *((int *)arg);
    printf("%d\n",data);
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    //pthread_create(&tid, NULL, func, (void *)"hello child");
    int data = 100;
    pthread_create(&tid, NULL, func, &data);
    sleep(1);
    return 0;
}

并发性

线程最重要的特性是并发,线程函数 doSomething() 会与主线程 main() 同时运行,这是它与普通函数调用的根本区别。需要特别提醒的是,由于线程函数的并发性,在线程中访问共享资源需要特别小心,因为这些共享资源会被多个线程争抢,形成“竞态”。

2.2线程的退出

#include <pthread.h>

void pthread_exit(void *retval);

主线程退出后,其余线程可以继续运行,但请注意,上述代码中如果主线程不调用 pthread_exit() 的话,那么相当于退出了整个进程,则子线程也会被迫退出。

2.3线程的接合

#include <pthread.h>

int pthread_join(pthread_t tid, void **val);

包括主线程在内,所有线程的地位是平等的,任何线程都可以先退出,任何线程也可以接合另外一条线程。

2.4其它

2.4.1获取线程TID

#include <pthread.h>

pthread_t pthread_self(void);

2.4.2线程错误码

线程函数对系统错误码的处理跟标准C库函数的处理方式有很大不同,标准C库函数会对全局错误码 errno 进行设置,而线程函数发生错误时会直接返回错误码。

2.4.3函数单例(一个实例)

许多时候,我们希望某个函数只被严格执行一次,这种需求在一些初始化功能模块中尤为常见。

,由于线程的并发特性,我们无法预先知晓哪条线程会对信号量进行初始化,于是就希望有一种只执行一遍的函数单例,可以被众多的并发线程放心去调用。

#include <pthread.h>

// 函数单例控制变量
pthread_once_t once_control = PTHREAD_ONCE_INIT;

// 函数单例启动接口
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

线程的属性

1.1查看线程属性

1.2属性变量发使用

由于线程属性众多,因此需要的时候不直接设置,而是先将它们置入一个统一的属性变量中,然后再以此创建线程。属性变量是一种内置数据类型,需要用如下函数接口专门进行初始化和销毁:

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

2.分离属性

2.1僵尸线程

默认情况下,线程启动后处于可接合状态(即未分离),此时的线程可以在退出时让其他线程接合以便释放资源,但若其他线程未及时调用 pthread_join() 去接合它,它将成为僵尸线程,浪费系统资源。

2.2分离与接合

2.3线程启动后强制分离

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

void *func(void *arg)
{
    // 分离线程属性
    pthread_detach(pthread_self());
    int count = 10;
    while (count--)
    {
        printf("count : %d\n",count);
        sleep(1);
    }
    pthread_exit("子线程退出");
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, func, NULL);
    //sleep(1);
    // 接合子线程
    // 如果先执行接合函数后,那么设置分离属性则无效
    void *val;
    int ret = pthread_join(tid, &val);
    if(ret != 0)
    {
        perror("接合失败:");
    }
    else
    {
        printf("接合成功:%s\n",(char *)val);
    }
    // 主线程退出,也会等待子线程先退出
    pthread_exit(NULL);
    return 0;
}

3.基本概念

互斥与同步是最基本的逻辑概念:

  • 互斥指的是控制两个进度使之互相排斥,不同时运行。

  • 同步指的是控制两个进度使之有先有后,次序可控。

4.互斥锁

4.1基本逻辑

使得多线程间互斥运行的最简单办法,就是增加一个互斥锁。任何一条线成要开始运行互斥区间的代码,都必须先获取互斥锁,而互斥锁的本质是一个二值信号量,因此当其中一条线程抢先获取了互斥锁之后,其余线程就无法再次获取了,效果相当于给相关的资源加了把锁,直到使用者主动解锁,其余线程方可有机会获取这把锁。

4.2函数接口

定义

互斥锁是一个特殊的变量,定义如下:

#include <pthread>
pthread_mutex_t m;

初始化与销毁 未经初始化的互斥锁是无法使用的,初始化互斥锁有两种办法:

静态初始化:pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

动态初始化:

#include <pthread.h>

// 初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                       const pthread_mutexattr_t *restrict attr);

// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

加锁与解锁

#include <pthread.h>
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

// 加锁
pthread_mutex_lock( &m );

// 解锁
pthread_mutex_unlock( &m );

5.读写锁

5.1基本逻辑

将对资源的读写操作加以区分:读操作可以多任务并发执行,只有写操作才进行恰当的互斥。

定义

与互斥锁类似,读写锁也是一种特殊的变量:

pthread_rwlock_t rw;

初始化:

#include <pthread.h>

// 静态初始化:
pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;

// 动态初始化与销毁:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
                        const pthread_rwlockattr_t *restrict attr);

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

加锁

  • 如果只对数据进行读操作,那么就加 → 读锁。

  • 如果要对数据进行写操作,那么就加 → 写锁。

// 读锁
// 1,阻塞版本
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 2,非阻塞版本
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

// 写锁
// 1,阻塞版本
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 2,非阻塞版本
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

//解锁
#include <pthread.h>

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);


编译的时候记得加 -DRDLOCK 或者 -DWRLOCK
gcc 读写锁.c -pthread -DRDLOCK
./a.out

POSIX信号量

1.基本概念

POSIX信号量与IPC信号量组中的信号量元素的逻辑完全一样,但POSIX信号量操作更加简便,接口更加易用。在多进程多线程中运用广泛。

POSIX信号量分成两种:

  • POSIX匿名信号量

    • 通常用在线程间

    • 只存在于内存,在文件系统中不可见

  • POSIX具名信号量

    • 通常用在进程间

    • 存在于文件系统/dev/shm中,可被不同进程操作

2.POSIX匿名信号量

2.1定义

#include <semaphore.h>
sem_t s;

2.2初始化

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

2.3P/V操作

#include <semaphore.h>

int sem_wait(sem_t *sem); // P操作
int sem_post(sem_t *sem); // V操作

条件量的基本概念

在许多场合中,程序的执行通常需要满足一定的条件,条件不成熟的时候,任务应该进入睡眠阻塞等待,条件成熟时应该可以被快速唤醒。另外,在并发程序中,其他任务同时具备访问该条件,因此任何时候都必须以互斥的方式对条件进行访问。条件量就是专门解决上述场景的逻辑机制。

注意,上述表述中,条件和条件量是两个不同的东西,所谓条件就是指程序要继续运行所需要的前提条件,比如文件是否读完、内存是否清空等具体的场景限定,而条件量(即pthread_cond_t)是一种同步互斥变量,专用于解决上述逻辑场景。

说明:

  1. 在进行条件判断前,先加锁(防止其他任务并发访问)

  2. 成功加锁后,判断条件是否允许

    • 若条件允许,则直接操作临界资源,然后释放锁

    • 若条件不允许,则进入条件量的等待队列中睡眠,并同时释放锁

  3. 在条件量中睡眠的任务,可以被其他任务唤醒,唤醒时重新判定条件是否允许程序继续执行,当然也是必须先加锁。

2.条件量的使用

条件量一般要跟互斥锁(或二值信号量)配套使用,互斥锁提供锁住临界资源的功能,条件量提供阻塞睡眠和唤醒的功能。

// 单个唤醒,唤醒第一个进入条件量中睡眠的任务
pthread_cond_signal(&v);

// 集体唤醒,唤醒进入条件量中睡眠的所有任务
pthread_cond_broadcast(&v);

3.线程取消

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

注意:线程收到取消请求,就等价于提前退出。

设置线程响应取消的状态

include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);

3.取消点,设置线程响应取消类型

只有线程遇到取消点时才会退出,取消点接口 查阅 man 7 pthreads

fprintf()
fputc()
fputs()
sleep()
printf()
usleep()

设置取消点响应

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

2.死锁概念

死锁指的是由于某种逻辑问题,导致等待一把永远无法获得的锁的困境。比如最简单的是同一线程,连续对同一锁资源进行加锁,就进入了死锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值