pthread线程知识要点

线程之间通信的两个基本问题是互斥和同步。`

#include

一、pthread_create
1、函数原型
int pthread_create(pthread_t tidp,const pthread_attr_t attr,(void)(start_rtn)(void),void arg);

2、函数功能:
pthread_create是类Unix操作系统(Unix、Linux、Mac OS X等)的创建线程的函数。
它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。

返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于指定各种不同的线程属性。
新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,
如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。

3、返回值:
表示成功,返回0;表示出错,返回-1。

4、参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。

示例:
// 线程的运行函数
void say_hello(void args)
{
cout << "Hello Runoob!" << endl;
return 0;
}

int main()
{
    int indexes = 10;
    pthread_t tids;
    int ret = pthread_create(&tids, NULL, say_hello, (void *)&(indexe));
    pthread_exit(NULL);
}

二、pthread_join
1、函数定义:
int pthread_join(pthread_t thread, void **retval);
2、函数功能:
pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止

三、线程存在的问题和临界区
前面我们知道了怎么创建线程,下面我们再来看看这样一个实例,创建100个线程,它们都访问了同一变量,
其中一半对这个变量进行加1操作,一半进行减1操作,按道理其结果会等于0.

#include 
#include 
#include 
#include 
#define NUM_THREAD 100
void * thread_inc(void * arg);
void * thread_des(void * arg);
long long num = 0;   //long long类型是64位整数型,多线程共同访问

int main(int argc, char *argv[])
{
    pthread_t thread_id[NUM_THREAD];
    int i;

    //创建100个线程,一半执行thread_inc,一半执行thread_des
    for(i = 0; i < NUM_THREAD; i++)
    {
        if(i %2)
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
        else
            pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
    }

    //等待线程返回
    for (i = 0; i < NUM_THREAD; i++)
        pthread_join(thread_id[i], NULL);

    printf("result: %lld \n", num);  //+1,-1按道理结果是0
    return 0;
}

//线程入口函数1
void * thread_inc(void * arg)
{
    for (int i = 0; i < 50000000; i++)
        num += 1;//临界区(引起问题的语句就是临界区位置)
    return NULL;
}

//线程入口函数2
void * thread_des(void * arg)
{
    for (int i = 0; i < 50000000; i++)
        num -= 1;//临界区
    return NULL;
}

从运行结果看并不是0,而且每次运行的结果都不同。那这是什么原因引起的呢? 
是因为每个线程访问一个变量是这样一个过程:先从内存取出这个变量值到CPU,
然后CPU计算得到改变后的值,最后再将这个改变后的值写回内存。因此,我们可以很容易看出,
多个线程访问同一变量,如果某个线程还只刚从内存取出数据,还没来得及写回内存,这时其它线程又访问了这个变量,所以这个值就会不正确了。

3.1、接下来我们再来讲讲怎么解决这个问题:线程同步
    互斥量和信号量。

    互斥:
    互斥量技术从字面也可以理解,就是临界区有线程访问,其它线程就得排队等待,它们的访问是互斥的,实现方式就是给临界区加锁与释放锁。

    #include 

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);  //创建互斥量

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

    int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁

    int pthread_mutex_unlock(pthread_mutex_t *mutex);//释放锁
    临界区围住一定要lock和unlock一一对应。
    /*扩展临界区,减少加锁,释放锁调用次数,但这样变量必须加满到50000000次后其它线程才能访问.
    这样是延长了线程的等待时间,但缩短了加锁,释放锁函数调用的时间,这里没有定论,自己酌情考虑*/
    void * thread_inc(void * arg)
    {
        pthread_mutex_lock(&mutex);  //互斥量锁住
        for (int i = 0; i < 1000000; i++)
            num += 1;
        pthread_mutex_unlock(&mutex); //互斥量释放锁
        return NULL;
    }

    /*缩短了线程等待时间,但循环创建,释放锁函数调用时间增加*/
    void * thread_des(void * arg)
    {
        for (int i = 0; i < 1000000; i++)
        {
            pthread_mutex_lock(&mutex);
            num -= 1;
            pthread_mutex_unlock(&mutex);
        }
        return NULL;
    }

    信号量:
    号量与互斥量类似,只是互斥量是用锁来控制线程访问而信号量是用二进制0,1来完成控制线程顺序。
    sem_post信号量加1,sem_wait信号量减1,当信号量为0时,sem_wait就会阻断,因此通过这样让信号量加1减1就能控制线程的执行顺序了。

    #include 

    int sem_init(sem_t *sem, int pshared, unsigned int value);//创建信号量
    int sem_destroy(sem_t *sem);//销毁信号量
    int sem_post(sem_t *sem);//信号量加1
    int sem_wait(sem_t *sem);//信号量减1,为0时阻塞

    #include 
    #include 
    #include 

    void * read(void * arg);
    void * accu(void * arg);
    static sem_t sem_one;
    static sem_t sem_two;
    static int num;

    int main(int argc, char *argv[])
    {
        pthread_t id_t1, id_t2;
        sem_init(&sem_one, 0, 0);
        sem_init(&sem_two, 0, 1);

        pthread_create(&id_t1, NULL, read, NULL);
        pthread_create(&id_t2, NULL, accu, NULL);

        pthread_join(id_t1, NULL);
        pthread_join(id_t2, NULL);

        sem_destroy(&sem_one);
        sem_destroy(&sem_two);

        return 0;
    }

    void * read(void * arg)
    {
        int i;
        for (i = 0; i < 5; i++) {
            fputs("Input num: ", stdout);
            sem_wait(&sem_two);
            scanf("%d", &num);
            sem_post(&sem_one);
        }
        return NULL;
    }

    void * accu(void * arg)
    {
        int sum = 0 , i;
        for (i = 0; i < 5; i++) {
            sem_wait(&sem_one);
            sum+= num;
            sem_post(&sem_two);
        }
        printf("Result: %d \n", sum);
        return NULL;
    }

四.多线程并发服务端的实现