linux---线程学习

本文以POSIX标准为依据,介绍线程创建和执行流程,如使用pthread_create函数。阐述线程终止的三种方式,包括pthread_exit等。分析线程存在的问题和临界区,强调线程同步的重要性,并介绍互斥量和信号量两种同步机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

目录

线程的创建和执行流程

pthread_create

线程终止

pthread_exit

pthread_join

pthread_cancel

线程存在的问题和临界区

线程同步

互斥量

信号量


POSIX是Portable Operating System Interface for Computer Environment(适用于计算机环境的可移植操作系统接口)的简写,是为了提高UNIX系列操作系统间的移植性而制定的API规范。下面要介绍的线程创建方法也是以POSIX标准为依据的。

线程的创建和执行流程

线程具有单独的执行流,因此需要单独定义线程的main函数,还需要请求操作系统在单独的执行流中执行该函数。完成该功能的函数如下。

pthread_create

#include <pthread.h>

int pthread_create(pthread_t * restrict thread, const pthread_attr_t * restrict attr, void *(* start_routine)(void *), void * restrict arg);
成功时返回0,失败时返回其他值。

thread-保存新创建线程ID的变量地址值。线程与与进程相同,也需要区分不同线程的ID。
attr-用于传递线程属性的参数,传递NULL时,创建默认属性的线程。
start_routine-相当于线程的main函数的、在单独执行流中执行的函数地址值(函数指针)。
arg-通过start_routine传递调用函数时包含参数信息的变量地址值。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
void* thread_main(void* arg);
char* get_time();
int main(int argc, char* argv[])
{
    pthread_t t_id;
    int thread_param = 5;
    printf("the current time:%s",get_time());
    puts("start of main");

    if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param))
    {
        puts("pthread_create() error");
        return -1;
    }

    sleep(10);
    printf("the current time:%s",get_time());
    puts("end of main");
    return 0;
}

void* thread_main(void* arg)
{
    int i = 0;
    int cnt = *((int*)arg);
    for(; i < cnt; i++)
    {
        sleep(1);
        printf("the current time:%s",get_time());
        puts("running thread");
    }
    return NULL;
}

char* get_time()
{
    time_t time_para;
    time(&time_para);
    return ctime(&time_para);
}

 

代码中调用sleep函数使main函数停顿10s,这是为了延迟进程的终止时间。 main函数中的return语句执行后终止进程,同时终止内部创建的子进程,因此,为了保证线程的正常执行而添加sleep语句。

运行结果:

mali@mali:~/code/thread$ gcc pthread1.c -o pthread1 -lpthread
mali@mali:~/code/thread$ ./pthread1 
in main thread,the current time:Tue Aug 13 22:38:56 2019
start of main

in main thread,tid:140530872768256,0x7fcfe4c05700

in new thread,the current time:Tue Aug 13 22:38:57 2019
new thread:  pid 11116 tid 140530872768256 (0x7fcfe4c05700)

in new thread,the current time:Tue Aug 13 22:38:58 2019
new thread:  pid 11116 tid 140530872768256 (0x7fcfe4c05700)

in new thread,the current time:Tue Aug 13 22:38:59 2019
new thread:  pid 11116 tid 140530872768256 (0x7fcfe4c05700)

in new thread,the current time:Tue Aug 13 22:39:00 2019
new thread:  pid 11116 tid 140530872768256 (0x7fcfe4c05700)

in new thread,the current time:Tue Aug 13 22:39:01 2019
new thread:  pid 11116 tid 140530872768256 (0x7fcfe4c05700)

in main thread,the current time:Tue Aug 13 22:39:06 2019
main thread:  pid 11116 tid 140530881103616 (0x7fcfe53f8700)
mali@mali:~/code/thread$ 

将sleep函数改为sleep(2)的执行结果:

mali@mali:~$ ./pthread1 
the current time:Sun Jul 14 22:44:49 2019
start of main
the current time:Sun Jul 14 22:44:50 2019
running thread
the current time:Sun Jul 14 22:44:51 2019
end of main

