Linux - pthread

pthread(POSIX thread)库是个标准C/C++多线程API库,线程没有父子之分。与标准 fork() 相比,线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的 CPU 时间,使得线程创建比新进程创建快上十到一百倍。因为这一点,可以大量使用线程而无需太过于担心带来的 CPU 或内存不足。

1. 多线程基础

  • 线程的操作包括:创建、终止、同步、调度、数据管理和进程交互
  • 主线程创建多个线程后,既不维护一个线程列表,也不知道谁创建了它,没有父子之分
  • 所有的线程共享进程的同样的地址空间
  • 同一进程下的线程共享:进程的指令、进程数据、文件句柄、信号和信号处理、当前的工作目录和用户和组ID
  • 每个线程特有的:线程ID、寄存器集和栈指针、本地变量栈和返回地址、信号掩码、优先级和返回的ERRONO(成功为0)
  • 线程正常退回为0

1.1 线程的创建和销毁

int pthread_create(
                    pthread_t* thread, //threadId
                    const pthread_attr_t * attr,  
                    void* (*start_routine)(void *), //thread_function
                    void* arg //thread_function's parameter
                  );

参数:

  • thread - 线程的唯一ID (unsigned long int defined in bits/pthreadtypes.h)
  • attr - 设置线程属性,参考:sys/types.h,默认为NULL。线程属性包括:
    • 线程的分离状态属性 (joinable? 缺省: PTHREAD_CREATE_JOINABLE. 其他: PTHREAD_CREATE_DETACHED)
    • 调度策略 (real-time? PTHREAD_INHERIT_SCHED,PTHREAD_EXPLICIT_SCHED,SCHED_OTHER)
    • 调度参数
    • 继承属性 (Default: PTHREAD_EXPLICIT_SCHED Inherit from parent thread: PTHREAD_INHERIT_SCHED)
    • 有效范围 (Kernel threads: PTHREAD_SCOPE_SYSTEM User threads: PTHREAD_SCOPE_PROCESS Pick one or the other not both.)
    • 线程栈末尾的警戒缓冲区大小(字节数)
    • 线程栈的最低地址 (See unistd.h and bits/posix_opt.h _POSIX_THREAD_ATTR_STACKADDR)
    • 栈大小 (default minimum PTHREAD_STACK_SIZE set in pthread.h),
  • void * (*start_routine) - 线程启动函数,start_routine接受 void * 作为参数,同时返回值的类型也是 void *。这表明可以用 void * 向新线程传递任意类型的数据,新线程完成时也可返回任意类型的数据。
  • *arg - 函数指针参数. 要传递多个参数,可以打包成一个结构体

一个 POSIX 线程的简单示例程序:

#include 
#include 
#include 

void* run(void* msg)
{
    sleep(20);
    printf("%s\n", msg);
}

int main()
{
    pthread_t pid1, pid2; //unsigned long int
    int iret1 = 0, iret2 = 0;
    pthread_create(&pid1, NULL, run, "Hello, pthread! Good try 1");
    if(iret1)
    {
        fprintf(stderr,"Error - pthread_create() return code: %d\n",iret1);
        exit(EXIT_FAILURE);
    }

    pthread_create(&pid2, NULL, run, "Hello, pthread! Good try 2");
    if(iret2)
    {
        fprintf(stderr,"Error - pthread_create() return code: %d\n",iret1);
        exit(EXIT_FAILURE);
    }
    printf("pthread_create() for thread 1 returns: %d\n",iret1);
    printf("pthread_create() for thread 2 returns: %d\n",iret2);

    if ( pthread_join(pid1, NULL) || pthread_join(pid2, NULL) ) {
        printf("error joining thread.");
        abort();
    }

    return 0;
}

