本文为自己的懒人笔记,没怎么梳理。看到什么小点,就写了什么小点。
懒人笔记
1.进程空间:
text:只读,包括常量
data: stack heap static
bss:未初始化的全部变量,静态变量2.线程的特点是只具有自己的堆栈区
其他空间全部共享,所以,线程并不是没有自己空间的,如果堆栈的变量想要传递就没法直接共享。3.主线程一旦退出,进程结束。
所以,多线程的时候,如果主线程退出,那么进程空间回收。其他线程歇菜。
线程的特点还是开销比较小。4.线程有三种退出方式:
线程可以简单的从启动例程中返回,返回值是线程的退出码。
线程可以被同一进程中的其他线程取消。
线程调用pthread_exit();5.pthread_self()获取线程id.
注意返回值是 unsigned long int,所以打印的时候要使用%lu方式。
为什么线程退出:一定是pthread_exit或者是简单退出,不能是exit
因为后者是进程退出,只要线程调用该函数,进程空间回收,所有资源都没有了。6.进程退出注意两个点:
1.不能用exit退出,这是退出进程。整个线程都将被销毁
2.资源要回收。7.说起资源回收,也是和进程的不同。
进程的资源回收必须是父进程,但是线程可以主线程回收,也可以系统去回收。
线程有两种状态,joinable and detatch8.这一对好朋友需要说一下:
void pthread_exit(void* retval);
void pthread_join( pthread_t thread, void** retval );
一个返回,一个接受。到底什么关系。
先说pthread_exit, 粘一段手册的内容:
The value pointed to by retval should not be located on the calling thread’s stack, since the contents of that stack are undefined after the thread terminates.
也就是说,retval其实是返回值的地址,是一个传出参数。
再说pthread_join,主要是弄清楚,为什么是void**类型。因为对于pthread_exit的传出参数retval来说,pthread_join其实获取的是这个值,但是如果在主线程当中想要获取这个值,那只能把这个值传出去。因此,需要一个传出参数。那就只能是retval这个变量的地址,因此是type(&retval)类型,即void* 类型.9.calloc需要初始化,malloc则不需要初始化。
10.int pthread_cancle( pthread_t thread );对于这个函数而言,也是线程退出的一种方式。先初步了解,手册是这么说的:send a cancellation request to a thread.注意,只是send a caccellation request,thread不是立即退出。它必须得到cancellation point才可以退出终止。
一般而言根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。11.说一个用它的场景:
考虑这样一个问题。
现在有10000个人,每个人的名字都不一样。
我现在需要找到 kang这个人,假设这个人一定存在的。
那么怎么找?
第一种办法:枚举,当然不好。因为量很大。
其次,多线程。
thd1: 1-1000
thd2; 1001 - 2000
….thd10 - 9000-10000
假设现在这个人的编号是1234,那么第二个想成找到之后返回,其他线程没有必要找了。
主线程可以把其他线程杀死。12.线程终止清理函数,主要是在线程异常终止,还没回收资源,就被释放了。
所以,资源没有回收,利用清理函数。13.线程最麻烦的是,如果有一个线程异常退出了,那么整个进程都会退出。这会导致其他线程也异常退出,整个系统挂掉。
但是,多进程就不会出现这个问题。14.注意一点,pthread_create失败返回非0,不是-1.小心
15.对共享变量的访问,没有办法是原子操作,导致操作异常。
try_lock()加锁不成功时,不会阻塞。通过返回值判断一下。
锁的初始化有静态和动态两种形式。
一般使用动态。16.
void* thread_handle( void* arg ){
printf( “thread: %lu\n”, pthread_self() );
int* pcnt = (int*)arg;
int cnt = *pcnt;
while(cnt–){
++g_val;
}
printf( “hello,world” ); // 这里这么写也是 不行的!!!
pthread_exit(NULL);
}
因为本质上他们都访问了stdout这个文件,相当于读者-写者问题。不行。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void err_handle(const char* msg){
perror(msg);
exit(1);
}
void* thread_handle( void* arg ){
char* msg = (char*)arg;
printf( "thread: %s\n", msg );
printf( "thread id: %lu\n", pthread_self() );
int fds[2];
pipe(fds);
char buf[1024];
read( fds[0], buf, 1023 );
/*
char* ret_msg = "how are you!\n";
pthread_exit((void*)ret_msg); // return
*/
/*
int* ret_val = (int*)malloc( sizeof(int) * 1 );
*ret_val = 1024;
if(!ret_val){
err_handle("malloc");
}
pthread_exit( (int*)ret_val );
*/
pthread_exit(NULL);
}
int main( int argc, char* argv[] ){
pthread_t tid;
int ret = 0;
char* s = "hello,world!";
printf( "main id: %lu\n", pthread_self() );
ret = pthread_create( &tid, NULL, thread_handle, (void*)s );
if(-1 == ret){
err_handle("pthread_create");
}
/*这个是通常情况下退出,不需要线程的返回参数。
ret = pthread_join(tid, NULL);
if(-1 == ret){
err_handle("pthread_join");
}
printf( "Thread join!\n" );
*/
/*
char* ret_msg = NULL;
ret = pthread_join(tid, (void**)&ret_msg);
if(-1 == ret){
err_handle("pthread_join");
}
printf("thread has finished, retval is %s\n", ret_msg);
exit(EXIT_SUCCESS);
*/
/*
int* ret_val = NULL;
ret = pthread_join( tid, (void**)&ret_val );
if(-1==ret){
err_handle("pthread_join");
}
printf( "thread join: ret_val is %d\n", *ret_val );
*/
pthread_cancel(tid);
exit(EXIT_SUCCESS);
}
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define N 2 // thread num
void err_handle(const char* msg){
perror(msg);
exit(EXIT_FAILURE);
}
void* thread_handle(void* arg);
int g_val = 0; // global variable
typedef struct task{
int cnt;
pthread_mutex_t* pmylock;
}task_t,*ptask;
int main( int argc, char* argv[] ){
pthread_t tid_arr[N];
int i = 0;
int ret = 0;
int cnt = 100000000;
pthread_mutex_t mylock;
ret = pthread_mutex_init(&mylock, NULL);
if(ret){
err_handle("pthread_mutex_init");
exit(EXIT_FAILURE);
}
task_t a_task;
a_task.cnt = cnt;
a_task.pmylock = &mylock;
for(i = 0; i < N; ++i){
ret = pthread_create( tid_arr + i, NULL, thread_handle, (void*)&a_task );
if(0 != ret) err_handle("pthread_create");
}
for(i = 0; i < N; ++i){
ret = pthread_join(tid_arr[i], NULL);
if(-1 == ret) err_handle("pthread_join");
}
ret = pthread_mutex_destroy(&mylock);
if(ret){
err_handle("pthread_mutex_destroy");
exit(EXIT_FAILURE);
}
printf( "main: %lu\n", pthread_self() );
printf("g_val = %d\n", g_val);
exit(EXIT_SUCCESS);
}
void* thread_handle(void* arg){
ptask p_a_task = (ptask)arg;
int cnt = p_a_task->cnt;
int i;
pthread_mutex_lock(p_a_task->pmylock);
for(i = 0; i < cnt; ++i){
++g_val;
}
pthread_mutex_unlock(p_a_task->pmylock);
printf("thread: %lu\n", pthread_self());
pthread_exit(NULL);
}
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <malloc.h>
#include <errno.h>
void err_msg( int en , const char* msg );
void err_msg1( const char* msg );
void err_msg2( const char* msg );
void* thread_handle( void* );
int main( int argc, char* argv[] ){
pthread_t tid = 0;
int ret = 0;
int i = 0;
int ret_val = 0; // 返回值这里,线程返回什么类型,这里写什么类型即可。其实应该写int* ret_val;但是线程返回值不是指针类型,它是直接把值类型,强转。因为,指针变量和整型变量都是占4个字节,所以虽然这么转不是非常纯正,但是并不会出错。语义不好,但是可以实现。
ret = pthread_create(&tid, NULL, thread_handle, NULL );
if( 0 != ret ){
err_msg(ret, "pthread_create");
}
for( i = 0; i < 10; ++i ){
printf( "%lu : hello my child!\n", pthread_self() );
if(5==i)
pthread_cancle(tid);
sleep(1);
}
ret = pthread_join(tid, (void**)&ret_val);
if( 0 != ret ){
err_msg(ret, "pthread_join");
}
printf( "%lu: Thread is joined, retval is %d\n", pthread_self(), ret_val );
exit(EXIT_SUCCESS);
}
void err_msg( int en, const char* msg ){
errno = en;
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg1( const char* msg ){
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg2( const char* msg ){
fprintf(stderr, "%s\n", msg );
exit(EXIT_FAILURE);
}
void* thread_handle( void* arg){
int i = 0;
char* msg = (char*)malloc( sizeof(char) * 32 );
if(!msg)
err_msg2("Not enough space!");
for( i = 0; i < 10; ++i ){
printf( "%lu: Hello, my father!\n", pthread_self() );
sleep(1);
}
free(msg);
printf("Dynamic space is freed!\n");
pthread_exit((void*)10);
}
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <malloc.h>
#include <errno.h>
void err_msg( int en , const char* msg );
void err_msg1( const char* msg );
void err_msg2( const char* msg );
void* thread_handle( void* );
int main( int argc, char* argv[] ){
pthread_t tid = 0;
int ret = 0;
int i = 0;
void* ret_val = NULL; // 注意这里,这是标准的写法。
ret = pthread_create(&tid, NULL, thread_handle, NULL );
if( 0 != ret ){
err_msg(ret, "pthread_create");
}
for( i = 0; i < 10; ++i ){
printf( "%lu : hello my child!\n", pthread_self() );
sleep(1);
}
ret = pthread_join(tid, (void**)&ret_val);
if( 0 != ret ){
err_msg(ret, "pthread_join");
}
printf( "%lu: Thread is joined, retval is %d\n", pthread_self(), (int)ret_val );
/*
注意,这里为什么可以这么写。这点我之前说过,线程里面返回的是:返回值的地址,而我们在主线程里面接受的也是返回值的地址。只不过,我现在并不关心返回值的地址所指向
向的返回值到底是什么,我只是用这个返回值地址。因为整型变量和指针变量所占内存一样,所以我接返回值地址,其实传递的是一个整数值。此时对它解引用是没有意义的。
因为它就不是地址,它只不过和地址具有一样的大小,所以借助它进行值的传递。万万不可解引用!!!
*/
exit(EXIT_SUCCESS);
}
void err_msg( int en, const char* msg ){
errno = en;
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg1( const char* msg ){
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg2( const char* msg ){
fprintf(stderr, "%s\n", msg );
exit(EXIT_FAILURE);
}
void* thread_handle( void* arg){
int i = 0;
char* msg = (char*)malloc( sizeof(char) * 32 );
if(!msg)
err_msg2("Not enough space!");
for( i = 0; i < 10; ++i ){
printf( "%lu: Hello, my father!\n", pthread_self() );
sleep(1);
}
free(msg);
printf("Dynamic space is freed!\n");
pthread_exit((void*)10);
}
懒人笔记
今天说下看pthread_cancle当中清理函数的问题。当然,这里有一个误区就是清理函数不是只可以用在pthread_cancel的,这里的用法属于异常终止的情形。它还有正常终止的情形。就是如下两个函数:
线程终止清理函数
void pthread_cleanup_push( void (routine)(void), void* arg ) ;
void pthread_cleanup_pop( int execute );这两个函数的作用是什么?
These functions manipulate the calling thread’s stack of thread-cancellation clean-up handlers. 从这句话我们可以看出,这两个函数它们本省不是清理函数,他们是用来管理调用线程,在线程终止时的清理函数栈的。什么又是清理函数呢?清理函数就是当线程异常终止时(即被取消)自动执行的函数代码,当然不光是这一种情形,还有正常终止的情形。
当然,它的这个用途和pthread_cancle结合起来,可以避免如线程异常终止,锁无法释放这样的问题。注意,手册里面强调的是,终止时的清理函数,并没有特别强调异常终止,所以要一视同仁。
其实push函数有点像注册函数,总之这两个函数用来管理线程终止时的清理函数调用栈。
线程终止时,清理函数的执行时机,注意不区分是异常还是正常终止。
A cancellation clean-up handler is popped from the stack and executed in the following circumstances:
1. When a thread is canceled, all of the stacked clean-up handlers are
popped and executed in the reverse of the order in which they were
pushed onto the stack.(这种情况其实说的是异常终止,pop的参数并不影响这种情况。异常终止的语义,清理函数中加入异常终止需要处理的代码逻辑即可)
2. When a thread terminates by calling pthread_exit(3), all clean-up
handlers are executed as described in the preceding point. (Clean-
up handlers are not called if the thread terminates by performing a
return from the thread start function.)(这种情况说的是正常终止,但是必须是通过pthread_exit()调用才是可以的,return不行。正常终止的语义,清理函数中加入正常终止需要处理的代码逻辑即可)
3. When a thread calls pthread_cleanup_pop() with a nonzero execute
argument, the top-most clean-up handler is popped and executed.(这种情况我其实不是很懂他的语义,反正就是pop的参数给非0,此时栈顶清理函数弹出并执行),看手册里面是return NULL,然后结合这种情况实现的。pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \
{
struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute));
}
可见,pthread_cleanup_push()带有一个”{“,而pthread_cleanup_pop()带有一个”}”,因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。线程取消
这一块,还是我之前考虑的那种场景,比如在1-100里面找一个数,那么我可以让2个线程干这个事,一个从[1,50]找,一个从[51,100]开始找。如果一个找到,返回控制线程,此时可以向另一个线程发送取消请求。
需要注意的时,接受取消请求的函数有可能没有取消点,所以可以在需要测试是否有取消请求发送过来的地方,加入void pthread_testcancel(void);即可。相当于插入了一个测试点。以便在一个没有包含取消点的执行代码线程中响应取消请求.
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <malloc.h>
#include <errno.h>
void err_msg( int en , const char* msg );
void err_msg1( const char* msg );
void err_msg2( const char* msg );
void* thread_handle( void* );
void cleanup_handler(void* );
int main( int argc, char* argv[] ){
pthread_t tid = 0;
int ret = 0;
int i = 0;
void* ret_val = NULL;
ret = pthread_create(&tid, NULL, thread_handle, NULL );
if( 0 != ret ){
err_msg(ret, "pthread_create");
}
for( i = 0; i < 10; ++i ){
printf( "%lu : hello my child!\n", pthread_self() );
if(5==i){
ret = pthread_cancel(tid);
if(0 != ret )
err_msg(ret, "pthread_cancel");
}
sleep(1);
}
ret = pthread_join(tid, (void**)&ret_val);
if( 0 != ret ){
err_msg(ret, "pthread_join");
}
if(PTHREAD_CANCELED == ret_val){
printf("Thread was canceled!\n");
}
else{
printf( "Thread terminated normally!\n" );
}
exit(EXIT_SUCCESS);
}
void err_msg( int en, const char* msg ){
errno = en;
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg1( const char* msg ){
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg2( const char* msg ){
fprintf(stderr, "%s\n", msg );
exit(EXIT_FAILURE);
}
void* thread_handle( void* arg){
int i = 0;
char* msg = (char*)malloc( sizeof(char) * 32 );
if(!msg)
err_msg2("Not enough space!");
pthread_cleanup_push( cleanup_handler, (void*)msg ); // register a cleanup handler
for( i = 0; i < 10; ++i ){
pthread_testcancel(); // A cancellation point
printf( "%lu: Hello, my father!\n", pthread_self() );
sleep(1);
}
pthread_cleanup_pop(0);
return;
}
void cleanup_handler(void* arg ){
printf( "cleanup_handler called!\n" );
free( (char*)arg );
printf( "Dynamic space is freed!\n" );
}
代码
这一块代码主要是,用互斥量实现对公共资源的互斥访问。
//buy_and_sell_ticket.c
#include "my_common.h"
#include "my_err.h"
#define THREADS_NUM 3
typedef struct task{
int ticket_num;
pthread_mutex_t lock;
}task_t, *p_task;
void task_init( p_task ptask );
void task_destroy( p_task ptask );
void main_handle( pthread_t* tid_arr, p_task ptask );
void* thread_handle( void* arg );
int main( int argc, char* argv[] ){
pthread_t tid_arr[THREADS_NUM];
task_t a_task;
main_handle( tid_arr, &a_task );
exit(EXIT_SUCCESS);
}
void task_init( p_task ptask ){
int ret = 0;
if(!ptask)
err_msg2("Invald arguments!");
ptask->ticket_num = 10;
ret = pthread_mutex_init( &ptask->lock, NULL );
if( 0 != ret )
err_msg( ret, "pthread_mutex_init" );
return;
}
void task_destroy( p_task ptask ){
int ret = 0;
if(!ptask)
err_msg2("Invald arguments!");
ret = pthread_mutex_destroy( &ptask->lock );
if( 0 != ret )
err_msg( ret, "pthread_mutex_destroy" );
return;
}
void main_handle( pthread_t* tid_arr, p_task ptask ){
int i = 0;
int ret = 0;
if(!tid_arr || !ptask)
err_msg2( "Invalid arguments!" );
task_init(ptask);
for(i = 0; i < THREADS_NUM; ++i){
ret = pthread_create( tid_arr + i, NULL, thread_handle, (void*)ptask );
if(ret != 0)
err_msg( ret, "pthread_create" );
}
for(i = 0; i < THREADS_NUM; ++i){
ret = pthread_join( tid_arr[i], NULL);
if(ret != 0)
err_msg( ret, "pthread_join" );
}
task_destroy(ptask);
return;
}
void* thread_handle( void* arg ){
int ret = 0;
p_task ptask = (p_task)arg;
if(!ptask)
err_msg2( "Invalid arguments!" );
while(1){
ret = pthread_mutex_lock( &ptask->lock );
if( ptask->ticket_num > 0 ){
printf( "%lu buy a ticket, ticket number is %d.\n", pthread_self(), ptask->ticket_num );
ptask->ticket_num--;
ret = pthread_mutex_unlock( &ptask->lock );
sleep(1); // for fair
}
else{
ret = pthread_mutex_unlock( &ptask->lock );
break;
}
}
pthread_exit(NULL);
}
//my_err.h
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "my_err.h"
void err_msg( int en, const char* msg ){
errno = en;
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg1( const char* msg ){
perror(msg);
exit(EXIT_FAILURE);
}
void err_msg2( const char* msg ){
fprintf( stderr, "%s\n", msg );
exit(EXIT_FAILURE);
}
代码(标准-互斥量实现同步问题)
下面这段代码,其实是用互斥量实现了同步问题,有点类似生产者-消费者模型了。
#include "my_common.h"
#include "my_err.h"
#define THREADS_NUM 3
typedef struct task{
int ticket_num;
pthread_mutex_t lock;
}task_t, *p_task;
void task_init( p_task ptask );
void task_destroy( p_task ptask );
void main_handle( pthread_t* tid_arr, p_task ptask );
void* thread_handle( void* arg );
int main( int argc, char* argv[] ){
pthread_t tid_arr[THREADS_NUM];
task_t a_task;
main_handle( tid_arr, &a_task );
exit(EXIT_SUCCESS);
}
void task_init( p_task ptask ){
int ret = 0;
if(!ptask)
err_msg2("Invald arguments!");
ptask->ticket_num = 10;
ret = pthread_mutex_init( &ptask->lock, NULL );
if( 0 != ret )
err_msg( ret, "pthread_mutex_init" );
return;
}
void task_destroy( p_task ptask ){
int ret = 0;
if(!ptask)
err_msg2("Invald arguments!");
ret = pthread_mutex_destroy( &ptask->lock );
if( 0 != ret )
err_msg( ret, "pthread_mutex_destroy" );
return;
}
void main_handle( pthread_t* tid_arr, p_task ptask ){
int i = 0;
int ret = 0;
if(!tid_arr || !ptask)
err_msg2( "Invalid arguments!" );
task_init(ptask);
for(i = 0; i < THREADS_NUM; ++i){
ret = pthread_create( tid_arr + i, NULL, thread_handle, (void*)ptask );
if(ret != 0)
err_msg( ret, "pthread_create" );
}
while(1){
ret = pthread_mutex_lock( &ptask->lock );
if( 0 == ptask->ticket_num ){ // 不满足要求,busy waiting
printf( "%lu supply 5 tickets.\n", pthread_self() );
ptask->ticket_num += 5;
}
ret = pthread_mutex_unlock( &ptask->lock );
sleep( rand()%3+1 ); // for fair
}
for(i = 0; i < THREADS_NUM; ++i){
ret = pthread_join( tid_arr[i], NULL);
if(ret != 0)
err_msg( ret, "pthread_join" );
}
task_destroy(ptask);
return;
}
void* thread_handle( void* arg ){
int ret = 0;
p_task ptask = (p_task)arg;
if(!ptask)
err_msg2( "Invalid arguments!" );
while(1){
ret = pthread_mutex_lock( &ptask->lock );
if( ptask->ticket_num > 0 ){ // 不满足要求,busy waiting
printf( "%lu buy a ticket, ticket number is %d.\n", pthread_self(), ptask->ticket_num );
ptask->ticket_num--;
}
ret = pthread_mutex_unlock( &ptask->lock );
sleep( rand()%3+1 ); // for fair
}
pthread_exit(NULL);
}
懒人笔记
1.说一下上面这段代码的问题:
用互斥量实现同步,需要资源的计数器(这本生也是一种公共资源),去判断资源是否使用完毕。从以上代码可以看出,两处都存在busy waiting情况。即条件不满足的情况,各自还是在轮询。
其实,没有资源的时候,各自进程可以阻塞。比如,有票的时候,补票的进程阻塞,没票的时候,卖票的进程阻塞。这样就不用忙等待。可以让出宝贵的CPU资源,不要让他busy waiting做没意义的事。因此,我们需要一个机制,
1).条件不满足,等待
2).条件满足,别人要能唤醒等待的。2.竞争条件:如果两个程序执行顺序不一样,结果不同。
考虑这样的一段代码:
父进程创建子进程!
父进程先执行,那么它去注册一个信号,此时子进程后执行,向父进程发送信息。父进程可以收到。
但是如果子进程先执行,但是因为父进程此时还没有注册信号,所以子进程向父进程发送,子进程没法接受。先说我自己理解的SIGUSR1这个信号,其他信号都有自己对应的相应事件,是系统定义好的。
比如,你注册了键盘中断,那么敲了键盘之后,注册信号的进程会收到这个信号。
但是,SIGUSR1这个信号是用户自定义的,也就是说没有系统自定义的事件去触发这个信号的发送。
暂时就理解到这。当程序的先后执行条件不同-结果不同时,会产生竞争条件!所以,需要避免。
其实,也就是同步的关系。
比如,父亲要先注册,孩子才能发送。有先后关系。同步关系。vfork()创建的进程一定是孩子先跑,但是空间不独立,这点和fork不一样。
3.pthread_cond_wait();
实现:
1.把锁放开
2.休眠
3.返回再拿锁1,2这是原子操作,昭哥说如果不是原子,则会产生竞争条件。我看了手册,线程A放开锁之后,如果休眠不是原子操作,那么线程B抢走了锁,然后唤醒了所有线程,那么此时,线程A如果再去休眠,就没人可以唤醒它了。这和之前说的竞争条件就一样了。
4.对于锁以及条件变量,静态初始化和动态初始化的区别,
动态初始化相当于调用malloc函数申请内存,所以最后要释放内存。
那么
pthread_cond_init()
pthread_cond_destroy()应该成对使用。
并且,动态在init的时候,还可以设置属性,相对来说功能更强大一点。
但是,静态初始化,不能设置属性,但是省去了初始化和销毁。5.In Thread1:
pthread_mutex_lock(&m_mutex);
pthread_cond_wait(&m_cond,&m_mutex);
pthread_mutex_unlock(&m_mutex);In Thread2:
pthread_mutex_lock(&m_mutex);
pthread_cond_signal(&m_cond);
pthread_mutex_unlock(&m_mutex);为什么要与pthread_mutex 一起使用呢? 这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。 如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁) 的时候才能调用cond_singal.
>
懒人笔记
我又参照下面这个链接,对条件变量有了更进一步的认识。
[深入理解pthread_cond_wait、pthread_cond_signal]
考虑这个链接里面说的一个问题,两个变量x,y
一个线程负责修改这两个变量,另外一个线程必须等待x>y的时候才可以执行。
那么,可以考虑直接用互斥量来实现,代码如下:
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "my_err.h"
int x = 0;
int y = 10;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
void* modifier(void*);
void* tester(void*);
int main( int argc, char* argv[] ){
pthread_t tid1, tid2;
int ret = 0;
ret = pthread_create( &tid1, NULL, modifier, NULL );
if(ret != 0)
err_msg(ret, "pthread_create");
ret = pthread_create( &tid2, NULL, tester, NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
ret = pthread_join( tid1, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
ret = pthread_join( tid2, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
exit(EXIT_SUCCESS);
}
void* modifier(void*arg){
while(1){
pthread_mutex_lock(&mut);
if( x > y ){
pthread_mutex_unlock(&mut);
break;
}
else{
/* modify x and y */
++x;
--y;
printf( "%lu: x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
sleep(1);
}
}
}
void* tester(void*arg){
while(1){
pthread_mutex_lock(&mut);
/* operate on x and y until x > y */
if( x > y ){
printf( "%lu: x=%d, y=%d, and x > y now.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
break;
}
pthread_mutex_unlock(&mut);
sleep(1);
}
}
//代码运行结果
139704398386944: x=1, y=9.
139704398386944: x=2, y=8.
139704398386944: x=3, y=7.
139704398386944: x=4, y=6.
139704398386944: x=5, y=5.
139704398386944: x=6, y=4.
139704389994240: x=6, y=4, and x > y now.
从上面的代码运行结果,我们可以发现,同步语义确实实现了。tester线程确实在x>y之后才开始它对x,y的代码逻辑。
但是,用互斥量实现同步,我们可以发现很明显的问题。就是在线程tester里面,当x>y这个条件不满足,需要反复轮询条件,直到条件满足,CPU处于busy waiting状态。
下面用条件变量实现:先上代码。
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "my_err.h"
int x = 0;
int y = 10;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* modifier(void*);
void* tester(void*);
int main( int argc, char* argv[] ){
pthread_t tid1, tid2;
int ret = 0;
ret = pthread_create( &tid1, NULL, modifier, NULL );
if(ret != 0)
err_msg(ret, "pthread_create");
ret = pthread_create( &tid2, NULL, tester, NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
ret = pthread_join( tid1, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
ret = pthread_join( tid2, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
exit(EXIT_SUCCESS);
}
void* modifier(void*arg){
while(1){
pthread_mutex_lock(&mut);
if( x > y ){
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mut);
break;
}
else{
/* modify x and y */
++x;
--y;
printf( "%lu: x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
sleep(1);
}
}
}
void* tester(void*arg){ // while已经没有了,不再轮询
pthread_mutex_lock(&mut);
/* operate on x and y until x > y */
if( x <= y ){
pthread_cond_wait(&cond, &mut);
}
printf( "%lu: x=%d, y=%d, and x > y now.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
}
分析上面的代码可以看出,在tester里面,对于x>y条件的while轮询已经没有了。写代码的逻辑是,对于不满足条件的逻辑要进行处理,即对x<=y的逻辑要进行处理,如果满足x<=y,那么在这个条件上阻塞,不占用CPU资源,直到被唤醒。
下面这部分代码,我想证明“惊群效应”。所以,我多写了一个tester,因为signal可能在多处理器唤醒不止一个线程,但是资源只有一个,那么这么做就会错误,因为只有一个资源,你不能唤醒多个线程。
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "my_err.h"
int x = 0;
int y = 10;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* modifier(void*);
void* tester(void*);
int main( int argc, char* argv[] ){
pthread_t tid1, tid2;
pthread_t tid3;
int ret = 0;
ret = pthread_create( &tid1, NULL, modifier, NULL );
if(ret != 0)
err_msg(ret, "pthread_create");
ret = pthread_create( &tid2, NULL, tester, NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
ret = pthread_create( &tid3, NULL, tester, NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
ret = pthread_join( tid1, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
ret = pthread_join( tid2, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
ret = pthread_join( tid3, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
exit(EXIT_SUCCESS);
}
void* modifier(void*arg){
while(1){
pthread_mutex_lock(&mut);
if( x > y ){
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mut);
break;
}
else{
/* modify x and y */
++x;
--y;
printf( "%lu: x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
sleep(1);
}
}
}
void* tester(void*arg){
pthread_mutex_lock(&mut);
/* operate on x and y until x > y */
if( x <= y ){
pthread_cond_wait(&cond, &mut);
}
printf( "%lu: x=%d, y=%d, and x > y now.\n", pthread_self(), x, y );
++x,++y;
printf( "%lu: we do increament on x and y, x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
}
/*运行结果
140647973668608: x=1, y=9.
140647973668608: x=2, y=8.
140647973668608: x=3, y=7.
140647973668608: x=4, y=6.
140647973668608: x=5, y=5.
140647973668608: x=6, y=4.
140647965275904: x=6, y=4, and x > y now.
140647965275904: we do increament on x and y, x=7, y=5.
然后就卡住了。。。
*/
上面代码失败,没有验证了“惊群效应”,因为signal函数只唤醒了一个线程,其实这是我希望看到的结果。但是,并没有验证我的假设,可是我的假设也是正确的。那只能证明一点,我的机器可能时单核的,或者多个线程都分配到了一个核上。
下面的代码我再次尝试证明“惊群效应”,还是失败了。但是,这对于我理解,pthread_cond_wait的返回非常有帮助,因为返回还有一个“强锁”的过程,可以试想,如果返回不“强锁”,这将是disaster
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "my_err.h"
#define N 32
int x = 0;
int y = 10;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* modifier(void*);
void* tester(void*);
int main( int argc, char* argv[] ){
pthread_t tid1;
pthread_t tid_arr[N];
int ret = 0;
int i = 0;
ret = pthread_create( &tid1, NULL, modifier, NULL );
if(ret != 0)
err_msg(ret, "pthread_create");
for( i = 0; i < N; ++i ){
ret = pthread_create( tid_arr + i, NULL, tester, NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
}
ret = pthread_join( tid1, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
for( i = 0; i < N; ++i ){
ret = pthread_join( tid_arr[i], NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
}
exit(EXIT_SUCCESS);
}
void* modifier(void*arg){
while(1){
pthread_mutex_lock(&mut);
if( x > y ){
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mut);
break;
}
else{
/* modify x and y */
++x;
--y;
printf( "%lu: x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
sleep(1);
}
}
}
void* tester(void*arg){
pthread_mutex_lock(&mut);
/* operate on x and y until x > y */
if( x <= y ){
pthread_cond_wait(&cond, &mut);
}
printf( "%lu: x=%d, y=%d, and x > y now.\n", pthread_self(), x, y );
++x,++y;
printf( "%lu: we do increament on x and y, x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
}
/*
139797750634240: x=1, y=9.
139797750634240: x=2, y=8.
139797750634240: x=3, y=7.
139797750634240: x=4, y=6.
139797750634240: x=5, y=5.
139797750634240: x=6, y=4.
139797691885312: x=6, y=4, and x > y now.
139797691885312: we do increament on x and y, x=7, y=5.
139797683492608: x=7, y=5, and x > y now.
139797683492608: we do increament on x and y, x=8, y=6.
139797675099904: x=8, y=6, and x > y now.
139797675099904: we do increament on x and y, x=9, y=7.
139797666707200: x=9, y=7, and x > y now.
139797666707200: we do increament on x and y, x=10, y=8.
139797742241536: x=10, y=8, and x > y now.
139797742241536: we do increament on x and y, x=11, y=9.
139797733848832: x=11, y=9, and x > y now.
139797733848832: we do increament on x and y, x=12, y=10.
139797725456128: x=12, y=10, and x > y now.
139797725456128: we do increament on x and y, x=13, y=11.
139797658314496: x=13, y=11, and x > y now.
139797658314496: we do increament on x and y, x=14, y=12.
139797717063424: x=14, y=12, and x > y now.
139797717063424: we do increament on x and y, x=15, y=13.
139797708670720: x=15, y=13, and x > y now.
139797708670720: we do increament on x and y, x=16, y=14.
139797700278016: x=16, y=14, and x > y now.
139797700278016: we do increament on x and y, x=17, y=15.
139797649921792: x=17, y=15, and x > y now.
139797649921792: we do increament on x and y, x=18, y=16.
139797641529088: x=18, y=16, and x > y now.
139797641529088: we do increament on x and y, x=19, y=17.
139797633136384: x=19, y=17, and x > y now.
139797633136384: we do increament on x and y, x=20, y=18.
139797624743680: x=20, y=18, and x > y now.
139797624743680: we do increament on x and y, x=21, y=19.
139797616350976: x=21, y=19, and x > y now.
139797616350976: we do increament on x and y, x=22, y=20.
139797607958272: x=22, y=20, and x > y now.
139797607958272: we do increament on x and y, x=23, y=21.
139797599565568: x=23, y=21, and x > y now.
139797599565568: we do increament on x and y, x=24, y=22.
139797591172864: x=24, y=22, and x > y now.
139797591172864: we do increament on x and y, x=25, y=23.
139797582780160: x=25, y=23, and x > y now.
139797582780160: we do increament on x and y, x=26, y=24.
139797574387456: x=26, y=24, and x > y now.
139797574387456: we do increament on x and y, x=27, y=25.
139797565994752: x=27, y=25, and x > y now.
139797565994752: we do increament on x and y, x=28, y=26.
139797557602048: x=28, y=26, and x > y now.
139797557602048: we do increament on x and y, x=29, y=27.
139797549209344: x=29, y=27, and x > y now.
139797549209344: we do increament on x and y, x=30, y=28.
139797540816640: x=30, y=28, and x > y now.
139797540816640: we do increament on x and y, x=31, y=29.
139797532423936: x=31, y=29, and x > y now.
139797532423936: we do increament on x and y, x=32, y=30.
139797524031232: x=32, y=30, and x > y now.
139797524031232: we do increament on x and y, x=33, y=31.
139797515638528: x=33, y=31, and x > y now.
139797515638528: we do increament on x and y, x=34, y=32.
139797507245824: x=34, y=32, and x > y now.
139797507245824: we do increament on x and y, x=35, y=33.
139797498853120: x=35, y=33, and x > y now.
139797498853120: we do increament on x and y, x=36, y=34.
139797490460416: x=36, y=34, and x > y now.
139797490460416: we do increament on x and y, x=37, y=35.
139797482067712: x=37, y=35, and x > y now.
139797482067712: we do increament on x and y, x=38, y=36.
*/
分析一下这个执行过程,如果没有pthread_cond_wait返回后,抢锁的过程,那么这32个线程都将被唤醒去对公共资源x,y进行修改,这将会导致不一致性的问题。因为32个线程应该执行32次加加操作,如果不强锁,肯定不能保证时32次。正是返回的抢锁操作,才导致没有这个问题。
下面说“惊群现象”,主要就是参考前面那篇博文了。1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上 的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.
使用pthread_cond_signal一般不会有“惊群现象”产生,但是多处理器的情形则不一定。其实从上面代码看出,我的代码也没有产生“惊群现象”。
综上,为了彻底不出错,对于唤醒后的线程还需要再对条件进行判断,直到条件满足。
代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "my_err.h"
#define N 32
int x = 0;
int y = 10;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void* modifier(void*);
void* tester(void*);
int main( int argc, char* argv[] ){
pthread_t tid1;
pthread_t tid_arr[N];
int ret = 0;
int i = 0;
ret = pthread_create( &tid1, NULL, modifier, NULL );
if(ret != 0)
err_msg(ret, "pthread_create");
for( i = 0; i < N; ++i ){
ret = pthread_create( tid_arr + i, NULL, tester, NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
}
ret = pthread_join( tid1, NULL );
if( ret != 0 )
err_msg( ret, "pthread_join" );
for( i = 0; i < N; ++i ){
ret = pthread_join( tid_arr[i], NULL );
if( ret != 0 )
err_msg( ret, "pthread_create" );
}
exit(EXIT_SUCCESS);
}
void* modifier(void*arg){
while(1){
pthread_mutex_lock(&mut);
if( x > y ){
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mut);
break;
}
else{
/* modify x and y */
++x;
--y;
printf( "%lu: x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
sleep(1);
}
}
}
void* tester(void*arg){
pthread_mutex_lock(&mut);
/* operate on x and y until x > y */
while( x <= y ){// 唤醒后对条件再判断,避免“惊群现象”
pthread_cond_wait(&cond, &mut);
}
printf( "%lu: x=%d, y=%d, and x > y now.\n", pthread_self(), x, y );
++x,++y;
printf( "%lu: we do increament on x and y, x=%d, y=%d.\n", pthread_self(), x, y );
pthread_mutex_unlock(&mut);
}
下面给出补票-买票的条件变量代码,其实我的代码也没有出现惊群现象,可能跟我的机器有关.
代码
#include "my_common.h"
#include "my_err.h"
#define THREADS_NUM 3
typedef struct task{
int ticket_num;
pthread_mutex_t lock;
pthread_cond_t empty; // when que is empty, send a signal
pthread_cond_t full; // when que is full, send a signal
}task_t, *p_task;
void task_init( p_task ptask );
void task_destroy( p_task ptask );
void main_handle( pthread_t* tid_arr, p_task ptask );
void* thread_handle( void* arg );
int main( int argc, char* argv[] ){
pthread_t tid_arr[THREADS_NUM];
task_t a_task;
main_handle( tid_arr, &a_task );
exit(EXIT_SUCCESS);
}
void task_init( p_task ptask ){
int ret = 0;
if(!ptask)
err_msg2("Invald arguments!");
ptask->ticket_num = 10;
ret = pthread_mutex_init( &ptask->lock, NULL );
if( 0 != ret )
err_msg( ret, "pthread_mutex_init" );
ret = pthread_cond_init( &ptask->empty, NULL );
if( 0 != ret )
err_msg( ret, "pthread_cond_init" );
ret = pthread_cond_init( &ptask->full, NULL );
if( 0 != ret )
err_msg( ret, "pthread_cond_init" );
return;
}
void task_destroy( p_task ptask ){
int ret = 0;
if(!ptask)
err_msg2("Invald arguments!");
ret = pthread_mutex_destroy( &ptask->lock );
if( 0 != ret )
err_msg( ret, "pthread_mutex_destroy" );
ret = pthread_cond_destroy( &ptask->empty );
if( 0 != ret )
err_msg( ret, "pthread_cond_destroy" );
ret = pthread_cond_destroy( &ptask->full );
if( 0 != ret )
err_msg( ret, "pthread_cond_destroy" );
return;
}
void main_handle( pthread_t* tid_arr, p_task ptask ){
int i = 0;
int ret = 0;
if(!tid_arr || !ptask)
err_msg2( "Invalid arguments!" );
task_init(ptask);
for(i = 0; i < THREADS_NUM; ++i){
ret = pthread_create( tid_arr + i, NULL, thread_handle, (void*)ptask );
if(ret != 0)
err_msg( ret, "pthread_create" );
}
/*busy waiting
while(1){
ret = pthread_mutex_lock( &ptask->lock );
if( 0 == ptask->ticket_num ){
printf( "%lu supply 5 tickets.\n", pthread_self() );
ptask->ticket_num += 5;
}
ret = pthread_mutex_unlock( &ptask->lock );
sleep( rand()%3+1 ); // for fair
}*/
while(1){
ret = pthread_mutex_lock(&ptask->lock);
//if( ptask->ticket_num > 0 ){ // 1.票数不为空
while( ptask->ticket_num > 0 ){ // 1.票数不为空 - 避免惊群现象
pthread_cond_wait( &ptask->empty, &ptask->lock ); // 2.阻塞,等待票数为空
}
printf( "%lu supply 5 tickets.\n", pthread_self() );
ptask->ticket_num += 5;
pthread_cond_signal( &ptask->full );
ret = pthread_mutex_unlock(&ptask->lock);
//sleep( rand()%3+1 ); // for fair
}
for(i = 0; i < THREADS_NUM; ++i){
ret = pthread_join( tid_arr[i], NULL);
if(ret != 0)
err_msg( ret, "pthread_join" );
}
task_destroy(ptask);
return;
}
void* thread_handle( void* arg ){
int ret = 0;
p_task ptask = (p_task)arg;
if(!ptask)
err_msg2( "Invalid arguments!" );
/*busy waiting
while(1){
ret = pthread_mutex_lock( &ptask->lock );
if( ptask->ticket_num > 0 ){
printf( "%lu buy a ticket, ticket number is %d.\n", pthread_self(), ptask->ticket_num );
ptask->ticket_num--;
}
ret = pthread_mutex_unlock( &ptask->lock );
sleep( rand()%3+1 ); // for fair
}*/
while(1){
ret = pthread_mutex_lock( &ptask->lock );
//if( 0 == ptask->ticket_num ){ // 1.票数为空
while( 0 == ptask->ticket_num ){ // 1.票数为空 - 避免惊群现象
pthread_cond_signal(&ptask->empty);
pthread_cond_wait( &ptask->full, &ptask->lock ); // 2.阻塞,等待票数满
}
printf( "%lu buy a ticket, ticket number is %d.\n", pthread_self(), ptask->ticket_num );
ptask->ticket_num--;
ret = pthread_mutex_unlock( &ptask->lock );
//sleep( rand()%3+1 ); // for fair
}
pthread_exit(NULL);
}
懒人笔记
这一块我补充一下进程的竞争条件,也就是说如果程序的执行顺序不一样,那么它的结果也是不一样的。先看下面这段代码:
代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handle(int num){
printf( "Signal number is %d.\n", num );
}
int main( int argc, char* argv[] ){
if( argc < 2 )
exit(EXIT_FAILURE);
int first = atoi( argv[1] );
pid_t pid = fork();
if(!pid){
if(1==first) // parent first
sleep(1);
printf( "This is child.\n" );
kill( getppid(), SIGUSR1 );
exit(1);
}
else{
if(2==first) // child first
sleep(1);
printf( "This is father.\n" );
signal( SIGUSR1, handle );
wait(NULL);
}
exit(EXIT_SUCCESS);
}
/* first = 1
This is father.
This is child.
Signal number is 10.
first = 2
This is child.
User defined signal 1
*/
上面的代码是父进程和子进程分别先后执行,其中父进程如果先执行,那么它要注册信号,然后子进程发送信号。可以正常结束。
但是如果子进程先执行,由于此时父进程没有注册信号,它收到了来自子进程的信号,此时父进程就会终止。再考虑下面这段代码,代码上来先忽略子进程发送的信号。主要是为了模拟可以让子进程先执行,父进程就无法退出的情形。
代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int flag = 0;
void handle(int num){
printf( "Signal number is %d.\n", num );
flag = 1;
}
int main( int argc, char* argv[] ){
if( argc < 2 )
exit(EXIT_FAILURE);
int first = atoi( argv[1] );
signal( SIGALRM, SIG_IGN ); // 忽略SIGALRM信号
pid_t pid = fork();
if(!pid){
if(1==first) // parent first
sleep(1);
printf( "This is child.\n" );
kill( getppid(), SIGALRM );
exit(1);
}
else{
if(2==first) // child first
sleep(1);
printf( "This is father.\n" );
signal( SIGALRM, handle );
while(!flag);
wait(NULL);
}
exit(EXIT_SUCCESS);
}
/*
first = 1
This is father.
This is child.
Signal number is 14.
first = 2
This is child.
This is father.
...程序卡住了
*/
上面的代码,如果子进程先执行,父进程是没法收到信号的,收不到信号无法改变循环不变式的值,就会卡主。这段代码比较好的模拟了竞争条件,主要是为了和之前说的条件变量的wait和signal。主要是为了硕pthread_cond_wait为什么要拿锁,并且时释放锁和休眠必须是原子操作。你想想,如果不拿锁,执行signal的那个进程和执行wait的进程同时执行,那边先唤醒了,这边才休眠,那不就是和上面代码一样了,永远没人能唤醒你。还有,如果释放锁和休眠不是原子操作,那么释放锁之后,这个锁被signal的进程抢走,然后立即signal,这边再休眠,还是不能被唤醒。所以,必须要拿锁,并且释放锁和休眠必须是原子操作。