通过调用sleep函数控制线程的执行相当于预测程序的执行流程,但实际上这是不可能完成的事情,而且稍有不慎,很可能干扰程序的正常执行流。通常利用pthread_join来控制线程的执行流。

线程终止

如果进程中的任意线程调用了exit _Exit或者_exit,那么整个进程就会终止。

上述程序如果更改thread_main函数,在循环中添加exit函数:

void* thread_main(void* arg)
{
    int i = 0;
    int cnt = *((int*)arg);
    for(; i < cnt; i++)
    {
        sleep(1);
        printf("\nin new thread,the current time:%s",get_time());
        printids("new thread: ");
        exit(0); //退出程序

    }
    return NULL;
}
mali@mali:~/code/thread$ ./pthread1 
in main thread,the current time:Tue Aug 13 22:45:06 2019
start of main

in main thread,tid:140659031983872,0x7fedbba2c700

in new thread,the current time:Tue Aug 13 22:45:07 2019
new thread:  pid 11178 tid 140659031983872 (0x7fedbba2c700)
mali@mali:~/code/thread$ 

 单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。

  • 线程可以简单地从启动例程中返回,返回值是线程的退出码
  • 线程可以被同一进程中的其他线程取消
  • 线程调用pthread_exit

pthread_exit

#include <phread.h>

void pthread_exit(void* rval_ptr);

rval_ptr参数一个无类型指针,与传给启动例程的单个参数类似。

pthread_join

进程中的其他线程也可以通过调用pthread_join函数访问到这个指针 

#include <pthread.h>
int pthread_join(pthread_t thread, void** rval_ptr);

成功时返回0,失败时返回其他值。
thread-该参数ID的线程终止后才会从该函数返回。
rval_ptr-保存线程的main函数返回值的指针变量地址值

调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。

如果线程简单地从它的启动例程返回,rval_ptr就包含返回码。如果线程被取消,由rval_ptr指定的内存单元就设置为PTHREAD_CANCELED.

可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。

如果对线程的返回值并不感兴趣,那么可以把rval_ptr设置为NULL。在这种情况下,调用pthread_join函数可以等待指定的线程终止,但并不获取线程的终止状态。 

示例一:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
void* thread_main(void* arg);
char* get_time();
int main(int argc, char* argv[])
{
    pthread_t t_id;
    int thread_param = 5;
    void* thr_ret;
    printf("the current time:%s",get_time());
    puts("start of main");

    if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param))
    {
        puts("pthread_create() error");
        return -1;
    }
    if(pthread_join(t_id, &thr_ret) != 0)
    {
        puts("pthread_join() error");
        return -1;
    }

    printf("Thread return message:%s\n",(char*)thr_ret);
    printf("the current time:%s",get_time());
    puts("end of main");

    free(thr_ret);

    return 0;
}

void* thread_main(void* arg)
{
    int i = 0;
    int cnt = *((int*)arg);
    char* msg = (char*)malloc(sizeof(char) * 50);
    strcpy(msg, "Hello,I'am thread~\n");

    for(i; i < cnt; i++)
    {
        sleep(1);
        printf("the current time:%s",get_time());
        puts("running thread");
    }

    return (void*)msg;
}

char* get_time()
{
    time_t time_para;
    time(&time_para);
    return ctime(&time_para);
}

运行结果:

mali@mali:~$ gcc pthread1.c  -o pthread1 -lpthread
mali@mali:~$ ./pthread1 
the current time:Sun Jul 14 22:57:57 2019
start of main
the current time:Sun Jul 14 22:57:58 2019
running thread
the current time:Sun Jul 14 22:57:59 2019
running thread
the current time:Sun Jul 14 22:58:00 2019
running thread
the current time:Sun Jul 14 22:58:01 2019
running thread
the current time:Sun Jul 14 22:58:02 2019
running thread
Thread return message:Hello,I'am thread~

the current time:Sun Jul 14 22:58:02 2019
end of main