问题:

  • 新线程创建之后主线程如何运行?答案:主线程按顺序继续执行下一行程序(本例中执行 "if (pthread_join(...))")。
  • 新线程结束时如何处理?答案:新线程先停止,然后作为其清理过程的一部分,等待与另一个线程合并或“连接”。
  • pthread_create() 将一个线程拆分为两个, pthread_join() 将两个线程合并为一个线程。pthread_join() 的第一个参数是 threadId。第二个参数是void*,如果 void 指针不为 NULL,pthread_join 将线程的 void * 返回值放置在指定的位置上。

上例thread_function() 花了 20 秒才完成。在 thread_function() 结束很久之前,主线程就已经调用了 pthread_join()。如果发生这种情况,主线程将中断(转向睡眠)然后等待 thread_function() 完成。当 thread_function() 完成后, pthread_join() 将返回。这时程序又只有一个主线程。当程序退出时,所有新线程已经使用 pthread_join() 合并了。这就是应该如何处理在程序中创建的每个新线程的过程。如果没有合并一个新线程,则它仍然对系统的最大线程数限制不利。这意味着如果未对线程做正确的清理,最终会导致 pthread_create() 调用失败。

2. 互斥对象(Mutex)

场景:示例2

#include 
#include 
#include 

enum {MAX_THREAD_NUMBER = 3}; //C语言const不是常量的意思,不能用const定义
int number = 0;
pthread_t pid[MAX_THREAD_NUMBER] = {0};
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;

void* Counter(void* nth)
{
    int count = 10;
   
    //pthread_mutex_lock(&mymutex);
    int local = number;
    for (count = 0; count < 10; count++)
    {
        local++;
        printf("Counter: %d, thread id : %d, thread number :%ld\n", local, *(int*)nth, pthread_self());
    }
    number = local;
    //pthread_mutex_unlock(&mymutex);
   
}

int main()
{
    int i = 0;
    for(i = 0; i < MAX_THREAD_NUMBER; i++)
    {
        pthread_create(&pid[i], NULL, &Counter, (void*)&i);
    }

    for(i = 0; i < MAX_THREAD_NUMBER; i++)
    {
        pthread_join(pid[i], NULL);
    }


    return 0;
}

编译上面的代码,我们发现多线程并没有累加全局number,是因为这个变量被各自线程局部化了,如何共享全局变量呢?使用互斥对象

两个线程不能同时对同一个互斥对象(Mutex)加锁。

锁定了互斥对象的线程能够存取复杂的数据结构,而不必担心同时会有其它线程干扰直到互斥对象被解锁为止。

互斥对象是这样工作的:如果线程 a 试图锁定一个互斥对象,而此时线程 b 已锁定了同一个互斥对象时,线程 a 就将进入睡眠状态。一旦线程 b 释放了互斥对象(通过 pthread_mutex_unlock() 调用),线程 a 就能够锁定这个互斥对象(换句话说,线程 a 就将从 pthread_mutex_lock() 函数调用中返回,同时互斥对象被锁定)。同样地,当线程 a 正锁定互斥对象时,如果线程 c 试图锁定互斥对象的话,线程 c 也将临时进入睡眠状态。对已锁定的互斥对象上调用 pthread_mutex_lock() 的所有线程都将进入睡眠状态,这些睡眠的线程将“排队”访问这个互斥对象。

2.1 互斥对象初始

pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init( pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr)

以上2中方式,一种是静态初始化,另外一种是动态初始化。

一旦使用 pthread_mutex_init() 初始化了互斥对象,就应使用 pthread_mutex_destroy() 消除它。pthread_mutex_destroy() 接受一个指向 pthread_mutext_t 的指针作为参数,并释放创建互斥对象时分配给它的任何资源。请注意, pthread_mutex_destroy() 不会 释放用来存储 pthread_mutex_t 的内存。释放自己的内存完全取决于您。还必须注意一点,pthread_mutex_init() 和 pthread_mutex_destroy() 成功时都返回零。

2.2 锁定

pthread_mutex_lock(pthread_mutex_t *mutex)

