多线程编程基础


线程的基本函数   多线程

  1.线程建立:并发

#include <pthread.h>ide

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

参数说明:高并发

thread:指向pthread_create类型的指针,用于引用新建立的线程。post

attr:用于设置线程的属性,通常不须要特殊的属性,因此能够简单地设置为NULL。spa

*(*start_routine)(void *):传递新线程所要执行的函数地址。线程

arg:新线程所要执行的函数的参数。指针

调用若是成功,则返回值是0,若是失败则返回错误代码。orm

能够调用pthread_self( )获取当前线程的id

 

2.线程终止

    若是须要只终止某个线程而不终止整个进程,能够有三种方法:

  • 从线程函数return。这种方法对主线程不适用,从main函数return至关于调用exit。

  • 一个线程能够调用pthread_cancel终止同一进程中的另外一个线程。

  • 线程能够调用pthread_exit终止本身。


#include <pthread.h>

void pthread_exit(void *retval);

参数说明:

retval:返回指针,指向线程向要返回的某个对象。

线程经过调用pthread_exit函数终止执行,并返回一个指向某对象的指针。注意:毫不能用它返回一个指向局部变量的指针,由于线程调用该函数后,这个局部变量就不存在了,这将引发严重的程序漏洞。

 

3.线程等待

#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

参数说明:

th:将要等待的张璐,线程经过pthread_create返回的标识符来指定。

thread_return:一个指针,指向另外一个指针,然后者指向线程的返回值。


有关分离线程

在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程可以被其余线程收回其资源和杀死。在被其余线程回收以前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其余线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

默认状况下,线程被建立成可结合的。为了不存储器泄漏,每一个可结合线程都应该要么被显示地回收,即调用pthread_join;要么经过调用pthread_detach函数被分离。若是一个可结合线程结束运行但没有被join,则它的状态相似于进程中的Zombie Process,即还有一部分资源没有被回收,因此建立线程者应该调用pthread_join来等待线程运行结束,并可获得线程的退出代码,回收其资源。因为调用pthread_join后,若是该线程没有运行结束,调用者会被阻塞。

线程同步与互斥

A.mutex (互斥量)

原子操做:要么都执行,要么都不执行

对于多线程的程序,访问冲突的问题是很广泛的,解决的办法是引入互斥锁(Mutex,MutualExclusiveLock),得到锁的线程能够完成“读-修改-写”的操做,而后释放锁给其它线程,没有得到锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操做组成一个原子操做,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行作这个操做。Mutex用pthread_mutex_t类型的变量表示,能够这样初始化和销毁:

wKioL1e4J4DjTg8OAABUEE9GrXs387.jpg

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

pthread_mutex_init函数对Mutex作初始化,参数attr设定Mutex的属性,若是attr为NULL则表示缺省属性,本章不详细介绍Mutex属性,感兴趣的读者能够参考[APUE2e]。用pthread_mutex_init函数初始化的Mutex能够用pthread_mutex_destroy销毁。若是Mutex变量是静态分配的(全局变量或static变量),也能够用宏定义PTHREAD_MUTEX_INITIALIZER操做能够用下列函数:

wKiom1e4J_TgrKuJAABRIIuDb6Y787.jpg

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

一个线程能够调用pthread_mutex_lock得到Mutex,若是这时另外一个线程已经调用pthread_mutex_lock得到了该Mutex,则当前线程须要挂起等待,直到另外一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能得到该Mutex并继续执行。若是一个线程既想得到锁,又不想挂起等待,能够调用pthread_mutex_trylock,若是Mutex已经被 另外一个线程得到,这个函数会失败返回EBUSY,而不会使线程挂起等待。

通常状况下,若是同一个线程前后两次调用lock,在第二次调用时,因为锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被本身占用着的,该线程又被挂起而没有机会释放锁,

所以就永远处于挂起等待状态了,这叫作死锁(Deadlock)。另外一种典型的死锁情形是这样:线程A得到了锁1,线程B得到了锁2,这时线程A调用lock试图得到锁2,结果是须要挂起等待线程B释放锁2,而这时线程B也调用lock试图得到锁1,结果是须要挂起等待线程A释放锁1,因而线程A和B都永远处于挂起状态了。不难想象,若是涉及到更多的线程和更多的锁,有没有可能死锁的问题将会变得复杂和难以判断。

B. Condition Variable(条件变量)

wKiom1e4KaSyJJG6AABOaA1xKvE344.jpg

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

和Mutex的初始化和销毁相似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。若是ConditionVariable是静态分配的,也能够用宏定义PTHEAD_COND_INITIALIZER初始化,至关于用pthread_cond_init函数初始化而且attr参数为NULL。Condition Variable的操做能够用下列函数:

wKioL1e4KkrDfcQHAABK8q1XcDs817.jpg

wKiom1e4KkqQ58fxAAAzgXthsqo137.jpg

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

可见,一个Condition Variable老是和一个Mutex搭配使用的。一个线程能够调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数作如下三步操做:

1. 释放Mutex

2. 阻塞等待

3. 当被唤醒时,从新得到Mutex并返回

c. Semaphore(信号量)