示例二:

/* pthread2.c */
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
void printids(const char* s)
{
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
            (unsigned long)tid, (unsigned long)tid);
}

void* thread_func1(void* arg)
{
    printids("thread 1 returning: ");
    return (void*)1;
}

void* thread_func2(void* arg)
{
    printids("thread 2 exiting: ");
    pthread_exit((void*)2);
}

int main(int argc, char* argv[])
{
    pthread_t tid1, tid2;
    void* tret;

    if(pthread_create(&tid1, NULL, thread_func1, NULL) != 0)
    {
        perror("pthread_create thread1 error");
        return -1;
    }

    if(pthread_create(&tid2, NULL, thread_func2, NULL) != 0)
    {
        perror("pthread_create() thread2 error");
        return -1;
    }
    printids("in main thread: ");

    if(pthread_join(tid1, &tret) != 0)
    {
        perror("pthread_join with thread1 error");
        return -1;
    }
    printf("thread1 exit code: %ld\n",(long)tret);

    if(pthread_join(tid2, &tret) != 0)
    {
        perror("pthread_join with thread2 error");
        return -1;
    }
    printf("thread2 exit code: %ld\n",(long)tret);
    return 0;
}

mali@mali:~/code/thread$ gcc pthread2.c -o pthread2 -lpthread
mali@mali:~/code/thread$ ./pthread2
in main thread:  pid 11352 tid 140046566270720 (0x7f5f21d5a700)
thread 2 exiting:  pid 11352 tid 140046549542656 (0x7f5f20d66700)
thread 1 returning:  pid 11352 tid 140046557935360 (0x7f5f21567700)
thread1 exit code: 1
thread2 exit code: 2
mali@mali:~/code/thread$ 

可以看到,当一个线程通过调用pthread_exit退出或者简单地从启动例程中返回时,进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态

pthread_create和pthread_exit函数的无类型指针参数可以传递的值不止一个,这个指针可以传递包含复杂信息的结构的地址,但是注意,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的。例如,在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内容可能已经改变了。又如,线程在自己的栈上分配了一个结构,然后把指向这个结构的指针传给pthread_exit,那么调用pthread_join的线程试图使用该结构时,这个栈有可能已经被撤销,这块内存已作他用。

如下程序给出了用自动变量(分配在栈上)作为pthread_exit的参数时出现的问题。

/* pthread3.c */
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
void printids(const char* s)
{
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,
            (unsigned long)tid, (unsigned long)tid);
}
struct foo
{
    int a;
    int b;
    int c;
    int d;
};

void printfoo(const char* s, const struct foo *fp)
{
    printf("%s",s);
    printf(" structure at 0x%lx\n", (unsigned long)fp);
    printf(" foo.a = %d\n", fp->a);
    printf(" foo.b = %d\n", fp->b);
    printf(" foo.c = %d\n", fp->c);
    printf(" foo.d = %d\n", fp->d);

}
void* thread_func1(void* arg)
{
    struct foo foo = {1, 2, 3, 4};
    printfoo("thread 1:\n", &foo);
    pthread_exit((void*)&foo);
}

void* thread_func2(void* arg)
{
    printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());
    pthread_exit((void*)0);
}

int main(int argc, char* argv[])
{
    pthread_t tid1, tid2;
    struct foo *fp;

    if(pthread_create(&tid1, NULL, thread_func1, NULL) != 0)
    {
        perror("pthread_create thread1 error");
        return -1;
    }
    if(pthread_join(tid1, (void*)&fp) != 0)
    {
        perror("pthread_join with thread1 error");
        return -1;
    }
    sleep(1);
    printf("parent starting second thread\n");

    if(pthread_create(&tid2, NULL, thread_func2, NULL) != 0)
    {
        perror("pthread_create() thread2 error");
        return -1;
    }
    sleep(1);
    printfoo("parent:\n",fp);
    return 0;
}