pthread_mutex_lock() 接受一个指向互斥对象的指针作为参数以将其锁定。如果碰巧已经锁定了互斥对象,调用者将进入睡眠状态。函数返回时,将唤醒调用者(显然)并且调用者还将保留该锁。函数调用成功时返回零,失败时返回非零的错误代码。

pthread_mutex_unlock(pthread_mutex_t *mutex)

pthread_mutex_unlock() 与 pthread_mutex_lock() 相配合,它把线程已经加锁的互斥对象解锁。始终应该尽快对已加锁的互斥对象进行解锁(以提高性能)。并且绝对不要对您未保持锁的互斥对象进行解锁操作(否则,pthread_mutex_unlock() 调用将失败并带一个非零的 EPERM 返回值)。

pthread_mutex_trylock(pthread_mutex_t *mutex)

当线程正在做其它事情的时候(由于互斥对象当前是锁定的),如果希望锁定互斥对象,这个调用就相当方便。调用 pthread_mutex_trylock() 时将尝试锁定互斥对象。如果互斥对象当前处于解锁状态,那么您将获得该锁并且函数将返回零。然而,如果互斥对象已锁定,这个调用也不会阻塞。当然,它会返回非零的 EBUSY 错误值。然后可以继续做其它事情,稍后再尝试锁定。

2.3 线程的内部状态

2.3.1 执行顺序

假设主线程将创建三个新线程:线程 a、线程 b 和线程 c。假定首先创建线程 a,然后是线程 b,最后创建线程 c。

pthread_create( &thread_a, NULL, thread_function, NULL);
pthread_create( &thread_b, NULL, thread_function, NULL);
pthread_create( &thread_c, NULL, thread_function, NULL);

在第一个 pthread_create() 调用完成后,可以假定线程 a 不是已存在就是已结束并停止。第二个 pthread_create() 调用后,主线程和线程 b 都可以假定线程 a 存在(或已停止)。

然而,就在第二个 create() 调用返回后,主线程无法假定是哪一个线程(a 或 b)会首先开始运行。虽然两个线程都已存在,线程 CPU 时间片的分配取决于内核和线程库。至于谁将首先运行,并没有严格的规则。尽管线程 a 更有可能在线程 b 之前开始执行,但这并无保证。对于多处理器系统,情况更是如此。如果编写的代码假定在线程 b 开始执行之前实际上执行线程 a 的代码,那么,程序最终正确运行的概率是 99%。或者更糟糕,程序在您的机器上 100% 地正确运行,而在您客户的四处理器服务器上正确运行的概率却是零。

2.3.2 全局变量引用

现在来看另一个假想的例子。假设有许多线程,他们都正在执行下列代码:

number = number + 1;

如果示例2没有采用线程的局部变量,而是直接用全局的nubmer进行自增,那么需要考虑用pthread_mutex_lock() 和 pthread_mutex_unlock() 调用吗?答案是:最好要!也许有人会说“不”。编译器极有可能把上述赋值语句编译成一条机器指令。大家都知道,不可能"半途"中断一条机器指令。即使是硬件中断也不会破坏机器指令的完整性。基于以上考虑,很可能倾向于完全省略 pthread_mutex_lock() 和 pthread_mutex_unlock() 调用。不要这样做,原因:使用单条内嵌汇编操作码在单处理器系统上可能不会有什么问题。每个加一操作都将完整地进行,并且多半会得到期望的结果。但是多处理器系统则截然不同。在多 CPU 机器上,两个单独的处理器可能会在几乎同一时刻(或者,就在同一时刻)执行上述赋值语句。不要忘了,这时对内存的修改需要先从 L1 写入 L2 高速缓存、然后才写入主存。(SMP 机器并不只是增加了处理器而已;它还有用来仲裁对 RAM 存取的特殊硬件。)最终,根本无法搞清在写入主存的竞争中,哪个 CPU 将会"胜出"。要产生可预测的代码,应使用互斥对象。互斥对象将插入一道"内存关卡",由它来确保对主存的写入按照线程锁定互斥对象的顺序进行。

