http://blog.chinaunix.net/uid-21084809-id-2215376.html
1.9.2 进程的创建
pid_t fork(void) 功能:创建子进程。
fork 被调用一次,却返回两次。**先返回parent process, 后返回child process**
它可能有三种不同的返回值:
- 在父进程中,fork返回新创建的子进程的PID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
void main()
{
pid_t pid;
/*此时仅有一个进程*/
pid=fork();
/*此时已经有两个进程在同时运行*/
if(pid<0)
printf("error in fork!");
else if(pid==0)
printf("I am the child process, ID is %d\n",getpid());
else
printf("I am the parent process,ID is %d\n",getpid());
}
$./fork_test
I am the parent process, my process ID is 1991
I am the child process, my process ID is 1992
父进程的数据空间、堆栈空间都会给子进程一个拷贝,而不是共享这些内存。在子进程中对count进行自加1的操作,但是并没有影响到父进程中的count值,父进程中的count值仍然为0
pid_t vfork(void) 功能:创建子进程,parent process与child process 共享数据段
fork与vfork区别:
1.fork要拷贝父进程的数据段;**而vfork则不需要完全拷贝父进程的数据段,子进程与父进程共享数据段。**
2.fork不对父子进程的执行次序进行任何限制;而在**vfork调用中,子进程先运行,父进程挂起**
阻塞等待
wait
waitpid
中文 pthreads
linux man page
- 3: Library functions documents the functions provided by the standard C library.
pthread_attr_destroy(3)
pthread_attr_getaffinity_np(3)
pthread_attr_getdetachstate(3)
pthread_attr_getguardsize(3)
pthread_attr_getinheritsched(3)
pthread_attr_getschedparam(3)
pthread_attr_getschedpolicy(3)
pthread_attr_getscope(3)
pthread_attr_getstack(3)
pthread_attr_getstackaddr(3)
pthread_attr_getstacksize(3)
pthread_attr_init(3)
pthread_attr_setaffinity_np(3)
pthread_attr_setdetachstate(3)
pthread_attr_setguardsize(3)
pthread_attr_setinheritsched(3)
pthread_attr_setschedparam(3)
pthread_attr_setschedpolicy(3)
pthread_attr_setscope(3)
pthread_attr_setstack(3)
pthread_attr_setstackaddr(3)
pthread_attr_setstacksize(3)
pthread_cancel(3)
pthread_cleanup_pop(3)
pthread_cleanup_pop_restore_np(3)
pthread_cleanup_push(3)
pthread_cleanup_push_defer_np(3)
pthread_create(3)
pthread_detach(3)
pthread_equal(3)
pthread_exit(3)
pthread_getaffinity_np(3)
pthread_getattr_np(3)
pthread_getconcurrency(3)
pthread_getcpuclockid(3)
pthread_getname_np(3)
pthread_getschedparam(3)
pthread_join(3)
pthread_kill(3)
pthread_kill_other_threads_np(3)
pthread_rwlockattr_getkind_np(3)
pthread_rwlockattr_setkind_np(3)
pthread_self(3)
pthread_setaffinity_np(3)
pthread_setcancelstate(3)
pthread_setcanceltype(3)
pthread_setconcurrency(3)
pthread_setname_np(3)
pthread_setschedparam(3)
pthread_setschedprio(3)
pthread_sigmask(3)
pthread_sigqueue(3)
pthread_testcancel(3)
pthread_timedjoin_np(3)
pthread_tryjoin_np(3)
pthread_yield(3)
头文件:#include<pthread.h>
链接库: gcc -o test test.c -std=c11 -lpthread
线程
创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码),
所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源pthread_cleanup_push()/pthread_cleanup_pop()(类似于wait,waitpid)
但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞.
在有些情况下我们并不希望如此,比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码
pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)
这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
pthread_join使一个线程等待另一个线程结束。
代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。通过调用pthread_self()函数可以获得自身的线程号。
[设置线程属性 pthread_attr_init]https://www.linux.com/learn/docs/man/3789-pthreadattrinit3
具体介绍看上面的链接的man page
thread 同步方式:
互斥锁(Mutex) 用于控制访问 共享数据
条件变量(Condition variables)
Condition Variables同步机制
POSIX Pthreads Condition Variables API
Condition variables 对应的变量类型为:pthread_cond_t
pthread_cond_init(condition, attr)
一个 condition variable 必须被初始化,初始化有两种方式:
静态初始化 pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;
动态初始化可以使用 pthread_cond_init,参数 condition 用于返回 pthread_cond_t 变量,参数 attr 用于指定 condition variable 的属性,NULL 表示使用默认属性。属性对象的类型为 pthread_condattr_t,condition variable 只有一个进程共享属性,用于指定 condition variable 是否可以在不同的进程间使用(并不是所有的实现都支持进程共享属性)
pthread_cond_destroy(condition)
用于释放一个不再使用的 condition variable
pthread_cond_wait(condition, mutex)
此函数用于阻塞调用线程,直到此 condition variable 被激发。函数调用时需要使用一个已经锁定的 mutex 作参数(此 mutex 用于保护条件相关变量),调用会导致释放 mutex(这样其他线程就可以访问条件相关变量了)。之后阻塞线程被唤醒时,对应的 mutex 又会被自动的锁定(这样此线程则可以安全访问条件相关变量了)。最后程序员不要忘记去解锁此 mutex
pthread_cond_signal(condition)
用于激发 condition variable,唤醒一个等待此 condition variable 的线程
pthread_cond_broadcast(condition)
用于激发 condition variable,唤醒所有等待此 condition variable 的线程
- 信号量(semaphore)
信号量与其他进程间通信方式不大相同,主要用途是**保护临界资源。**
相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源。同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。
**信号量的分类:**
- 二值信号量:
`信号量`的值只能取0或1,类似于`互斥锁`。
但两者有不同:
`信号量`强调共享资源,只要共享资源可用,其他进程同样可以修改。
`信号量`的值;`互斥锁`更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
- 计数信号量:信号量的值可以取任意非负值
Synchronization between threads using read/write locks and barriers
自旋锁(Spin lock)
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
**其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。**
虽然它的效率比互斥锁高,但是它也有些不足之处:
1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。
自旋锁的用法如下:
首先定义:spinlock_t x;
然后初始化:spin_lock_init(spinlock_t *x); //自旋锁在真正使用前必须先初始化
在2.6.11内核中将定义和初始化合并为一个宏:DEFINE_SPINLOCK(x)
获得自旋锁:spin_lock(x); //只有在获得锁的情况下才返回,否则一直“自旋”
spin_trylock(x); //如立即获得锁则返回真,否则立即返回假
释放锁:spin_unlock(x);
作用域:
信号量: 进程间同步, 线程间保护临界 共享资源
互斥锁: 线程间(用于控制访问 共享数据)
NOTICE:
The POSIX semaphore API works with POSIX threads but is not part of threads standard, having been defined in the POSIX.1b, Real-time extensions (IEEE Std 1003.1b-1993) standard. Consequently the semaphore procedures are prefixed by "sem_" instead of "pthread_".
《Linux 多线程服务端编程:使用 muduo C++ 网络库》第 2.2 节总结了条件变量的使用要点:
条件变量只有一种正确使用的方式,几乎不可能用错。对于 wait 端:
1. 必须与 mutex 一起使用,该布尔表达式的读写需受此 mutex 保护。
2. 在 mutex 已上锁的时候才能调用 wait()。
3. 把判断布尔条件和 wait() 放到 while 循环中。
对于 signal/broadcast 端:
1. 不一定要在 mutex 已上锁的情况下调用 signal (理论上)。
2. 在 signal 之前一般要修改布尔表达式。
3. 修改布尔表达式通常要用 mutex 保护(至少用作 full memory barrier)。
4. 注意区分 signal 与 broadcast:“broadcast 通常用于表明状态变化,signal 通常用于表示资源可用。(broadcast should generally be used to indicate state change rather than resource availability。)”
归纳与总结:
- 只用非递归(non-recursive)的mutex。
- 不用 读写锁 和 信号量, 只用 互斥锁 和 条件变量。(muduo库和c++11 thread库也是只有 mutex和condition variable)
mutex和condition variable 都是非常底层的同步用语,主要用来实现更高级的并发编程工具。 一个多线程程序如果大量使用mutex和condition variable来同步,基本和铅笔刀锯大树一样。
所以==>
- 线程同步的四项原则:尽量用高层同步设施(线程池、队列、倒计时)
- 使用普通的 互斥锁 和 条件变量 完成剩余的同步工作,采用RAII惯用手法(idiom)和Scoped Locking.
以下是关于顺序执行线程 Demo
题目:比如3个线程,各自输出1,2,3。 让他们按照123,123,123 这个顺序连续打印10遍
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>//bool
// 线程数
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12
// 条件相关变量 count
int count = 0;
// 用于保护条件相关变量 count
pthread_mutex_t count_mutex[3];
// condition variable
pthread_cond_t count_threshold_cv[3];
int myTime = 0;
void *funcA(void *id)
{
long my_id = (long)id;
int i = 0;
for (i = 0; i < 10; i++) {
// 锁定条件相关变量
pthread_mutex_lock(&count_mutex[my_id]);
//do something
printf("AAA myTime =%d, %ld \n", myTime++, my_id);
//通知其他线程 开始作业
pthread_cond_signal(&count_threshold_cv[(my_id+1)%3]);//0+1 mod 3 = 1
// 等待 condition variable 被激发
pthread_cond_wait(&count_threshold_cv[my_id], &count_mutex[my_id]);
// 释放 mutex
pthread_mutex_unlock(&count_mutex[my_id]);
}
}
void *funcB(void *id)
{
long my_id = (long)id;
printf("enter B(): thread %ld\n", my_id);
int i = 0;
for (i = 0; i < 10; i++) {
// 锁定条件相关变量
pthread_mutex_lock(&count_mutex[my_id]);
// 等待 condition variable 被激发
pthread_cond_wait(&count_threshold_cv[my_id], &count_mutex[my_id]);
//do something
printf("BBB myTime =%d, %ld \n", myTime++, my_id);
//通知其他线程 开始作业
pthread_cond_signal(&count_threshold_cv[(my_id + 1) % 3]);//1+1 mod 3 = 2
// 释放 mutex
pthread_mutex_unlock(&count_mutex[my_id]);
}
}
void *funcC(void *id)
{
long my_id = (long)id;
int i = 0;
for (i = 0; i < 10; i++) {
// 锁定条件相关变量
pthread_mutex_lock(&count_mutex[my_id]);
// 等待 condition variable 被激发
pthread_cond_wait(&count_threshold_cv[my_id], &count_mutex[my_id]);
//do something
printf("CCC myTime =%d, %ld \n", myTime++, my_id);
//通知其他线程 开始作业
pthread_cond_signal(&count_threshold_cv[(my_id + 1) % 3]);//2+1 mod 3 = 0
// 释放 mutex
pthread_mutex_unlock(&count_mutex[my_id]);
}
}
int main(int argc, char *argv[])
{
int i;
long t1 = 0, t2 = 1, t3 = 2;
pthread_t threads[3];
// pthread_attr_t attr;
// 初始化 condition variable 和 mutex
pthread_mutex_init(&count_mutex[0], NULL);
pthread_mutex_init(&count_mutex[1], NULL);
pthread_mutex_init(&count_mutex[2], NULL);
pthread_cond_init(&count_threshold_cv[0], NULL);
pthread_cond_init(&count_threshold_cv[1], NULL);
pthread_cond_init(&count_threshold_cv[2], NULL);
// 初始化线程
// pthread_attr_init(&attr);
// pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);//default joinable
pthread_create(&threads[0], NULL, funcA, (void *)t1);
pthread_create(&threads[1], NULL, funcB, (void *)t2);
pthread_create(&threads[2], NULL, funcC, (void *)t3);
// 等待所有线程结束
for (i = 0; i<NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Main(): Waited on %d threads. Done.\n", NUM_THREADS);
// 清理和退出
// pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex[0]);
pthread_mutex_destroy(&count_mutex[1]);
pthread_mutex_destroy(&count_mutex[2]);
pthread_cond_destroy(&count_threshold_cv[0]);
pthread_cond_destroy(&count_threshold_cv[1]);
pthread_cond_destroy(&count_threshold_cv[2]);
pthread_exit(NULL);
}
gcc -o TestThread TestThread.c -pthread
./TestThread
(-pthred -lpthread的区别:待续)