mali@mali:~/code/thread$ gcc pthread3.c -o pthread3 -lpthread
mali@mali:~/code/thread$ ./pthread3
thread 1:
 structure at 0x7f970b9acf30
 foo.a = 1
 foo.b = 2
 foo.c = 3
 foo.d = 4
parent starting second thread
thread 2: ID is 140286711486208
parent:
 structure at 0x7f970b9acf30
 foo.a = 198633728
 foo.b = 32663
 foo.c = 196227394
 foo.d = 32663
mali@mali:~/code/thread$ ./pthread3
thread 1:
 structure at 0x7fc1b890df30
 foo.a = 1
 foo.b = 2
 foo.c = 3
 foo.d = 4
parent starting second thread
thread 2: ID is 140470001919744
parent:
 structure at 0x7fc1b890df30
 foo.a = -1194526464
 foo.b = 32705
 foo.c = -1196932798
 foo.d = 32705
mali@mali:~/code/thread$ 

可以看到,当主线程访问这个结构时,结构的内容(在线程tid1的栈上分配的)已经改变了。注意第二个线程(tid2)的栈是如何覆盖第一个线程的栈的。

为了解决这个问题,可以使用全局结构,或者用malloc函数分配结构。 

线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程。

pthread_cancel

#include <pthread.h>

int pthread_cancel(pthread_t tid);

返回值:若成功,返回0;否则,返回错误编号

在默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是,线程可以选择忽略取消或者控制如何被取消。

线程存在的问题和临界区

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#define NUM_THREAD 100
#define MAX_NUM 5000000
void* thread_inc(void* arg);
void* thread_des(void* arg);
long long num = 0;
/*
 *创建100个线程,其中一半执行thread_inc函数中的代码,另一半执行thread_des函数中的代码。全局变量num
 *经过增减该过程后应存有0,通过运行结果观察是否真能得到
 */
int main(int argc, char* argv[])
{
    pthread_t thread_id[NUM_THREAD];
    int i = 0;
    printf("sizeof long long:%lu\n", sizeof(long long));

    for(; 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);
    return 0;
}

void* thread_inc(void* arg)
{
    int i;
    for(i = 0; i < MAX_NUM; i++)
    {
        num += 1;
    }
    return NULL;
}

void* thread_des(void* arg)
{
    int i;
    for(i = 0; i < MAX_NUM; i++)
    {
        num -= 1;
    }
    return NULL;
}

运行结果:

mali@mali:~$ gcc pthread1.c  -o pthread1 -lpthread
mali@mali:~$ ./pthread1 
sizeof long long:8
result:-1592395
mali@mali:~$ ./pthread1 
sizeof long long:8
result:0
mali@mali:~$ ./pthread1 
sizeof long long:8
result:7676091
mali@mali:~$ ./pthread1 
sizeof long long:8
result:0
mali@mali:~$ ./pthread1 
sizeof long long:8
result:4494261
mali@mali:~$ ./pthread1 
sizeof long long:8
result:-2082309

可以看到,每次运行的结果均不同。

线程同步

同步的两面性:

线程同步用于解决线程访问顺序引发的问题.需要同步的情况可以从如下两方面考虑:

1.同时访问同一内存空间时发生的情况

2.需要指定访问同一内存空间的线程执行顺序的情况

当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题。同样,如果变量是只读的,多个线程同时读取该变量也不会有一致性问题。但是,当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值。

当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。在变量修改时间多余一个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种不一致就会出现。当然,这种行为是与处理器体系结构相关的,但是可移植的程序并不能对使用何种处理器体系结构做出任何假设。

下图描述了两个线程读写相同变量的假设例子。在这个例子中,线程A读取变量然后给这个变量赋予一个新的数值,但写操作需要两个存储器周期。当线程B在这两个存储器写周期中读取这个变量时,它就会得到不一致的值。

 

为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问该变量。在线程同步访问的图中,如果线程B希望读取变量,它首先要获取锁。同样,当线程A更新变量时,也需要获取同样的这把锁。这样,线程B在线程A释放锁以前就不能读取变量。