场景:如果互斥对象锁住的数据结构(链表)为空,那么获得锁的线程将什么操作都不做,然后释放锁,其他线程重复这个操作将会浪费CPU资源?那有什么方法能提高效率呢,此事,我们引入条件变量。

3. 条件变量

pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心。

锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。

3.1 pthread_cond_wait() 的内部故事:

第一个线程首先调用:pthread_mutex_lock(&mymutex);

然后,它检查了列表。没有找到感兴趣的东西,于是它调用:pthread_cond_wait(&mycond, &mymutex);

然后,pthread_cond_wait() 调用在返回前执行许多操作:pthread_mutex_unlock(&mymutex);

它对 mymutex 解锁,然后进入睡眠状态,等待 mycond 以接收 POSIX 线程“信号”。一旦接收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自 pthread_cond_signal() 或 pthread_cond_broadcast() 调用的信号),它就会苏醒。但 pthread_cond_wait() 没有立即返回 -- 它还要做一件事:重新锁定 mutex:pthread_mutex_lock(&mymutex)

pthread_cond_wait() 知道我们在查找 mymutex “背后”的变化,因此它继续操作,为我们锁定互斥对象,然后才返回。

3.2 初始化和清除

定义:pthread_cond_t mycond;
初始化:pthread_cond_init(&mycond,NULL);
销毁:pthread_cond_destroy(&mycond);

3.3 等待

pthread_cond_wait(&mycond, &mymutex);

请注意,代码在逻辑上应该包含 mycond 和 mymutex。一个特定条件只能有一个互斥对象,而且条件变量应该表示互斥数据“内部”的一种特殊的条件更改。一个互斥对象可以用许多条件变量(例如,cond_empty、cond_full、cond_cleanup),但每个条件变量只能有一个互斥对象。

pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改共享资源),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改共享资源。此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。

现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并修改了共享资源。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将苏醒。

现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。

简而言之:如果pthread_cond_wait() 函数不传递这个互斥量参数,那么解锁互斥量只能等到线程阻塞被唤醒从pthread_cond_wait() 函数返回,而唤醒又需要条件改变调用pthread_cond_wait() 函数,条件改变的前提是获得那个保护条件的互斥量的锁,而互斥量解锁又得等到阻塞的线程被唤醒。。死锁了。所以需要传递它使得互斥量的解锁在函数内进行。

3.4 发送信号和广播

对于发送信号和广播,需要注意一点。如果线程更改某些共享数据,而且它想要唤醒所有正在等待的线程,则应使用 pthread_cond_broadcast 调用,如下所示:pthread_cond_broadcast(&mycond);

在某些情况下,活动线程只需要唤醒第一个正在睡眠的线程。假设您只对队列添加了一个工作作业。那么只需要唤醒一个工作程序线程:pthread_cond_wait() ;

#include 
#include 
#include 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *fun(void *arg)
{
    int *var_p = arg;
    pthread_mutex_lock(&mutex);
    while(*var_p == 0) {
        pthread_cond_wait(&cond, &mutex); // wait
    }
    pthread_mutex_unlock(&mutex);
    puts("ok");
}
int main()
{
    int var = 0;
    pthread_t pid;
    pthread_create(&pid, NULL, fun, &var);
    getchar();
    pthread_mutex_lock(&mutex);
    var = 1;
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cond); // signal
    pthread_join(pid, NULL);
    return 0;
}

4. 线程本地变量(线程存储)

线程局部存储__thread和线程特有存储pthread_key_create

在一个进程中定义的全局或静态变量都是所有线程可见的,即每个线程共同操作一块存储区域。而有时我们可能有这样的需求:对于一个全局变量,每个线程对其的修改只在本线程内有效,各线程之间互不干扰。即每个线程虽然共享这个全局变量的名字,但这个变量的值就像只有在本线程内才会被修改和读取一样。
线程局部存储和线程特有数据都可以实现上述需求。

4.1. 线程局部存储