Mutex变量是非0即1的,可看做一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源,加锁时得到该资源,将Mutex减到0,表示再也不有可用资源,解锁时释放该资源,将Mutex从新加到1,表示又有了一个可用资源。semaphore变量的类型为sem_t,sem_init()初始化一个semaphore变量,value参数表示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程间同步,这里只介绍这种状况。在用完semaphore变量以后应该调用sem_destroy()释放与semaphore相关的资源。

wKioL1e4LnuBejb1AABI8zwgH4g096.jpg

调用sem_wait()能够得到资源(P操做),使semaphore的值减1,若是调用sem_wait()时semaphore的值已是0,则挂起等待。若是不但愿挂起等待,能够调用sem_trywait()。调用sem_post()能够释放资源(V操做),使semaphore的值加1,同时唤醒挂起等待的线程。

示例:

生产者-消费者模型,基于固定大小的环形队

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <semaphore.h>

#define _SEM_PRO_ 64
#define _SEM_CON_ 0

sem_t sem_product;
sem_t sem_consume;

pthread_mutex_t con_lock;
pthread_mutex_t pro_lock;

int bank[_SEM_PRO_];

void* producter(void* _val)
{
    int index_pro = 0;
    while(1)
    {
        sem_wait(&sem_product);
        pthread_mutex_lock(&pro_lock);

        int _product = rand()%1000;
        bank[index_pro] = _product;
        printf("product1 done...val is : %d \n", _product);

        pthread_mutex_unlock(&pro_lock);
        sem_post(&sem_consume);

        ++index_pro;
        index_pro = index_pro%_SEM_PRO_;

        //sleep(1);
    }
}

void* producter2(void* _val)
{
    int index_pro = 0;
    while(1)
    {
        sem_wait(&sem_product);
        pthread_mutex_lock(&pro_lock);
        
        int _product = rand()%1000;
        bank[index_pro] = _product;
        printf("product2 done...val is : %d \n", _product);

        pthread_mutex_unlock(&pro_lock);
        sem_post(&sem_consume);

        ++index_pro;
        index_pro = index_pro%_SEM_PRO_;

        //sleep(1);
    }
}

void* consumer(void* _val)
{
    int index_con = 0;
    while(1)
    {

        sem_wait(&sem_consume);
        pthread_mutex_lock(&con_lock);

        int _consume = bank[index_con];
        printf("consume1 done...val is : %d\n", _consume);

        pthread_mutex_unlock(&con_lock);
        sem_post(&sem_product);
        
        ++index_con;
        index_con = index_con%_SEM_PRO_;

        //sleep(1);
    }
}

void* consumer2(void* _val)
{
    int index_con = 0;
    while(1)
    {
        sem_wait(&sem_consume);
        pthread_mutex_lock(&con_lock);

        int _consume = bank[index_con];
        printf("consume2 done...val is : %d\n", _consume);

        pthread_mutex_unlock(&con_lock);
        sem_post(&sem_product);
        
        ++index_con;
        index_con = index_con%_SEM_PRO_;

        //sleep(1);
    }
}

void run_product_consume()
{
    pthread_t tid_pro;
    pthread_t tid_con;
    pthread_t tid_con2;
    pthread_t tid_pro2;

    pthread_create(&tid_pro, NULL, producter, NULL);
    pthread_create(&tid_con, NULL, consumer, NULL);
    pthread_create(&tid_pro2, NULL, producter2, NULL);
    pthread_create(&tid_con2, NULL, consumer2, NULL);

    pthread_join(tid_pro, NULL);
    pthread_join(tid_con, NULL);
    pthread_join(tid_pro2, NULL);
    pthread_join(tid_con2, NULL);
}

void destroy_all_sem()
{
    printf("process done...\n");

    sem_destroy(&sem_product);
    sem_destroy(&sem_consume);

    pthread_mutex_destroy(&con_lock);
    pthread_mutex_destroy(&pro_lock);

    exit(0);
}

void init_all_sem()
{
    signal(2, destroy_all_sem);
    memset(bank, 0, sizeof(bank));

    sem_init(&sem_product, 0, _SEM_PRO_);
    sem_init(&sem_consume, 0, _SEM_CON_);

    pthread_mutex_init(&con_lock, NULL);
    pthread_mutex_init(&pro_lock, NULL);
}

int main()
{

    init_all_sem();
    run_product_consume();

    return 0;
}

d. 读写锁


在编写多线程的时候,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。一般而言,在读的过程当中,每每伴随着查找的操做,中间耗时很长。给这种代码段加锁,会极大地下降咱们程序的效率。所以,读写锁有派上了用场。

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分红读者和写者,读者只对共享资源进行读访问,写者则须要对共享资源进行写操做。这种锁相对于自旋锁而言,能提升并发性,由于在多处理器系统中,它容许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

wKioL1e4MS2R6YkgAAA52qnS9lU219.jpg

wKiom1e4MS7DW0wyAAAmJYT8fck967.jpg

wKioL1e4MS7iJgKxAABICiL23wk221.jpg

wKiom1e4MS7wLMXeAAA5kHn_CgU628.jpgj_0028.gifj_0028.gifj_0028.gifj_0028.gifj_0028.gifj_0028.gif

相关文章
相关标签/搜索