两个或多个线程试图在同一时间修改同一变量时,也需要进行同步。考虑变量增加操作的情况,增量操作通常分解为以下3步:

  • 从内存单元读入寄存器
  • 在寄存器中对变量做增量操作
  • 把新的值写回内存单元

如果两个线程试图几乎在同一时间对同一个变量做增量操作而不进行同步的话,结果就可能出现不一致,变量可能比原来增加了1,也有可能比原来增加了2,具体增加了1还是2要取决于第二个线程开始操作时获取的数值。如果第二个线程执行第1步要比第一个线程执行第3步要早,第二个线程读到的值与第一个线程一样,为变量加1,然后写回去,事实上没有实际的效果,总的来说变量只增加了1.

如果修改操作是原子操作,那么就不存在竞争。在前面的例子中,如果增加1只需要一个存储器周期,那么就没有竞争存在。如果数据总是以顺序一致出现的,就不需要额外的同步。当多个线程观察不到数据的不一致时,那么操作就是顺序一致的。

 

互斥量

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);
int pthread_mutex_destory(pthread_mutex_t* mutex);

成功时返回0,失败时返回其他值。
mutex-创建互斥量时传递保存互斥量的变量地址值,销毁时传递需要销毁的互斥量地址值
attr-传递即将创建的互斥量属性,没有特别需要指定的属性时传递NULL
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
成功时返回0,失败时返回其他值。

进入临界区前调用的函数就是pthread_mutex_lock。调用该函数时,发现有其他线程已进入临界区,则pthread_mutex_lock函数不会返回,直到里面的线程调用pthread_mutex_unlock函数退出临界区为止。也就是说,其他线程让出临界区之前,当前线程将一直处于阻塞状态.

pthread_mutex_lock(&mutex);

//临界区的开始

//...

//临界区的结束

pthread_mutex_unlock(&mutex);

还有一点要注意,线程退出临界区时,如果忘了调用pthread_mutex_unlock函数,那么其他为了进入临界区而调用pthread_mutex_lock函数的线程就无法摆脱阻塞状态。这种情况称为死锁(Dead-lock).

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#define NUM_THREAD 100
#define MAX_NUM 50000000
void* thread_inc(void* arg);
void* thread_des(void* arg);
long long num = 0;
pthread_mutex_t mutex;
/*
 *创建100个线程,其中一半执行thread_inc函数中的代码,另一半执行thread_des函数中的代码。全局变量num
 *经过增减该过程后应存有0,通过运行结果观察是否真能得到
 */
int main(int argc, char* argv[])
{
    pthread_t thread_id[NUM_THREAD+1];
    int i = 0;
    
    pthread_mutex_init(&mutex, NULL);
    printf("sizeof long long:%lu\n", sizeof(long long));

    for(; 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);
    pthread_mutex_destroy(&mutex);
    return 0;
}

void* thread_inc(void* arg)
{
    int i;
    pthread_mutex_lock(&mutex);
    for(i = 0; i < MAX_NUM; i++)
    {
        num += 1;
    }
    pthread_mutex_unlock(&mutex);

    return NULL;
}