线程局部存储提供了持久的每线程存储,每个线程都拥有一份对变量的拷贝。线程局部存储中的变量将一直存在,直到线程终止,届时会自动释放这一存储。一个典型的例子就是errno的定义(uClibc-0.9.32),每个线程都有自己的一份errno的拷贝,防止了一个线程获取errno时被其他线程干扰。
要定义一个线程局部变量很简单,只需简单的在全局或静态变量的声明中包含__thread说明符即可。例如:

static __thread int buf[MAX_ERROR_LEN];

这样定义的变量,在一个线程中只能看到本线程对其的修改。
关于线程局部变量的声明和使用,需要注意以下几点:

  1. 如果变量声明中使用了关键字static或extern,那么关键字__thread必须紧随其后。
  2. 与一般的全局或静态变量声明一样,线程局部变量在声明时可以设置一个初始值。
  3. 可以使用C语言取址操作符(&)来获取线程局部变量的地址。

在一个线程中修改另一个线程的局部变量:
__thread变量并不是在线程之间完全隐藏的,每个线程保存自己的一份拷贝,因此每个线程的这个变量的地址不同。但这个地址是整个进程可见的,因此一个线程获得另外一个线程的局部变量的地址,就可以修改另一个线程的这个局部变量。

C++中对__thread变量的使用有额外的限制:

  1. 在C++中,如果要在定义一个thread-local变量的时候做初始化,初始化的值必须是一个常量表达式。
  2. __thread只能修饰POD类型,即不带自定义的构造、拷贝、赋值、析构的类型,不能有non-static的protected或private成员,没有基类和虚函数,因此对定义class做了很多限制。但可以改为修饰class指针类型便无需考虑此限制。

4.2. 线程特有数据

上面是C/C++语言实现每线程变量的方式,而POSIX thread使用getthreadspecific和setthreadspecific 组件来实现这一特性,因此编译要加-pthread,但是使用这种方式使用起来很繁琐,并且效率很低。不过我也简单讲一下用法。
使用线程特有数据需要下面几步:

Thread Local变量的具体用法。

  • 创建一个类型为 pthread_key_t 类型的变量。
  • 调用 pthread_key_create() 来创建该变量。该函数有两个参数,第一个参数就是上面声明的 pthread_key_t 变量,第二个参数是一个清理函数,用来在线程释放该线程存储的时候被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。
  • 当线程中需要存储特殊值的时候,可以调用 pthread_setspcific() 。该函数有两个参数,第一个为前面声明的 pthread_key_t 变量,第二个为 void* 变量,这样你可以存储任何类型的值。
  • 如果需要取出所存储的值,调用 pthread_getspecific() 。该函数的参数为前面提到的 pthread_key_t 变量,该函数返回 void * 类型的值。

函数的原型:

int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

5. Gcc内置原子操作__sync_系列函数简述及例程

http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html

函数声明 

Gcc 4.1.2版本之后,对X86或X86_64支持内置原子操作。就是说,不需要引入第三方库(如pthread)的锁保护,即可对1、2、4、8字节的数值或指针类型,进行原子加/减/与/或/异或等操作。有了这套内置原子操作函数,写程序方便很多。老宋根据Gcc手册中《Using the GNU Compiler Collection (GCC)》章节内容,将__sync_系列17个函数声明整理简化如下:

