线程的基本函数   

  1.线程创建:

#include <pthread.h>

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

参数说明:

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

attr:用于设置线程的属性,一般不需要特殊的属性,所以可以简单地设置为NULL。

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

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

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

可以调用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类型的变量表示,可以这样初始化和销毁:

多线程编程基础_Linux

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

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

多线程编程基础_多线程_02

返回值:成功返回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(条件变量)

多线程编程基础_Linux_03

返回值:成功返回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的操作可以用下列函数:

多线程编程基础_Linux_04

多线程编程基础_编程基础_05

返回值:成功返回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相关的资源。

多线程编程基础_Linux_06

调用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数相关),但不能同时既有读者又有写者。

多线程编程基础_编程基础_07

多线程编程基础_多线程_08

多线程编程基础_多线程_09

多线程编程基础_多线程_10多线程编程基础_编程基础_11多线程编程基础_编程基础_11多线程编程基础_编程基础_11多线程编程基础_编程基础_11多线程编程基础_编程基础_11多线程编程基础_编程基础_11