C与多线程

C与多线程

参考博客

https://blog.csdn.net/networkhunter/article/details/100218945

第一个实例

#include <stdio.h>  
#include <pthread.h>  
void* thread( void *arg )  
{  
    //线程实现
    printf( "This is a thread and arg = %d.\n", *(int*)arg);  
    *(int*)arg = 12;  
    return arg;  
}  
int main( int argc, char *argv[] )  
{  
    //线程句柄
    pthread_t th;  
    int ret;  
    int arg = 10;  
    int *thread_ret = NULL;  
    /*
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 
        函数参数: 
        1. 线程句柄 thread:当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程进行管理。 

        2. 线程属性 attr: pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,
        给它传递NULL就行。具体线程有那些属性,我们后面再做介绍。

        3. 入口函数 start_routine(): 当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。
        如果线程创建成功,这个接口会返回0。 

        4. 入口函数参数 *arg : start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。
        这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。
        start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得。 
    */
    //                线程句柄,设置属性,入口函数,参数
    ret = pthread_create( &th, NULL, thread, &arg );  
    if( ret != 0 ){  
        printf( "Create thread error!\n");  
        return -1;  
    }  
    printf( "主函数执行.\n" );  
    //可通过pthread_join获取线程返回值
    /*
        因为函数参数是按值传递的,所以要想改变变量,必须传递地址
        pthread_join会阻塞主进程的执行
    */
    pthread_join( th, (void**)&thread_ret );  
    printf( "线程返回 = %d.\n", *thread_ret );  
    return 0;  
}  

执行结果
在这里插入图片描述

线程的分离与合并

跟内存分配类似,在动态分配了内存后须释放内存free或delete,若未进行释放,会导致内存泄漏
若创建的线程未进行回收,否则会产生资源泄漏

线程合并

线程的合并是一种主动回收线程资源的方案。

当一个进程或线程调用了针对其它线程的 pthread_join() 接口,就是线程合并了。

这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。

当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。

上面第一个例子就是使用的线程合并

线程分离

与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()

线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。

因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。

线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。

线程的属性

线程的初始化与销毁函数

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

线程拥有哪些属性呢?
一般地,Linux下的线程有:绑定属性、分离属性、调度属性、堆栈大小属性和满占警戒区大小属性。

绑定属性

轻进程和Linux系统的内核线程拥有相同的概念,属于内核的调度实体。一个轻进程可以控制一个或多个线程。
在计算机操作系统中,轻量级进程(LWP)是一种实现多任务的方法。与普通进程相比,LWP与其他进程共享所有(或大部分)它的逻辑地址空间和系统资源;与线程相比,LWP有它自己的进程标识符,并和其他进程有着父子关系;这是和类Unix操作系统的系统调用vfork()生成的进程一样的。另外,线程既可由应用程序管理,又可由内核管理,而LWP只能由内核管理并像普通进程一样被调度。Linux内核是支持LWP的典型例子。

绑定属性规定当前线程是否绑定轻进程,被绑定的线程具有较高的相应速度,因为操作系统的调度主体是轻进程,绑定线程可以保证在需要的时候它总有一个轻进程可用。绑定属性就是干这个用的。

函数定义如下:

int pthread_attr_setscope(pthread_attr_t *attr, int scope);

参数解析

  • 第一个就是线程属性对象的指针

  • 第二个就是绑定类型

    • PTHREAD_SCOPE_SYSTEM(绑定的)
    • PTHREAD_SCOPE_PROCESS(非绑定的)
分离属性

前面说过线程能够被合并和分离,分离属性就是让线程在创建之前就决定它应该是分离的。

若设置了此属性,就没有必要使用pthread_join()或pthread_detach()来回收资源了

函数定义如下:

pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate); 

参数解析

  • 第一个就是线程属性对象的指针

  • 第二个参数是否分离

    • PTHREAD_CREATE_DETACHED(分离的)
    • PTHREAD_CREATE_JOINABLE(可合并的,也是默认属性)
调度属性

线程的调度属性有三个,分别是:算法、优先级和继承权
Linux提供的线程调度算法有三个:时间片轮转、先到先服务和其它

函数定义如下:

pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

参数解析

  • 第二个参数决定调度算法

    • SCHED_RR(轮询)
    • SCHED_FIFO(先进先出)
    • SCHED_OTHER(其它)
堆栈大小属性

虽然同一个进程的线程之间是共享内存空间的,但是它的局部变量确并不共享。原因就是局部变量存储在堆栈中,而不同的线程拥有不同的堆栈。Linux系统为每个线程默认分配了8MB的堆栈空间,如果觉得这个空间不够用,可以通过修改线程的堆栈大小属性进行扩容。

函数定义如下:

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);  

参数解析

它的第二个参数就是堆栈大小了,以字节为单位
需要注意的是,线程堆栈不能小于16KB,而且尽量按4KB(32位系统)或2MB(64位系统)的整数倍分配,也就是内存页面大小的整数倍。

满栈警戒区属性

