Linux学习笔记(四)----多线程

线程的概念

        线程是指运行中程序的最小调度单位。一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级进程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该系统中的全部系统资源,比如文件描述符和信号处理等。一个进程可以并发多个线程,每条线程并行执行不同的任务。轻量级进程多指的是内核线程(kernel thread),把用户线程(usr thread)成为线程。
 

线程与进程的区别

        1,在Linux下:进程是资源分配的最小单位,线程是程序执行的最小单位。

        2,进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他
    进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈
    和局部变量,但线程没有单独的地址空间,一个线程死掉就是整个进程死掉
    所以多进程程序比多线程程序健壮。

        3,线程是一种非常“节俭”的多任务操作方式,和进程相比,它不用分配它独立的地址空间,不用建立众多的数据表来维护它的代码段、堆栈段和数据段,而线程彼此之间使用相同的地址空间,共享大部分数据, 线程间彼此切换时间也远远小于进程间切换时间。

多线程开发的相关函数        

        多线程的开发的基本概念主要有三点:线程互斥锁条件 。下面一一介绍:

线程: 

        线程操作又分为:创建线程,退出线程,等待线程。 

        创建线程: 

#include <pthread.h>

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

// 返回值:成功返回0,失败返回错误码


//thread:返回线程ID
attr:设置线程的属性,attr为NULL表⽰示使⽤用默认属性
start_routine:是个函数地址,线程启动后要执⾏的函数
arg:传给线程启动函数的参数,如要传多个参数, 可以用结构封装。

        可以通过pthread_self()函数来获取用户的线程ID   

     退出线程:                       

void pthread_exit(void *value_ptr);

返回值类型为空

//value_ptr:表示线程退出状态,通常传NULL

注意:多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。

        还有一个取消(杀死)线程的函数

int pthread_cancel(pthread_t thread);

返回值:成功返回0,失败返回错误码

thread:想要杀死线程的ID号

一个线程可以自己把自己杀死,也可以被别人杀死

  • 线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。
  • 取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。
  • 可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthreestcancel函数自行设置一个取消点

        等待线程:

int pthread_join(pthread_t thread,void **retval)

返回值:成功返回0,失败返回错误码

value_ptr: 它指向一个指针,指针指向线程的返回值

       pthread_join作用主要是:主线程等待新线程退出,否则就会导致进程的内存泄漏 

        调用此函数的线程将一致阻塞,直到指定线程调用pthread_exit或者退出

// demo1

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

void *tfn1(void *arg){
    int i=3;
    static int ret = 5;            
    while(i--){

        printf("This is  thread1%ld running!\n", pthread_self());
        printf("from data:%d\n",*((int*)arg));
    }

    pthread_exit((void *)&ret);                    //返回一个整形数5
}

int main()
{

    pthread_t tid;

    int param = 66;
    void *ret;

    if(pthread_create(&tid, NULL, tfn1, (void*)&param) == 0){
        printf("main%ld: create tfn1 success!\n", pthread_self());
    }
    pthread_join(tid, &ret);

    printf("from tfn1 return value is %d\n", *((int*)ret));

    return 0;
}

执行结果:

linux@ubuntu$gcc demo1.c -lpthread

linux@ubuntu$ ./a.out 
main139755713562432: create tfn1 success!
This is  thread1139755705120512 running!
from data:66
This is  thread1139755705120512 running!
from data:66
This is  thread1139755705120512 running!
from data:66
from tfn1 return value is 5

 互斥锁:

       先了解一下同步、互斥与原子操作的概念

【同步】:

  是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

【互斥】:

  是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

【原子操作】:

        原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程) 
原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断 

         互斥锁

        互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )

        如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;

        如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

互斥锁的相关API

        1.互斥锁的初始化

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

pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:

pthread_mutex_t   mutex; 变量mutex只有两种取值1、0。



 参数attr:指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,
                                                    默认属性为快速互斥锁 。
返回值:成功返回0,失败返回错误码

        2.销毁一个互斥锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

        3.加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

        4.尝试加锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);

        5.解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

看一个例子

//使用互斥量解决多线程抢占资源的问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
 
char* buf[5]; //字符指针数组  全局变量
int pos; //用于指定上面数组的下标
 
//1.定义互斥量
pthread_mutex_t mutex;
 
void *task(void *p)
{
    //3.使用互斥量进行加锁
    pthread_mutex_lock(&mutex);
 
    buf[pos] = (char *)p;
    sleep(1);
    pos++;
 
    //4.使用互斥量进行解锁
    pthread_mutex_unlock(&mutex);
}
 
int main(void)
{
    //2.初始化互斥量, 默认属性
    pthread_mutex_init(&mutex, NULL);
 
    //1.启动一个线程 向数组中存储内容
    pthread_t tid, tid2;
    pthread_create(&tid, NULL, task, (void *)"zhangfei");
    pthread_create(&tid2, NULL, task, (void *)"guanyu");
    //2.主线程进程等待,并且打印最终的结果
    pthread_join(tid, NULL);
    pthread_join(tid2, NULL);
 
    //5.销毁互斥量
    pthread_mutex_destroy(&mutex);
 
    int i = 0;
    printf("字符指针数组中的内容是:");
    for(i = 0; i < pos; ++i)
    {
        printf("%s ", buf[i]);
    }
    printf("\n");
    return 0;
}

死锁的相关概念

        死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

条件     

  互斥量和读写锁解决了多线程访问共享变量产生的竞争问题,那么条件变量的作用何在呢?? 
     条件变量的作用是用于多线程之间关于共享数据状态变化的通信。当一个动作需要另外一个动作完成时才能进行,即:当一个线程的行为依赖于另外一个线程对共享数据状态的改变时,这时候就可以使用条件变量 

条件变量用来 阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。
  互斥锁一个明显的缺点是它只有两种状态: 锁定和非锁定。
        而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。 

  使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将 重新锁定互斥锁并重新测试条件是否满足。
该部分转自:https://blog.csdn.net/soygrow/article/details/79221673 

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);

返回值:函数成功返回0;任何其他返回值都表示错误

可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

阻塞在条件变量上pthread_cond_wait 

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
返回值:函数成功返回0;任何其他返回值都表示错误

释放阻塞的所有线程pthread_cond_broadcast

int pthread_cond_broadcast(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误 

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。

由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

解除在条件变量上的阻塞pthread_cond_signal

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
返回值:函数成功返回0;任何其他返回值都表示错误
它对 counter_lock 解锁,然后进入睡眠状态,等待 mycond 以接收 POSIX 线程“信号”。一旦接收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自 pthread_cond_signal() 或 pthread_cond_broadcast() 调用的信号),它就会苏醒。但 pthread_cond_wait() 没有立即返回 -- 它还要做一件事:重新锁定  counter_lock。 

原文链接:https://blog.csdn.net/soygrow/article/details/79221673

阻塞直到指定时间pthread_cond_timedwait

int pthread_cond_timedwait(pthread_cond_t *cv,
            pthread_mutex_t *mp, const structtimespec * abstime);

返回值:函数成功返回0;任何其他返回值都表示错误 

函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。

原文链接:https://blog.csdn.net/soygrow/article/details/79221673

释放条件变量pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t *cv);

返回值:函数成功返回0;任何其他返回值都表示错误
释放条件变量。
注意:条件变量占用的空间并未被释放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值