type __sync_fetch_and_add (type *ptr, type value, ...) // 将value加到*ptr上,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_sub (type *ptr, type value, ...) // 从*ptr减去value,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_or (type *ptr, type value, ...) // 将*ptr与value相或,结果更新到*ptr, 并返回操作之前*ptr的值
type __sync_fetch_and_and (type *ptr, type value, ...) // 将*ptr与value相与,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_xor (type *ptr, type value, ...) // 将*ptr与value异或,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_nand (type *ptr, type value, ...) // 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_add_and_fetch (type *ptr, type value, ...) // 将value加到*ptr上,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_sub_and_fetch (type *ptr, type value, ...) // 从*ptr减去value,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_or_and_fetch (type *ptr, type value, ...) // 将*ptr与value相或, 结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_and_and_fetch (type *ptr, type value, ...) // 将*ptr与value相与,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_xor_and_fetch (type *ptr, type value, ...) // 将*ptr与value异或,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_nand_and_fetch (type *ptr, type value, ...) // 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之后新*ptr的值
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...) // 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回true
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...) // 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回操作之前*ptr的值
__sync_synchronize (...) // 发出完整内存栅栏
type __sync_lock_test_and_set (type *ptr, type value, ...) // 将value写入*ptr,对*ptr加锁,并返回操作之前*ptr的值。即,try spinlock语义
void __sync_lock_release (type *ptr, ...) // 将0写入到*ptr,并对*ptr解锁。即,unlock spinlock语义

6. 生产者和消费者案例

生产者生产同一类型的商品放到仓库,多个消费者随机的不停消费

Linux - pthread_第1张图片

#include 
#include 
#include 
#include 
#include 
#include 

#define BUFF_SIZE 16    // 缓存区大小
#define CONSUMER_NUM 3    // 消费者人数

int isStop = 0;        // 判断停止线程

struct prodcons
{
    int buffer[BUFF_SIZE];
    pthread_mutex_t lock;
    int readpos,writepos;
    pthread_cond_t notEmpty;
    pthread_cond_t notFull;
}m_prodconsStruct;

typedef struct prodcons prodcons_t;

void initial(struct prodcons* shareStruct)
{
    pthread_mutex_init(&shareStruct->lock,NULL);
    pthread_cond_init(&shareStruct->notEmpty,NULL);
    pthread_cond_init(&shareStruct->notFull,NULL);
    shareStruct->readpos=0;
    shareStruct->writepos=0;
    memset(shareStruct->buffer, -1, sizeof(int)*BUFF_SIZE); // -1 表示未写入数据
}

void writeIn(prodcons_t *shareStruct,int data)
{
    pthread_mutex_lock(&shareStruct->lock);

    /*
       线程挂起条件:
       对写指针而言,缓存是环形。
       1.写指针追上读指针表示缓存被写满,此时不可再写,否则会覆盖未读数据;
       2.收到线程停止指令;
       此处是对已经读过的缓存清零以便下次写时判断是否是写满状态,另一方案读写指针用一直累加的方法判断谁前谁后.
       */
    while((shareStruct->writepos == shareStruct->readpos)
            && (shareStruct->buffer[(shareStruct->writepos+1)%BUFF_SIZE] != -1)
            && !isStop)
    {
        printf("wait for empty space\n");
        pthread_cond_wait(&shareStruct->notFull,&shareStruct->lock);
    }

    if(isStop)
    {
        pthread_mutex_unlock(&shareStruct->lock);
        pthread_exit((void *)2);
    }

    shareStruct->buffer[shareStruct->writepos % BUFF_SIZE] = data;
    printf("writeIn----->%d\n",data);
    shareStruct->writepos++;
    if(shareStruct->writepos >= BUFF_SIZE)
        shareStruct->writepos = 0; // 缓存存满后,从头开始再存

    pthread_cond_signal(&shareStruct->notEmpty);
    pthread_mutex_unlock(&shareStruct->lock);
}

int readOut(prodcons_t *shareStruct, int threadNum)
{
    pthread_mutex_lock(&shareStruct->lock);

    while((shareStruct->readpos == shareStruct->writepos)
            && (shareStruct->buffer[(shareStruct->readpos+1)%BUFF_SIZE] == -1)
            && !isStop)
    {
        printf("wait for data, Consumer Number %d\n", threadNum);
        pthread_cond_wait(&shareStruct->notEmpty, &shareStruct->lock);
    }

    if(isStop)
    {
        pthread_mutex_unlock(&shareStruct->lock);
        exit(EXIT_FAILURE);
        //pthread_exit((void *)2);
    }

    int data = shareStruct->buffer[shareStruct->readpos % BUFF_SIZE];
    printf("Consumer Number %d thread : 0x%0x and data = %d\n",threadNum,pthread_self(), data);
    shareStruct->buffer[shareStruct->readpos % BUFF_SIZE] = -1;    // 读之后清数据
    shareStruct->readpos++;
    if(shareStruct->readpos >= BUFF_SIZE)
        shareStruct->readpos=0; // 缓存读完后,从头再读

    pthread_cond_signal(&shareStruct->notFull);
    pthread_mutex_unlock(&shareStruct->lock);
    return data;
}

