20175306「情報セキュリティシステム設計の基礎」第13週の学習の概要
あなたは、綿密最も重要な章では、(期間は10ポイントを占めた)必要とされるものの再学ぶと思う本を検索:
- この章のすべての演習を完了
- 主なポイントの詳細な概要
- あなたにあなたの学習のパートナーの概要を説明し、フィードバックを得る結び目
上記研究の概要テンプレート、ブログ(エッセイ)によって公表された学習プロセスを参照すると、ブログのタイトル「研究」情報セキュリティシステム設計の基礎時間今週のように、ジョブ投入によるブログ(エッセイ)「第13週の研究では、結論付けました」夜11時59分。
章XII:並行プログラミング学習の概要
- ロジック制御が時間的に重複して流れる場合、それらは同時です。そして今、多くの異なるレベルのコンピュータシステム上の問題。
- 現代のオペレーティングシステムは、3つの基本的な構成並行プログラムの手段を提供する
。1.プロセス:各論理プロセスは、制御フローである
2.I / O多重化:並行プログラミングのこの形態において、Aでアプリケーションでスケジュールされたプロセスのコンテキストは、彼らの論理的な流れを示し
3.スレッド:スレッドが単一プロセス・フロー・ロジックコンテキストで実行されています。 - アプリケーションレベルの同時実行が有効です:
1.遅いI / Oデバイス。アプリケーションに到達する遅いI / Oデバイス(ディスクなど)からのデータを待っているときに、別のプロセスを実行するカーネルはCPUがビジー状態に保ちます。各アプリケーションは、I / O要求および他の有用な仕事を交互に、同様の方法で同時に使用することができます。
人々との相互作用2.。そして、人間とコンピュータの相互作用は、コンピュータが複数のタスクを同時に実行する能力を持っている必要があり。彼らは文書を印刷するときたとえば、あなたがウィンドウのサイズを変更することもできます。この機能は、最新のWindowsシステムの同時使用を提供します。ユーザが(例えばマウスクリックにより)特定の動作を要求するたびに、論理は、別個同時この操作を実行するために作成されて流れます。
3.減らすために仕事を遅らせることによって遅れました。時々 、アプリケーションはいくつかの同時操作の遅延を低減することによって、それらを遅延させることによって同時に実行、および他の操作が可能。アイドル状態のCPUサイクルがこのように動作する単一の遊離を減少させる、これらのアイドル期間を利用がある場合、例えば、ダイナミックメモリアロケータは、遅延組み合わせることができ、より低い優先度「マージ」ストリーム上で同時に実行中にそれを置きます遅延。
4.サービス複数のネットワーククライアント。
マルチコアマシン上で並列計算。最近の多くのシステムは、マルチコアプロセッサが装備されている、マルチコアプロセッサは、複数のCPUを含んでいます。これらのストリームはなくインターリーブ実行を、並列に実行することができるので、アプリケーションは、一般的に、より高速なマルチコアプロセッサマシン上で単一のマシン上で実行されているよりも並行フローに分割されます。
12.1プロセスベースの並行プログラミング
同時サーバーベースのプロセス
- リソースに死んだ子プロセスを回復するためにSIGCHLDハンドラを使用します。
- 親プロセスは、メモリリークを回避するために、(添付記述子の)それぞれのconnfdコピーを閉じる必要があります。
参照カウントでソケットファイルテーブルエントリconnfd親と子が閉じられるまでなので、クライアント接続が終了します。
プロセスのメリットについて
- ファイルテーブルを共有することで、親と子の間で共有状態情報、が、ユーザーのアドレス空間を共有しません。
明示的なプロセス間通信(IPC)機構を使用します。しかし、コストが高い、彼らが遅くなる傾向にあります。
同時基づいてプログラムI / Oの多重化
- I / Oイベントが発生した場合にのみ一つ以上の後、カーネルプロセスを一時停止が必要な、選択機能を使用して、それがアプリケーションに制御を返します。
int select(int n,fd_set *fdset,NULL,NULL,NULL); 返回已经准备好的描述符的非0的个数,若出错则为-1。
- タイプが設定された処理機能を選択FD_SET、サイズNビットのベクトルとして見て、記述子のセットと呼ばれます。
bn-1,......,b1,b0
- ディスクリプタのセットを処理する方法:
- それらの割り当て
- 別の変数にこのタイプの変数
FD_ZERO、FD_SET、FD_CLRとFD_ISSETマクロを変更し、それらを確認します。
同時イベント駆動型サーバーベースのI / Oの多重化
- I / O多重イベント同時運転者の基礎として使用することができます。
- ステート・マシン:状態、入力イベント、出力イベントや転移のセット。
循環から:入力と出力の状態間の同じ転送。
I / O多重化技術のメリット
- 比較すると、プロセスベースの設計は、単一のプロセスのコンテキストで実行されている、プログラマはプロセスの動作をより細かく制御できます、各論理の流れは簡単に流れの間でデータを共有して、アドレス空間のすべてにアクセスすることができます。
複雑性をコーディング、粒子サイズを小さくすると同時に、複雑さが増加します。粒子サイズ:タイムスライスごとに実行される命令の各論理ストリーム番号。
12.3スレッドベースの並行プログラミング
スレッド:プロセスフローのコンテキストで動作するロジックを自動的にカーネルによってスケジュールされ、それはそれ自身のスレッドコンテキスト、固有の整数ID、スタック、スタックポインタ、プログラムカウンタ、汎用レジスタ、及び条件コードを含むスレッドを有しています。で実行中のプロセスのすべてのスレッドは、プロセス全体の仮想アドレス空間を共有しました。
スレッドの実行モデル
ライフサイクルの開始時に各プロセスは、メインスレッド(メインスレッド)と呼ばれる単一のスレッド、です。ある時点で、ピア・スレッド(ピア・スレッド)を作成するためのメインスレッドは、この時点から開始し、2つのスレッドが同時に実行します。最後に、メインスレッドので、遅いシステムコールを実行します。それはインターバル割り込みシステムタイマーであるためか、制御はスレッドコンテキストスイッチによってピアに渡されます。ピアスレッドしばらく実行した後、メインスレッドに制御バックを渡して、上のようにします。
POSIXスレッド
- POSIXスレッドは、C言語処理スレッドにおける標準インターフェース、殺し、スレッドの回復、スレッドセーフな共有データとピアを作成するためのアプリケーションです。
スレッドコードとデータがローカルスレッドルーチンにカプセル化されています。
スレッドを作成します。
- 他のスレッドはpthread_createのを呼び出してスレッドを作成します。
int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f,void *arg); 成功则返回0,出错则为非零
関数が戻るには、パラメータは、IDの新規作成されたスレッドのTIDが含まれている場合は、新しいスレッドがpthread_self関数を呼び出すことによって、独自のスレッドIDを取得することができます。
pthread_t pthread_self(void);返回调用者的线程ID。
スレッドを終了
- スレッドは、次のいずれかの方法で終了します。
- スレッドの先頭に戻るには、スレッドが暗黙のうちに終了した場合。
関数を呼び出すことによってPthread_exit、明示的に終了したスレッド
void pthread_exit(void *thread_return);
資源回復スレッドが終了しました
他のスレッドを終了するための機能を待つを呼び出してpthread_joinをスレッド。
int pthread_join(pthread_t tid,void **thread_return); 成功则返回0,出错则为非零
切り離されたスレッド
時間内の任意の一点で、スレッドは、合成または分離することができます。糸の組み合わせは、その資源を回収し、再利用される前に、別のスレッドを殺すことができ、そのメモリリソースが解放されていません。それが終了すると、逆に別のスレッドは、リソースが自動的に解放されます。
int pthread_deacth(pthread_t tid); 成功则返回0,出错则为非零
スレッドを初期化します
pthread_onceは、スレッドルーチンに関連した初期化状態を可能にします。
pthread_once_t once_control=PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *once_control,void (*init_routine)(void)); 总是返回0
最初のステップ:サーバがクライアントからの接続要求を受け付けます。
子プロセスを作成するために、親クライアントの後、それが派生した新しい接続要求クライアント端末2、および戻り新しい子プロセス接続記述子(例えば記述子5)、その後、親と別のものを受け入れます記述子5と接続され、このサブプロセスは、その顧客にサービスを提供しています。
この場合には、親プロセスの下で接続要求を待っていると、2つのサブプロセスは、同時に、それぞれのクライアントにサービスを提供しています。
ステップ2:サーバースポーンこのクライアントサービスの子プロセス:
第三ステップ:サーバーは、別の接続要求を受け付けます。
スレッドの12.4以上のプログラムを共有変数
変数が共有されています。インスタンスを複数のスレッドは、この変数を参照する場合にのみ。
スレッド・メモリ・モデル
- 各スレッドは、独自のスレッドコンテキスト、一意の整数IDを含むスレッド、スタック、スタックポインタ、プログラムカウンタ、汎用レジスタ、及び条件コードを有しています。
- 登録は、共有されることはありません、そして仮想メモリは常に共有されています。
別のスレッドのスタックは、仮想アドレス空間のスタック領域に格納され、独立して、通常のアクセスに対応するスレッドですされています。
メモリへのマッピング変数
- 関数の外で定義された変数:グローバル変数
- ローカル自動変数は:関数内の変数を定義しますが、何の静的プロパティがありません。
ローカル静的変数:関数は、内部に定義され、変数の静的特性を有します。
共有変数
インスタンス変数が複数のスレッドを参照している場合にのみ場合、変数が共有されます。
12.5セマフォスレッド同期
同時に、共有変数の同期エラーの導入は、それは、オペレーティングシステムは、スレッドのための正しい順序を選択するかどうかを予測する方法はありません。
プログレスチャート
一つの状態から別の状態への命令としてモデル化されたn次元デカルト軌跡空間に実行モデルN同時スレッド、。
セマフォ
- P(s):如果s是非零的,那么P将s减一,并且立即返回。如果s为零,那么就挂起这个线程,直到s变为非零。
- V(s):将s加一,如果有任何线程阻塞在P操作等待s变为非零,那么V操作会重启线程中的一个,然后该线程将s减一,完成他的P操作。
- 信号量不变性:一个正确初始化了的信号量有一个负值。
- 信号量操作函数:
int sem_init(sem_t *sem,0,unsigned int value);//将信号量初始化为value int sem_wait(sem_t *s);//P(s) int sem_post(sem_t *s);//V(s)
使用信号量来实现互斥
- 二元信号量(互斥锁):将每个共享变量与一个信号量s联系起来,然后用P(s)(加锁)和V(s)(解锁)操作将相应的临界区包围起来。
禁止区:s<0,因为信号量的不变性,没有实际可行的轨迹线能够直接接触不安全区的部分
12.6 使用线程来提高并行性
并行程序的加速比通常定义为:
其中,p为处理器核的数量,T为在p个核上的运行时间。
12.7 其他并发问题
可重入性
- 可重入函数 (reentrant function),其特点在于它们具有这 样一种属性:当它们被多个线程调用时,不会引用任何共享数据。
- 可重入函数通常要比不可重人的线程安全的函数高效一些,因为它们不需要同步操作。更进一步来说,将第 2 类线程不安全函数转化为线程安全函数的唯一方法就是重写它,使之变为可重入的。
可重入函数、线程安全函数和线程不安全函数之间的集合关系:
- 检查某个函数的代码并先验地断定它是可重入的。
- 如果所有的函数参数都是传值传递的(即没有指针),并且所有的数据引用都是本地的自动栈变量(即没有引用静态或全局变量),那么函数就是显式可重入的 (explicitly reentrant),也就是说,无论它是被如何调用的,我们都可以断言它是可重入的。
我们总是使用术语可重入的 (reenntrant) 既包括显式可重入函数也包括隐式可重入函数。然而,认识到可重入性有时既是调用者也是被调用者的属性,并不只是被调用者单独的属性是非常重要的。
线程安全
- 定义四个(不相交的)线程不安全函数类:
- 不保护共享变量的函数。
- 保持跨越多个调用状态的函数。
- 返回指向静态变量指针的函数。
调用线程不安全函数的函数。
竞争
- 当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达他的控制流x点时,就会发生竞争。
为消除竞争,我么可以动态地为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针。
死锁
- 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
- 程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
- 重叠的禁止区域引起了一组称为死锁区域的状态。
- 死锁是一个相当难的问题,因为它是不可预测的。
互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。
小结:
- 一个并发程序是由在时间上重叠的一组逻辑流组成的。
- 三种不同的构建并发程序的机制:进程、I/O 多路复用和线程。
- 进程是由内核自动调度的,而且因为它们有各自独立的虚拟地址空间,所以要实现共享数 据,必须要有显式的 IPC 机制。事件驱动程序创建它们自己的并发逻辑流,这些逻辑流被模型化为状态机,用I/O 多路复用来显式地调度这些流。因为程序运行在一个单一进程中,所以在流之间共享数据速度很快而且很容易。线程是这些方法的综合。同基于进程的流一样,线程也是由内核自动调度的。同基于 I/O 多路复用的流一样,线程是运行在一个单一进程的上下文中的,因 此可以快速而方便地共享数据。
- 无论哪种并发机制,同步对共享数据的并发访问都是一个困难的问题。提出对信号量的 P 和 V操作就是为了帮助解决这个问题。信号量操作可以用来提供对共享数据的互斥访问,也对诸如生产者一消费者程序中有限缓冲区和读者一写者系统中的共享对象这样的资源访问进行调度。
- 并发也引人了其他一些困难的问题。被线程调用的函数必须具有一种称为线程安全的属性。
- 可重入函数是线程安全函数的一个真子集,它不访问任何共享数据。可重入函数通常比不可重人函数更为有效,因为它们不需要任何同步原语。竞争和死锁是并发程序中出现的另一些困难的问题。当程序员错误地假设逻辑流该如何调度时,就会发生竞争。当一个流等待一个永远不会发生的事件时,就会产生死锁。
教材学习中的问题和解决过程:
:练习题12.1:第33行代码,父进程关闭了连接描述符后,子进程仍然可以使用该描述符和客户端通信。为什么?
答:当父进程派生子进程是,它得到一个已连接描述符的副本,并将相关文件表中的引用计数从1增加到2.当父进程关闭他的描述符副本时,引用计数从2减少到1.因为内核不会关闭一个文件,直到文件表中他的应用计数值变为0,所以子进程这边的连接端将保持打开
练习题12.3:在Linux系统里,在标准输入上键入Ctrl+D表示EOF,若阻塞发生在对select的调用上,键入Ctrl+D会发生什么?
答:会导致select函数但会,准备好的集合中有描述符0
练习题12.4:在服务器中,每次使用select前都初始化pool.ready_set变量的原因?
答:因为pool.ready_set即作为输入参数也作为输出参数,所以在每一次调用select前都重新初始化他。输入时,他包含读集合,在输出,它包含准备好的集合
练习题12.5:在下图1中基于进程的服务器中,我们在两个位置小心地关闭了已连接描述符:父进程和子进程。然而,在图2中,基于线程的服务器中,我们只在一个位置关闭了已连接描述符:对等线程,为什么?
答:因为线程运行在同一个进程中,他们共享同一个描述符表,所以在描述符表中的引用计数与线程的多少是没有关系的都为1,因此只需要一个close就够了。
练习题12.9:设p表示生产者数量,c表示消费者数量,n表示以项目单元为单位的缓冲区大小。对于下面的美国场景,指出subfinsert和subfremove中的互斥信号量是否是必需的。
A.p =1,c =1,n>1
B.p =1,c=1,n=1
C.p>1,c>1,n=1
- A:是。因为生产者和消费者会并发地访问缓冲区
- B:不是。因为n=1,一个非空的缓冲区就相当于一个满的缓冲区。当缓冲区包含一个项目的时候,生产者就已经被阻塞了;当缓冲区为空的时候,消费者就被阻塞了。所以在任意时刻,只有一个线程可以访问缓冲区,不必加互斥锁。
- C:不是。同上。
练习题12.16:编写hello.c一个版本,创建和回收n个可结合的对等线程,其中n是一个命令行参数
- 答:代码如下
#include <stdio.h>
#include "csapp.h"
void *thread(void *vargp);
#define DEFAULT 4
int main(int argc, char* argv[]) {
int N;
if (argc > 2)
unix_error("too many param");
else if (argc == 2)
N = atoi(argv[1]);
else
N = DEFAULT;
int i;
pthread_t tid;
for (i = 0; i < N; i++) {
Pthread_create(&tid, NULL, thread, NULL);
}
Pthread_exit(NULL);
}
void *thread(void *vargp) {
printf("Hello, world\n");
return NULL;
}
练习题12.17:修改程序的bug,要求程序睡眠1秒钟,然后输出一个字符串
- 答:代码如下:
#include "csapp.h"
void *thread(void *vargp);
int main()
{
pthread_t tid;
Pthread_create(&tid, NULL, thread, NULL);
// exit(0);
Pthread_exit(NULL);
}
/* Thread routine */
void *thread(void *vargp)
{
Sleep(1);
printf("Hello, world!\n");
return NULL;
}
代码调试中的问题和解决过程
condvar.c
#include <stdlib.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct _msg{
struct _msg * next;
int num;
} msg;
msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *consumer ( void * p )
{
msg * mp;
for( ;; ) {
pthread_mutex_lock( &lock );
while ( head == NULL )
pthread_cond_wait( &has_product, &lock );
mp = head;
head = mp->next;
pthread_mutex_unlock ( &lock );
printf( "Consume %d tid: %d\n", mp->num, pthread_self());
free( mp );
sleep( rand() % 5 );
}
}
void *producer ( void * p )
{
msg * mp;
for ( ;; ) {
mp = malloc( sizeof(msg) );
pthread_mutex_lock( &lock );
mp->next = head;
mp->num = rand() % 1000;
head = mp;
printf( "Produce %d tid: %d\n", mp->num, pthread_self());
pthread_mutex_unlock( &lock );
pthread_cond_signal( &has_product );
sleep ( rand() % 5);
}
}
int main(int argc, char *argv[] )
{
pthread_t pid1, cid1;
pthread_t pid2, cid2;
srand(time(NULL));
pthread_create( &pid1, NULL, producer, NULL);
pthread_create( &pid2, NULL, producer, NULL);
pthread_create( &cid1, NULL, consumer, NULL);
pthread_create( &cid2, NULL, consumer, NULL);
pthread_join( pid1, NULL );
pthread_join( pid2, NULL );
pthread_join( cid1, NULL );
pthread_join( cid2, NULL );
return 0;
}
运行结果
mutex用于保护资源,wait函数用于等待信号,signal函数用于通知信号,wait函数中有一次对mutex的释放和重新获取操作,因此生产者和消费者并不会出现死锁。
createthread.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_t ntid;
void printids( const char *s )
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x) \n", s , ( unsigned int ) pid,
( unsigned int ) tid, (unsigned int ) tid);
}
void *thr_fn( void * arg )
{
printids( arg );
return NULL;
}
int main( void )
{
int err;
err = pthread_create( &ntid, NULL, thr_fn, "new thread: " );
if ( err != 0 ){
fprintf( stderr, "can't create thread: %s\n", strerror( err ) );
exit( 1 );
}
printids( "main threads: " );
sleep(1);
return 0;
}
运行结果
打印进程和线程ID
share.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
char buf[BUFSIZ];
void *thr_fn1( void *arg )
{
printf("thread 1 returning %d\n", getpid());
printf("pwd:%s\n", getcwd(buf, BUFSIZ));
*(int *)arg = 11;
return (void *) 1;
}
void *thr_fn2( void *arg )
{
printf("thread 2 returning %d\n", getpid());
printf("pwd:%s\n", getcwd(buf, BUFSIZ));
pthread_exit( (void *) 2 );
}
void *thr_fn3( void *arg )
{
while( 1 ){
printf("thread 3 writing %d\n", getpid());
printf("pwd:%s\n", getcwd(buf, BUFSIZ));
sleep( 1 );
}
}
int n = 0;
int main( void )
{
pthread_t tid;
void *tret;
pthread_create( &tid, NULL, thr_fn1, &n);
pthread_join( tid, &tret );
printf("n= %d\n", n );
printf("thread 1 exit code %d\n", (int) tret );
pthread_create( &tid, NULL, thr_fn2, NULL);
pthread_join( tid, &tret );
printf("thread 2 exit code %d\n", (int) tret );
pthread_create( &tid, NULL, thr_fn3, NULL);
sleep( 3 );
pthread_cancel(tid);
pthread_join( tid, &tret );
printf("thread 3 exit code %d\n", (int) tret );
}
运行结果
获得线程的终止状态,thr_fn 1,thr_fn 2和thr_fn 3三个函数对应终止线程的三种方法,即从线程函数return,调用pthread_exit终止自己和调用pthread_cancel终止同一进程中的另一个线程。
countwithmutex.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void *doit( void * );
int main(int argc, char **argv)
{
pthread_t tidA, tidB;
pthread_create( &tidA ,NULL, &doit, NULL );
pthread_create( &tidB ,NULL, &doit, NULL );
pthread_join( tidA, NULL );
pthread_join( tidB, NULL );
return 0;
}
void * doit( void * vptr)
{
int i, val;
for ( i=0; i<NLOOP; i++ ) {
pthread_mutex_lock( &counter_mutex );
val = counter++;
printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
counter = val + 1;
pthread_mutex_unlock( &counter_mutex );
}
return NULL;
}
运行结果
引入互斥锁(Mutex),获得锁的线程可以完成”读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据。
semphore.c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM];
sem_t blank_number, product_number;
void *producer ( void * arg )
{
static int p = 0;
for ( ;; ) {
sem_wait( &blank_number );
queue[p] = rand() % 1000;
printf("Product %d \n", queue[p]);
p = (p+1) % NUM;
sleep ( rand() % 5);
sem_post( &product_number );
}
}
void *consumer ( void * arg )
{
static int c = 0;
for( ;; ) {
sem_wait( &product_number );
printf("Consume %d\n", queue[c]);
c = (c+1) % NUM;
sleep( rand() % 5 );
sem_post( &blank_number );
}
}
int main(int argc, char *argv[] )
{
pthread_t pid, cid;
sem_init( &blank_number, 0, NUM );
sem_init( &product_number, 0, 0);
pthread_create( &pid, NULL, producer, NULL);
pthread_create( &cid, NULL, consumer, NULL);
pthread_join( pid, NULL );
pthread_join( cid, NULL );
sem_destroy( &blank_number );
sem_destroy( &product_number );
return 0;
}
运行结果:
semaphore表示信号量,semaphore变量的类型为sem_t,sem_init()初始化一个semaphore变量,value参数表示可用资源 的数量,pshared参数为0表示信号量用于同一进程的线程间同步。
count.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
void *doit( void * );
int main(int argc, char **argv)
{
pthread_t tidA, tidB;
pthread_create( &tidA ,NULL, &doit, NULL );
pthread_create( &tidB ,NULL, &doit, NULL );
pthread_join( tidA, NULL );
pthread_join( tidB, NULL );
return 0;
}
void * doit( void * vptr)
{
int i, val;
for ( i=0; i<NLOOP; i++ ) {
val = counter++;
printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
counter = val + 1;
}
}
运行结果:
- 这是一个不加锁的创建两个线程共享同一变量都实现加一操作的程序,在这个程序中虽然每个线程都给count加了5000,但由于结果的互相覆盖,最终输出值不是10000,而是5000。