void* thread_des(void* arg)
{
    int i;
    pthread_mutex_lock(&mutex);
    for(i = 0; i < MAX_NUM; i++)
    {
        num -= 1;
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}

运行结果:

mali@mali:~$ gcc pthread1.c  -o pthread1 -lpthread
mali@mali:~$ ./pthread1 
sizeof long long:8
result:0

信号量

#include <semaphore.h>
int sem_init(sem_t * sem, int pshared, unsigned int value);

int sem_destroy(sem_t *sem);

成功时返回0,失败时返回其他值。
sem-创建信号量时传递保存信号量的变量地址值,销毁时传递需要销毁的信号量变量地址值。
pshared-传递其他值时,创建可由多个进程共享的信号量;传递0时,创建只允许1个进程内部使用的信号量,
value-指定新创建的信号量初始值。
#include <semaphore.h>
#ifndef _MODE_T_
#define	_MODE_T_
typedef unsigned short mode_t;
#endif

typedef void		*sem_t;

int  sem_wait(sem_t *sem);

int  sem_post(sem_t *sem);
成功时返回0,失败时返回其他值。
sem-传递保存信号量读取值的变量地址值,传递给sem_post时信号量增1,传递给sem_wait时信号量减1

调用sem_init函数时,操作系统将创建信号量对象,此对象中记录着“信号量值”(Semaphore Value)整数。该值在调用sem_post函数时增1,调用sem_wait函数时减1.但信号量的值不能小于0,因此,在信号量为0的情况下调用sem_wait函数时,调用函数的线程将进入阻塞状态(因此函数未返回)。当然,此时如果有其他线程调用sem_post函数,信号量的值将变为1,而原本阻塞的线程可以将该信号量重新减为0并跳出阻塞状态。实际上就是通过这种特性完成临界区的同步操作,可以通过如下形式同步临界区(假设信号量的初始值为1)

    sem_wait(&sem);//信号量变为0
    //临界区的开始
    //......
    //临界区的结束
    sem_post(&sem);//信号量变为1

上述代码结构中,调用sem_wait函数进入临界区的线程在调用sem_post函数前不允许其他线程进入临界区。信号量的值在0和1之间跳转,因此,具有这种特性的机制称为“二进制信号量”。

下面的程序用信号量完成控制访问顺序的同步

/*semaphore.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

void* read(void* arg);
void* accu(void* arg);

static sem_t sem_one;
static sem_t sem_two;
static int num;
/**
 *线程A从用户输入得到值后存入全局变量num,此时线程B将取走该值并累加。
 *该过程共进行5次,完成后输出综合并退出程序。
 */

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 = 0;
    /*
     *利用信号量变量sem_two调用wait函数和post函数。这是为了防止在调用accu函数
     *的线程还未取走数据的情况下,调用read函数的线程覆盖原值
     */
    for (; 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;
    /*利用信号量变量sem_one调用wait和post函数。这是为了防止调用read函数
     *的线程写入新值前,accu函数取走(有可能read函数未写入新值,此次取的数据
     *还是旧值)数据。
     */
    for (i = 0; i < 5; i++)
    {
        sem_wait(&sem_one);
        sum += num;
        sem_post(&sem_two);
    }
    printf("Result: %d\n", sum);
    return NULL;
}

运行结果:

mali@mali:~/code$ gcc semaphore.c -o semaphore -lpthread
mali@mali:~/code$ ./semaphore 
Input num: 1
Input num: 2
Input num: 3
Input num: 4
Input num: 5
Result: 15

如果将上述代码中关于信号量的部分注释掉,观察得到什么结果 

/*semaphore.c*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

void* read(void* arg);
void* accu(void* arg);

//static sem_t sem_one;
//static sem_t sem_two;
static int num;
/**
 *线程A从用户输入得到值后存入全局变量num,此时线程B将取走该值并累加。
 *该过程共进行5次,完成后输出综合并退出程序。
 */

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 = 0;
    /*
     *利用信号量变量sem_two调用wait函数和post函数。这是为了防止在调用accu函数
     *的线程还未取走数据的情况下,调用read函数的线程覆盖原值
     */
    for (; 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;
    /*利用信号量变量sem_one调用wait和post函数。这是为了防止调用read函数
     *的线程写入新值前,accu函数取走(有可能read函数未写入新值,此次取的数据
     *还是旧值)数据。
     */
    for (i = 0; i < 5; i++)
    {
        //sem_wait(&sem_one);
        sum += num;
        //sem_post(&sem_two);
    }
    printf("Result: %d\n", sum);
    return NULL;
}

运行结果:

mali@mali:~/code$ gcc semaphore.c -o semaphore -lpthread
mali@mali:~/code$ ./semaphore 
Result: 0
Input num: 1
Input num: 2
Input num: 3
Input num: 4
Input num: 5

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值