void* producer(void* arg)
{
    int data = 0;
    while(1)
    {
        writeIn(&m_prodconsStruct, data);
        data++;
    }

    return NULL;
}

void* consumer(void* threadNum)
{
    while(1)    // C语言无bool类型,也就无true和false
    {
        readOut(&m_prodconsStruct,*(int*)threadNum);
    }

    return NULL;
}

int main()
{
    int err = -1, i=0;
    void* ret;
    pthread_t pid_pro;
    pthread_t pid_con[CONSUMER_NUM];

    // 初始化共享结构体
    initial(&m_prodconsStruct);

    // 创建生产者和消费者
    pthread_create(&pid_pro,NULL,producer, NULL);
    for(i=0; i < CONSUMER_NUM; i++)
    {
        pthread_create(&pid_con[i],NULL,consumer,(void*)&i);
    }


    // 延迟-----觉得线程能跑多久
    struct timeval timeout;
    timeout.tv_sec = 0;    // 1s
    timeout.tv_usec = 1000;
    select(0, NULL, NULL, NULL, &timeout);

    // 销毁生产者消费者线程
    isStop = 1;
    pthread_cond_broadcast(&m_prodconsStruct.notEmpty);
    pthread_cond_broadcast(&m_prodconsStruct.notFull);

    err = pthread_join(pid_pro,&ret);
    if(err != 0)
        printf("can not join producter thread :0x%0x\n", pid_pro);
    else
        printf("exit producter thread :0x%0x, exit code: %d\n", pid_pro, *(int*)ret);

    for(i = 0; i < CONSUMER_NUM; i++)
    {
        err = pthread_join(pid_con[i],&ret);
        if(err != 0)
            printf("can not join consumer thread :0x%0x\n", pid_con[i]);
        else
            printf("exit consumer thread :0x%0x, exit code: %d\n", pid_con[i], *(int*)ret);
    }

    printf("end...\n");
    exit(0);
}

5. 参考

  • IBM多线程:https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html
  • 快速上手:https://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/pthreads.html
  • API列表: https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html
int   pthread_attr_destroy(pthread_attr_t *);
int   pthread_attr_getdetachstate(const pthread_attr_t *, int *);
int   pthread_attr_getguardsize(const pthread_attr_t *, size_t *);
int   pthread_attr_getinheritsched(const pthread_attr_t *, int *);
int   pthread_attr_getschedparam(const pthread_attr_t *,
          struct sched_param *);
int   pthread_attr_getschedpolicy(const pthread_attr_t *, int *);
int   pthread_attr_getscope(const pthread_attr_t *, int *);
int   pthread_attr_getstackaddr(const pthread_attr_t *, void **);
int   pthread_attr_getstacksize(const pthread_attr_t *, size_t *);
int   pthread_attr_init(pthread_attr_t *);
int   pthread_attr_setdetachstate(pthread_attr_t *, int);
int   pthread_attr_setguardsize(pthread_attr_t *, size_t);
int   pthread_attr_setinheritsched(pthread_attr_t *, int);
int   pthread_attr_setschedparam(pthread_attr_t *,
          const struct sched_param *);