既然线程是有堆栈的,而且还有大小限制,那么就一定会出现将堆栈用满的情况。线程的堆栈用满是非常危险的事情,因为这可能会导致对内核空间的破坏,一旦被有心人士所利用,后果也不堪设想。为了防治这类事情的发生,Linux为线程堆栈设置了一个满栈警戒区。这个区域一般就是一个页面,属于线程堆栈的一个扩展区域。一旦有代码访问了这个区域,就会发出SIGSEGV信号进行通知。
虽然满栈警戒区可以起到安全作用,但是也有弊病,就是会白白浪费掉内存空间,对于内存紧张的系统会使系统变得很慢。所有就有了关闭这个警戒区的需求。同时,如果我们修改了线程堆栈的大小,那么系统会认为我们会自己管理堆栈,也会将警戒区取消掉,如果有需要就要开启它。

函数定义:

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);  

参数分析

它的第二个参数就是警戒区大小了,以字节为单位。与设置线程堆栈大小属性相仿,应该尽量按照4KB或2MB的整数倍来分配。当设置警戒区大小为0时,就关闭了这个警戒区。
虽然栈满警戒区需要浪费掉一点内存,但是能够极大的提高安全性,所以这点损失是值得的。而且一旦修改了线程堆栈的大小,一定要记得同时设置这个警戒区。

线程的同步

Linux提供的线程同步机制主要有互斥锁和条件变量。

互斥锁

首先我们看一下互斥锁。
所谓的互斥就是线程之间互相排斥,获得资源的线程排斥其它没有获得资源的线程。
Linux使用互斥锁来实现这种机制。

既然叫锁,就有加锁和解锁的概念。当线程获得了加锁的资格,那么它将独享这个锁,其它线程一旦试图去碰触这个锁就立即被系统“拍晕”。
当加锁的线程解开并放弃了这个锁之后,那些被“拍晕”的线程会被系统唤醒,然后继续去争抢这个锁。至于谁能抢到,只有天知道。
但是总有一个能抢到。于是其它来凑热闹的线程又被系统给“拍晕”了……如此反复。感觉线程的“头”很痛:)

从互斥锁的这种行为看,线程加锁和解锁之间的代码相当于一个独木桥,同一时刻只有一个线程能执行。
从全局上看,在这个地方,所有并行运行的线程都变成了排队运行了。比较专业的叫法是同步执行,这段代码区域叫临界区。同步执行就破坏了线程并行性的初衷了,临界区越大破坏得越厉害。
所以在实际应用中,应该尽量避免有临界区出现。实在不行,临界区也要尽量的小。如果连缩小临界区都做不到,那还使用多线程干嘛?

函数定义

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);  //初始化锁
int pthread_mutex_destory(pthread_mutex_t *mutex );  //销毁锁
int pthread_mutex_lock(pthread_mutex_t *mutex);  //加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); //加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);  //解锁

phtread_mutex_trylock()比较特别,用它试图加锁的线程永远都不会被系统“拍晕”,只是通过返回EBUSY来告诉程序员这个锁已经有人用了。至于是否继续“强闯”临界区,则由程序员决定。系统提供这个接口的目的可不是让线程“强闯”临界区的。它的根本目的还是为了提高并行性,留着这个线程去干点其它有意义的事情。当然,如果很幸运恰巧这个时候还没有人拥有这把锁,那么自然也会取得临界区的使用权。

测试用例:

#include <stdio.h>  
#include <stdlib.h>  
#include <errno.h>  
#include <unistd.h>  
#include <pthread.h>  
pthread_mutex_t g_mutex;  
int g_lock_var = 0;  
struct MyStruct
{
    int age;
    char name[256];
};
void* thread1( void *arg )  
{  
    int i, ret;  
    time_t end_time;  
    char *test_str = (char*)arg;
    //设置十秒
    end_time = time(NULL) + 10;  
    printf("%s\n",test_str);
    while( time(NULL) < end_time ) { 
        //加锁 ,进行下面的操作
        ret = pthread_mutex_trylock( &g_mutex );  
        //EBUSY资源占用
        if( EBUSY == ret ) {  
            printf( "thread1: the varible is locked by thread2.\n" );  
        } else {  
            printf( "thread1: lock the variable!\n" );  
            ++g_lock_var;  
            //解锁
            pthread_mutex_unlock( &g_mutex );  
        }  
        sleep(1);  
    }  
    return NULL;  
}  
void* thread2( void *arg )  
{  
    struct MyStruct *ms = (struct MyStruct *)arg;
    printf("MyStruct:   age:%d  name:%s \n",ms->age,ms->name);
    int i;  
    time_t end_time;  
    end_time = time(NULL) + 10;  
    while( time(NULL) < end_time ) {  
        //加锁
        pthread_mutex_lock( &g_mutex );  
        printf( "thread2: lock the variable!\n" );  
        ++g_lock_var;  
        sleep(1);  
        pthread_mutex_unlock( &g_mutex );  
    }  
    return NULL;  
}  
int main( int argc, char *argv[] )  
{  
    int i;  
    pthread_t pth1,pth2; 
    char test_str[256] = "Hello World";
    struct MyStruct ms;
    ms.age = 18;
    snprintf(ms.name,255,"张三");
    
    //初始化锁
    pthread_mutex_init( &g_mutex, NULL );  
    pthread_create( &pth1, NULL, thread1, (void *)test_str );  
    pthread_create( &pth2, NULL, thread2, (void *)&ms );  
    pthread_join( pth1, NULL );  
    pthread_join( pth2, NULL );  
    //销毁锁
    pthread_mutex_destroy( &g_mutex );  
    printf( "g_lock_var = %d\n", g_lock_var );  
    return 0;                              
}  

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值