int   pthread_attr_setschedpolicy(pthread_attr_t *, int);
int   pthread_attr_setscope(pthread_attr_t *, int);
int   pthread_attr_setstackaddr(pthread_attr_t *, void *);
int   pthread_attr_setstacksize(pthread_attr_t *, size_t);
int   pthread_cancel(pthread_t);
void  pthread_cleanup_push(void*), void *);
void  pthread_cleanup_pop(int);
int   pthread_cond_broadcast(pthread_cond_t *);
int   pthread_cond_destroy(pthread_cond_t *);
int   pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *);
int   pthread_cond_signal(pthread_cond_t *);
int   pthread_cond_timedwait(pthread_cond_t *, 
          pthread_mutex_t *, const struct timespec *);
int   pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *);
int   pthread_condattr_destroy(pthread_condattr_t *);
int   pthread_condattr_getpshared(const pthread_condattr_t *, int *);
int   pthread_condattr_init(pthread_condattr_t *);
int   pthread_condattr_setpshared(pthread_condattr_t *, int);
int   pthread_create(pthread_t *, const pthread_attr_t *,
          void *(*)(void *), void *);
int   pthread_detach(pthread_t);
int   pthread_equal(pthread_t, pthread_t);
void  pthread_exit(void *);
int   pthread_getconcurrency(void);
int   pthread_getschedparam(pthread_t, int *, struct sched_param *);
void* pthread_getspecific(pthread_key_t); 
int   pthread_join(pthread_t, void **);
int   pthread_key_create(pthread_key_t *, void (*)(void *));
int   pthread_key_delete(pthread_key_t);
int   pthread_mutex_destroy(pthread_mutex_t *);
int   pthread_mutex_getprioceiling(const pthread_mutex_t *, int *);
int   pthread_mutex_init(pthread_mutex_t *, const pthread_mutexattr_t *);
int   pthread_mutex_lock(pthread_mutex_t *);
int   pthread_mutex_setprioceiling(pthread_mutex_t *, int, int *);
int   pthread_mutex_trylock(pthread_mutex_t *);
int   pthread_mutex_unlock(pthread_mutex_t *);
int   pthread_mutexattr_destroy(pthread_mutexattr_t *);
int   pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *,
          int *);
int   pthread_mutexattr_getprotocol(const pthread_mutexattr_t *, int *);
int   pthread_mutexattr_getpshared(const pthread_mutexattr_t *, int *);
int   pthread_mutexattr_gettype(const pthread_mutexattr_t *, int *);
int   pthread_mutexattr_init(pthread_mutexattr_t *);
int   pthread_mutexattr_setprioceiling(pthread_mutexattr_t *, int);
int   pthread_mutexattr_setprotocol(pthread_mutexattr_t *, int);
int   pthread_mutexattr_setpshared(pthread_mutexattr_t *, int);
int   pthread_mutexattr_settype(pthread_mutexattr_t *, int);
int   pthread_once(pthread_once_t *, void (*)(void));
int   pthread_rwlock_destroy(pthread_rwlock_t *);
int   pthread_rwlock_init(pthread_rwlock_t *,
          const pthread_rwlockattr_t *);
int   pthread_rwlock_rdlock(pthread_rwlock_t *);
int   pthread_rwlock_tryrdlock(pthread_rwlock_t *);
int   pthread_rwlock_trywrlock(pthread_rwlock_t *);
int   pthread_rwlock_unlock(pthread_rwlock_t *);
int   pthread_rwlock_wrlock(pthread_rwlock_t *);
int   pthread_rwlockattr_destroy(pthread_rwlockattr_t *);
int   pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *,
          int *);
int   pthread_rwlockattr_init(pthread_rwlockattr_t *);
int   pthread_rwlockattr_setpshared(pthread_rwlockattr_t *, int);
pthread_t
      pthread_self(void);
int   pthread_setcancelstate(int, int *);
int   pthread_setcanceltype(int, int *);
int   pthread_setconcurrency(int);
int   pthread_setschedparam(pthread_t, int ,
          const struct sched_param *);
int   pthread_setspecific(pthread_key_t, const void *);
void  pthread_testcancel(void);

你可能感兴趣的:(Multithread)